antisketch 0.0.0-stage → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +103 -2
- package/app/distribution/brand.html +1 -0
- package/app/mcp.example.json +10 -0
- package/app/public/app.js +86 -0
- package/app/public/brand/favicon.svg +1 -0
- package/app/public/brand/identity.css +11 -0
- package/app/public/brand/identity.json +50 -0
- package/app/public/brand/mark.svg +1 -0
- package/app/public/fonts/Inter-LICENSE.txt +92 -0
- package/app/public/fonts/InterVariable.woff2 +0 -0
- package/app/public/fonts/PlayfairDisplay-Italic.ttf +0 -0
- package/app/public/fonts/PlayfairDisplay-LICENSE.txt +93 -0
- package/app/public/fonts/PlayfairDisplay.ttf +0 -0
- package/app/public/fonts/SpaceGrotesk-LICENSE.txt +93 -0
- package/app/public/fonts/SpaceGrotesk.ttf +0 -0
- package/app/public/index.html +15 -0
- package/app/public/setup.js +53 -0
- package/app/public/style.css +318 -0
- package/app/scripts/backup.js +8 -0
- package/app/scripts/restore.js +7 -0
- package/app/server/app.js +55 -0
- package/app/server/artwork.js +25 -0
- package/app/server/capture.js +56 -0
- package/app/server/codex-connection.js +89 -0
- package/app/server/compat.js +12 -0
- package/app/server/config.js +13 -0
- package/app/server/demo-fonts.js +14 -0
- package/app/server/files.js +9 -0
- package/app/server/generate.js +53 -0
- package/app/server/index.js +21 -0
- package/app/server/install.js +69 -0
- package/app/server/interaction-contract.js +2 -0
- package/app/server/interpret.js +108 -0
- package/app/server/library.js +44 -0
- package/app/server/mcp.js +15 -0
- package/app/server/network.js +23 -0
- package/app/server/package.js +33 -0
- package/app/server/releases.js +8 -0
- package/app/server/setup.js +6 -0
- package/app/server/store.js +25 -0
- package/app/server/validate.js +48 -0
- package/app/server/worker.js +26 -0
- package/bin/antisketch +25 -0
- package/bin/design-vault +3 -0
- package/package.json +80 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Harsh Joshi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,104 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Antisketch
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Turn websites you like into design skills for your coding agent.
|
|
4
|
+
|
|
5
|
+
Paste a link. Antisketch studies the site's typography, colors, spacing, and interactions, then builds two demos that use those ideas for different tasks. Review the results, ask for changes, and save the version you like as a skill.
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
The aim is to give your agent a useful understanding of a design: what gives it character, how the pieces work together, and how to apply it somewhere else.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
You'll need a Mac, Node.js 20.17 or newer, Google Chrome, and a Codex account.
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
npm install -g antisketch
|
|
17
|
+
antisketch
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The app opens at [localhost:4310](http://127.0.0.1:4310). Keep the Terminal window open while you use it; press **Ctrl-C** to stop.
|
|
21
|
+
|
|
22
|
+
Choose **Connect Codex** and sign in through the browser. If you're already signed in to Codex on your Mac, choose **Use this Codex account**. Antisketch includes the Codex runtime, so you don't need to install it separately. Preparing designs uses your account's allowance.
|
|
23
|
+
|
|
24
|
+
## From reference to skill
|
|
25
|
+
|
|
26
|
+
1. Add a website URL. You can leave a note about what caught your eye.
|
|
27
|
+
2. Review the captured pages, design tokens, and explanation of the design.
|
|
28
|
+
3. Try both demos. Each should apply the design to a different problem.
|
|
29
|
+
4. Ask for another revision if something feels off.
|
|
30
|
+
5. Pick a demo and approve the design when you're happy with it.
|
|
31
|
+
|
|
32
|
+
Preparation has several passes: capture desktop and mobile sections, write two distinct concept briefs, create original SVG artwork, build each demo, then render and test it. The two experiments build concurrently, with artwork, HTML/CSS, and JavaScript generated in separate passes to keep each response manageable. Codex compares the screenshots with the reference and gets one refinement pass when the review finds issues; experiments that need it are refined concurrently too, and a step that stalls is retried once automatically. This takes longer and uses more of your Codex allowance than a single generation. The visual review is visible beside the experiments; it helps you judge the result, but it isn't a quality guarantee.
|
|
33
|
+
|
|
34
|
+
You decide what gets approved. An approved skill includes the design spec, tokens, supporting evidence, and your chosen demo. Earlier approvals stay available when you make a new revision.
|
|
35
|
+
|
|
36
|
+
On an approved skill, choose **Use in…** to install it into Codex or Claude Code. For Devin, download the repository package and add it to your project. Other agents can use the portable ZIP by reading its `SKILL.md`.
|
|
37
|
+
|
|
38
|
+
## Where your work lives
|
|
39
|
+
|
|
40
|
+
Your library is saved on your Mac:
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
~/Library/Application Support/Antisketch/
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
A new installation starts empty. Updating or uninstalling the npm package leaves your library in place.
|
|
47
|
+
|
|
48
|
+
Antisketch sends reference screenshots and measurements to Codex to prepare the design and demos. Your saved library stays on disk. Codex handles sign-in credentials; Antisketch doesn't put them in your library or backups.
|
|
49
|
+
|
|
50
|
+
You can disconnect in **Studio settings** and still browse or export saved skills. Disconnecting Antisketch doesn't sign you out of other Codex apps.
|
|
51
|
+
|
|
52
|
+
## Updates and backups
|
|
53
|
+
|
|
54
|
+
Stop the app before updating, then run:
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
npm install -g antisketch@latest
|
|
58
|
+
antisketch
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
If you installed the earlier `@josharsh/antisketch` package, stop the app and switch once:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
npm uninstall -g @josharsh/antisketch
|
|
65
|
+
npm install -g antisketch
|
|
66
|
+
antisketch
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Your library and Codex connection stay in the same location.
|
|
70
|
+
|
|
71
|
+
To make a backup, stop the app and run:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
antisketch backup
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
It prints the backup location. You can also choose a destination with `antisketch backup /path/to/backup`. Copy backups to another disk if you want protection against losing this Mac.
|
|
78
|
+
|
|
79
|
+
To restore, stop the app, move the current library folder aside, and run `antisketch restore /path/to/backup`. Restore needs an empty destination. You'll connect Codex again afterward.
|
|
80
|
+
|
|
81
|
+
## Connect through MCP
|
|
82
|
+
|
|
83
|
+
An agent can read your approved skills through Antisketch's MCP server:
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"mcpServers": {
|
|
88
|
+
"antisketch": {
|
|
89
|
+
"command": "antisketch",
|
|
90
|
+
"args": ["mcp"]
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The MCP server can list and read approved skills. Review and approval happen in the app. If your agent can't find `antisketch`, use the path printed by `which antisketch`.
|
|
97
|
+
|
|
98
|
+
## A few things to know
|
|
99
|
+
|
|
100
|
+
Antisketch currently works with public websites on macOS. Sites behind a login or bot check may not capture correctly. It samples pages and interactions, so the extraction can miss details. The demos are a starting point for your review.
|
|
101
|
+
|
|
102
|
+
Capturing a design doesn't give you permission to reuse its fonts, photos, or logos. Source assets aren't copied into demos. Antisketch can embed its bundled, openly licensed Inter, Space Grotesk, and Playfair Display fonts. Other fonts use disclosed fallbacks. Illustrations are generated in code; photography and painterly artwork may still need a separate asset-making pass.
|
|
103
|
+
|
|
104
|
+
For a different port, run `PORT=4311 antisketch`. For a different library location, set `ANTISKETCH_DATA_DIR`; use the same setting for the app, backups, and MCP. `antisketch --no-open` starts the app without opening a browser.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Antisketch — A workshop for your eye</title><link rel="icon" href="/brand/favicon.svg"><link rel="stylesheet" href="/brand/identity.css"></head><body><header class="masthead"><a class="brand" href="/"><img src="/brand/mark.svg" alt=""><span>antisketch<small>A workshop for your eye</small></span></a><a class="back-link" href="/">Open your workshop ↗</a></header><main class="wrap"><section class="hero"><div class="hero-copy"><p class="eyebrow">YOUR EYE SETS THE DIRECTION</p><h1>Give your<br>agent an eye<span class="red">.</span></h1><p class="hero-intro">Bring a design you love. Study what makes it work. Try its language somewhere new—and keep what works as a reusable skill.</p><a class="text-link" href="/">Add your first reference ↗</a></div><div><p class="eyebrow">OBSERVE → EXPERIMENT → KEEP → USE</p><h2>A library of<br>your own decisions.</h2><p class="hero-intro">Your workspace starts empty. The references you collect, experiments you make, and skills you approve belong to your personal library on this Mac.</p></div></section><section class="tool-row"><div class="tool-label"><span class="mono">THE ANTISKETCH IDENTITY</span><p>Paper. Ink.<br>One decisive accent.</p></div><div class="tool-content"><div class="signatures"><div class="signature"><img src="/brand/mark.svg" alt=""><b>antisketch</b></div><div class="signature dark"><img src="/brand/mark.svg" alt=""><b>antisketch</b></div></div><div class="asset-links"><a href="/brand/mark.svg" download>Download the mark ↓</a><a href="/brand/identity.json" download>Identity tokens ↓</a></div></div></section></main></body></html>
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const $=s=>document.querySelector(s),$$=s=>[...document.querySelectorAll(s)];
|
|
3
|
+
const esc=v=>String(v??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
|
4
|
+
const active=['queued','capturing','interpreting','validating'];
|
|
5
|
+
let session=null,designs=[],releases=[],detail=null,tab='reference',selectedDemo=1,device='desktop',screenshot=0,revision=null,search='',routeGeneration=0,polling=false,elapsedTimer=null;
|
|
6
|
+
let pollFingerprint='',listFingerprint='',resizeObserver,approvalTarget=null;
|
|
7
|
+
const main=$('#main');
|
|
8
|
+
async function api(url,options={}){const response=await fetch(url,{...options,headers:{'Content-Type':'application/json',...(session?{'X-Antisketch-Token':session.token}:{}),...options.headers}});const result=await response.json();if(!response.ok){const error=new Error(result.error||'Unable to complete this action');error.id=result.id;throw error;}return result;}
|
|
9
|
+
function toast(message){$('#toast').textContent=message;$('#toast').classList.add('show');clearTimeout(toast.timer);toast.timer=setTimeout(()=>$('#toast').classList.remove('show'),4000);}
|
|
10
|
+
function date(value){return new Date(value).toLocaleDateString(undefined,{month:'short',day:'numeric'});}
|
|
11
|
+
function badge(status){return `<span class="badge ${active.includes(status)?'work':status==='failed'?'fail':status==='approved'?'approved':status==='cancelled'?'muted':''}">${esc(({queued:'In the queue',capturing:'Capturing',interpreting:'Finding its character',validating:'Checking the demos',review:'Ready for your eye',approved:'Approved skill',failed:'Needs attention',cancelled:'Paused here'})[status]||status)}</span>`;}
|
|
12
|
+
function file(name){return `/files/${detail.id}/${detail.selectedRevision.number}/${name}`;}
|
|
13
|
+
function renderNav(mode){$$('[data-nav]').forEach(el=>{const on=el.dataset.nav===mode;el.classList.toggle('active',on);if(on)el.setAttribute('aria-current','page');else el.removeAttribute('aria-current');});$('#inbox-count').textContent=designs.filter(d=>!d.archived&&d.status!=='approved').length;$('#library-count').textContent=new Set(releases.map(r=>r.design_id)).size;}
|
|
14
|
+
function renderConnection(){if(!session)return;$('#connection').textContent=session.provider.ready?'Connected to Codex':session.provider.state==='limited'?'Codex usage limit':'Connect Codex';$('#connection').classList.toggle('off',!session.provider.ready);}
|
|
15
|
+
async function refreshLists(){[designs,releases]=await Promise.all([api('/api/designs'),api('/api/releases')]);}
|
|
16
|
+
function collectionHeading(mode){
|
|
17
|
+
const titles={inbox:'The working<br>collection<span>.</span>',library:'An archive<br>of your eye<span>.</span>',archive:'Set aside<span>.</span><br>Still here.'};
|
|
18
|
+
const descriptions={inbox:'Start with something that catches your eye.<br>Study its language. Make something of your own.',library:'Design languages you’ve tried, refined, and kept.<br>Ready to become part of your next project.',archive:'References for another day.<br>Every capture and revision stays with them.'};
|
|
19
|
+
const last=releases[0],count=new Set(releases.map(r=>r.design_id)).size;
|
|
20
|
+
const specimen=last?`<a class="kept-specimen" href="#design/${last.design_id}/${last.revision}" aria-label="Open saved design: ${esc(last.name)}"><div class="specimen-print"><div class="specimen-top"><span>FROM THE COLLECTION</span><span>↗</span></div><img src="/files/${last.design_id}/${last.revision}/evidence/desktop.png" alt="${esc(last.name)} reference" loading="lazy"><div class="specimen-caption">${esc(last.name)}</div></div><span class="specimen-stamp">KEPT<br>${String(count).padStart(3,'0')}</span></a>`:`<div class="archive-mark" aria-hidden="true"><span></span><span></span><span></span><i>ROOM FOR A NEW POINT OF VIEW</i></div>`;
|
|
21
|
+
return `<section class="collection-intro"><div><div class="eyebrow"><span class="index-dot"></span>${({inbox:'01 / ANTISKETCH — THE WORKSHOP',library:'02 / THE APPROVED COLLECTION',archive:'03 / THE RESERVE COLLECTION'})[mode]}</div><h1>${titles[mode]}</h1><p>${descriptions[mode]}</p></div><div class="collection-aside">${specimen}</div></section>`;
|
|
22
|
+
}
|
|
23
|
+
function renderList(mode){main.className='collection-page';listFingerprint=JSON.stringify([designs,releases]);renderNav(mode);$('#breadcrumb').textContent='ANTISKETCH / '+({inbox:'WORK IN PROGRESS',library:'APPROVED COLLECTION',archive:'ARCHIVE'})[mode];
|
|
24
|
+
if(mode==='library'){main.innerHTML=collectionHeading(mode)+`<div class="section-line"><h2>THE KEPT EDITIONS <small>${String(releases.length).padStart(2,'0')}</small></h2><span class="eyebrow">READY FOR YOUR NEXT PROJECT</span></div>${releases.length?'<div class="release-list">'+releases.map(r=>`<article class="release-card"><div><span class="badge approved">APPROVED · V${r.version}</span><h3>${esc(r.name)}</h3><small>Revision ${r.revision} · kept ${date(r.created_at)}</small></div><div><a class="button secondary small" href="#design/${r.design_id}/${r.revision}">View design ↗</a><button class="button small" data-use-release="${r.id}">Use in… ↗</button></div></article>`).join('')+'</div>':`<div class="empty"><span class="empty-number" aria-hidden="true">00</span><div><span class="eyebrow">A COLLECTION IN THE MAKING</span><h2>Keep only what<br>you believe in.</h2><p>Your approved design languages will live here.</p><a class="button secondary" href="#inbox">Review a reference ↗</a></div></div>`}<div class="library-note"><span>↗</span>Versioned, portable, ready for your agent.<span class="note-end">ANTISKETCH / YOUR LOCAL WORKSHOP</span></div>`;return;}
|
|
25
|
+
const items=designs.filter(d=>mode==='archive'?d.archived:!d.archived&&d.status!=='approved').filter(d=>(d.name+' '+d.url).toLowerCase().includes(search.toLowerCase()));
|
|
26
|
+
main.innerHTML=collectionHeading(mode)+`${mode==='inbox'?`<form class="capture-bar" id="quick-add"><label for="quick-url">ADD A REFERENCE<span>Start with a website.</span></label><input id="quick-url" aria-label="Website to capture" name="url" placeholder="Paste a website URL…" inputmode="url"><button class="button" type="submit">Add reference <span>↗</span></button></form>${session&&!session.provider.ready?'<div class="notice warn">To prepare new references, connect Codex in Studio settings.</div>':''}`:''}<div class="section-line"><h2>${mode==='archive'?'ON THE SHELF':'ON THE TABLE'} <small>${items.length.toString().padStart(2,'0')}</small></h2><input id="search" aria-label="Search references" placeholder="Find a reference ↗" value="${esc(search)}"></div><div class="cards">${items.length?items.map((d,i)=>`<a href="#design/${d.id}" class="design-card"><div class="card-index"><span>STUDY ${String(i+1).padStart(3,'0')}</span><span>↗</span></div><div class="card-image ${['queued','failed','cancelled'].includes(d.status)?'placeholder':''}"><img src="/files/${d.id}/${d.current_revision}/evidence/desktop.png" alt="Captured ${esc(new URL(d.url).hostname)} homepage" loading="lazy">${badge(d.status)}</div><div class="card-body"><h3>${esc(d.name)}</h3><p>${esc(new URL(d.url).hostname)}</p><div class="card-footer"><span>R${d.current_revision} · ${date(d.updated_at)}${d.release_count?' · '+d.release_count+' approved':''}</span><span>Open study ↗</span></div></div></a>`).join(''):`<div class="empty"><span class="empty-number" aria-hidden="true">00</span><div><span class="eyebrow">${search?'SEARCH THE COLLECTION':mode==='archive'?'NOTHING SET ASIDE':'THE DESK IS CLEAR'}</span><h2>${search?'No matching references.':mode==='archive'?'For another day.':'Leave room for<br>the next good thing.'}</h2><p>${search?'Try a different name or website.':mode==='archive'?'Archived references stay here until you’re ready.':'Add a reference above. We’ll uncover its design language and explore it in two new contexts.'}</p></div>${!search&&mode==='inbox'?'<ol class="desk-process"><li><span>01</span><div>Look closely<small>Capture the details.</small></div></li><li><span>02</span><div>Try another angle<small>Explore working examples.</small></div></li><li><span>03</span><div>Make the call<small>Only you decide what stays.</small></div></li></ol>':''}</div>`}</div><div class="library-note"><span>↗</span>Your eye sets the direction. Your agent carries it forward.<span class="note-end">ANTISKETCH / YOUR LOCAL WORKSHOP</span></div>`;
|
|
27
|
+
$$('.card-image img,.specimen-print img').forEach(img=>img.addEventListener('error',()=>{img.style.display='none';}));
|
|
28
|
+
$('#quick-add')?.addEventListener('submit',e=>{e.preventDefault();openNew(new FormData(e.target).get('url'));});
|
|
29
|
+
$('#search').addEventListener('input',e=>{const position=e.target.selectionStart;search=e.target.value;renderList(mode);$('#search').focus();$('#search').setSelectionRange(position,position);});
|
|
30
|
+
}
|
|
31
|
+
function renderDetail(){main.className='detail-page';const d=detail,r=d.selectedRevision;renderNav(r.status==='approved'?'library':d.archived?'archive':'inbox');$('#breadcrumb').textContent='Your workspace / '+d.name;const editable=r.number===d.current_revision;const busy=active.includes(r.status);const release=d.releases.find(x=>x.revision===r.number);
|
|
32
|
+
main.innerHTML=`<div class="meta-row"><a href="#inbox">← Your desk</a>${badge(r.status)}<label>Revision <select id="revision-select" aria-label="View revision">${d.revisions.map(v=>`<option value="${v.number}" ${v.number===r.number?'selected':''}>${v.number} · ${esc(v.status)}</option>`).join('')}</select></label><a href="${esc(d.url)}" target="_blank" rel="noopener noreferrer">${esc(new URL(d.url).hostname)} ↗</a></div><div class="detail-head"><div><h1>${esc(d.summary?.name||d.name)}</h1><p>${esc(d.summary?.summary||'We’re gathering the details that give this reference its character. You can leave this screen; preparation continues while the app is running.')}</p></div><div class="detail-actions">${release?`<button class="button small" data-use-release="${release.id}">Use in… ↗</button><a class="button secondary small" href="/api/releases/${release.id}/download">Download skill ↓</a>`:''}${!busy&&editable?`<button class="button secondary small" id="archive-button">${d.archived?'Restore to inbox':'Archive'}</button>`:''}</div></div>${!editable?'<div class="notice">You’re looking at a previous revision. Its record is preserved; changes and approval happen on the latest revision.</div>':''}${r.status==='approved'?`<div class="notice">Version ${release?.version||''} is approved and frozen. Start a new revision below whenever your taste evolves.</div>`:''}${busy?progressHTML(r):r.status==='failed'||r.status==='cancelled'?`<div class="progress-card"><span class="eyebrow">${r.status==='failed'?'NEEDS A LITTLE ATTENTION':'STOPPED FOR NOW'}</span><h2>${r.status==='failed'?'Let’s give this another look.':'Your work is still here.'}</h2><p class="muted">${esc(r.error||r.stage)}</p><button class="button" id="retry-button">Retry extraction ↗</button>${d.capture?'<p class="fine">Captured evidence is retained below, even though this revision is incomplete.</p>':''}</div>`:''}${d.summary||d.capture?`<div class="tabs" role="tablist" aria-label="Review sections">${[['reference','01','Reference'],['language','02','Design language'],['experiments','03','Experiments'],['review','04','Review & keep']].map(([id,n,label])=>`<button role="tab" id="tab-${id}" aria-controls="tab-content" aria-selected="${tab===id}" tabindex="${tab===id?'0':'-1'}" data-tab="${id}" class="${tab===id?'active':''}"><span>${n}</span>${label}</button>`).join('')}</div><section id="tab-content" role="tabpanel" aria-labelledby="tab-${tab}"></section>`:''}`;
|
|
33
|
+
clearInterval(elapsedTimer);if(busy){const since=Date.parse(r.updated_at);const tick=()=>{const el=$('#elapsed');if(!el)return;const s=Math.max(0,Math.round((Date.now()-since)/1000));el.textContent=`${Math.floor(s/60)}:${String(s%60).padStart(2,'0')}`;};tick();elapsedTimer=setInterval(tick,1000);}
|
|
34
|
+
$('#revision-select').addEventListener('change',e=>{location.hash=`design/${d.id}/${e.target.value}`;});
|
|
35
|
+
$('#archive-button')?.addEventListener('click',()=>mutate(async()=>{await api(`/api/designs/${d.id}/archive`,{method:'POST',body:JSON.stringify({archived:!d.archived})});toast(d.archived?'Back on your desk':'Reference archived');await refreshLists();await loadDetail(d.id,r.number); }));
|
|
36
|
+
$('#cancel-button')?.addEventListener('click',()=>mutate(async()=>{await api(`/api/designs/${d.id}/cancel`,{method:'POST',body:'{}'});await loadDetail(d.id,r.number);}));
|
|
37
|
+
$('#retry-button')?.addEventListener('click',()=>newRevision(r.feedback||''));
|
|
38
|
+
$$('[data-tab]').forEach(button=>button.addEventListener('click',()=>{tab=button.dataset.tab;renderDetail();$(`#tab-${tab}`)?.focus();}));
|
|
39
|
+
$('.tabs')?.addEventListener('keydown',e=>{if(!['ArrowLeft','ArrowRight','Home','End'].includes(e.key))return;e.preventDefault();const tabs=$$('[data-tab]');let i=tabs.indexOf(document.activeElement);i=e.key==='Home'?0:e.key==='End'?tabs.length-1:(i+(e.key==='ArrowRight'?1:-1)+tabs.length)%tabs.length;tabs[i].click();});
|
|
40
|
+
renderTab();
|
|
41
|
+
}
|
|
42
|
+
function progressHTML(r){const stages=['queued','capturing','interpreting','validating'],current=stages.indexOf(r.status);return `<div class="progress-card"><div class="spinner" aria-hidden="true"></div><h2>${esc(r.stage)} <span class="elapsed" id="elapsed"></span></h2><p class="muted">We’re preparing an evidence-backed design study and two working applications. This usually takes about ten minutes; each step takes a minute or two. You can keep browsing your collection.</p><div class="steps">${['Queued','Capture','Interpret & create','Check'].map((x,i)=>`<span class="step ${i<current?'done':i===current?'current':''}">${i<current?'✓':String(i+1).padStart(2,'0')} ${x}</span>`).join('')}</div><button class="button secondary small" id="cancel-button">Cancel preparation</button></div>`;}
|
|
43
|
+
function renderTab(){resizeObserver?.disconnect();const root=$('#tab-content');if(!root)return;const d=detail,r=d.selectedRevision;
|
|
44
|
+
if(tab==='reference'){const c=d.capture;if(!c){root.innerHTML='<p class="muted">Reference captures will appear here when inspection finishes.</p>';return;}screenshot=Math.min(screenshot,c.screenshots.length-1);const s=c.screenshots[screenshot];root.innerHTML=`<div class="split"><div class="panel"><div class="panel-top"><select id="capture-select" aria-label="Reference capture">${c.screenshots.map((s,i)=>`<option value="${i}" ${i===screenshot?'selected':''}>${esc(s.label)}</option>`).join('')}</select><span>${s?.viewport?esc(s.viewport.width+' × '+s.viewport.height):'CAPTURED REFERENCE'}</span></div>${s?`<a href="${file(s.file)}" target="_blank" rel="noopener"><img class="reference-image" src="${file(s.file)}" alt="${esc(s.label)}"></a>`:'<p>No screenshot available.</p>'}</div><aside><div class="side-note"><span class="eyebrow">WHAT WE LOOKED AT</span><h3 style="margin-top:15px">Captured ${date(c.capturedAt)}</h3><span class="metric">${c.coverage.routes.length} <small style="font:12px var(--sans)">pages sampled</small></span><p>${c.coverage.viewports.map(esc).join('<br>')}</p></div><div class="side-note"><h3>States inspected</h3>${c.coverage.states.map(x=>`<span class="scope-pill">${esc(x)}</span>`).join('')}</div><div class="side-note"><h3>Know the edges</h3><ul>${[...c.warnings,...c.coverage.limits].map(x=>`<li>${esc(x)}</li>`).join('')}</ul></div><p class="fine">Source imagery is reference material. Approval does not grant permission to reuse its photos, logos, or fonts.</p></aside></div>`;$('#capture-select').addEventListener('change',e=>{screenshot=Number(e.target.value);renderTab();});return;}
|
|
45
|
+
if(!d.summary){root.innerHTML='<div class="notice">The design interpretation is not ready yet. Check the reference captures or retry preparation.</div>';return;}
|
|
46
|
+
if(tab==='language'){const tokens=d.tokens?.tokens||[];root.innerHTML=`<div class="notice">Measured values and interpreted principles stay separate. Tokens below retain their source evidence and captured scope.</div><div class="principles">${d.summary.principles.map(p=>`<article class="principle"><small>${esc(p.classification)}</small><h3>${esc(p.title)}</h3><p>${esc(p.description)}</p></article>`).join('')}</div><div class="section-line"><h2>The working ingredients <small>${tokens.length} tokens</small></h2><a class="link-button" href="${file('tokens.json')}" target="_blank" rel="noopener">View token data ↗</a></div><div class="swatches">${tokens.filter(t=>t.category==='color').map(t=>`<div class="swatch"><div class="swatch-color" data-color="${esc(t.value)}"></div><div class="swatch-info"><b>${esc(t.name)}</b><span>${esc(t.value)}</span></div></div>`).join('')}</div>${['fontFamily','dimension','spacing','radius','shadow','motion'].map(category=>{const group=tokens.filter(t=>t.category===category);return group.length?`<section class="token-section"><h3>${esc(({fontFamily:'Typography · family',dimension:'Typography · dimensions'})[category]||category)}</h3><table class="token-table"><thead><tr><th>NAME</th><th>MEASURED VALUE</th><th>SCOPE & EVIDENCE</th></tr></thead><tbody>${group.map(t=>`<tr><td>${esc(t.name)}</td><td>${esc(t.value)}</td><td>${esc(t.role)}<small>${t.evidence.map(e=>esc(e.capture+' · '+e.selector+' · '+e.property)).join('<br>')}</small></td></tr>`).join('')}</tbody></table></section>`:'';}).join('')}<details class="spec"><summary>Read the complete design specification ↗</summary><pre class="prose">${esc(d.specification)}</pre></details>`;$$('[data-color]').forEach(el=>{if(CSS.supports('color',el.dataset.color))el.style.backgroundColor=el.dataset.color;});return;}
|
|
47
|
+
if(tab==='experiments'){root.innerHTML=`${d.summary.visualReview?`<details class="spec"><summary>What the visual review found${d.summary.visualReview.repaired?' · refined after review':''}</summary><p>${esc(d.summary.visualReview.comparison)}</p>${d.summary.visualReview.experiments.map((v,i)=>`<p><b>Experiment ${i+1}</b> · ${esc(v.strengths.join(' '))}</p>${v.issues.length?`<ul>${v.issues.map(x=>`<li>${esc(x)}</li>`).join('')}</ul>`:''}`).join('')}<p class="fine">${esc(d.summary.visualReview.scope)}</p></details>`:''}<div class="demo-choices">${d.summary.demos.map(demo=>`<button class="demo-choice ${selectedDemo===demo.id?'active':''}" data-demo="${demo.id}" aria-pressed="${selectedDemo===demo.id}"><small>EXPERIMENT ${String(demo.id).padStart(2,'0')}</small><strong>${esc(demo.title)}</strong><p>${esc(demo.brief)}</p></button>`).join('')}</div><div class="device-bar"><span>LIVE EXAMPLE · Try the interactions</span><div class="segmented" role="group" aria-label="Preview size"><button data-device="desktop" aria-pressed="${device==='desktop'}" class="${device==='desktop'?'active':''}">Desktop</button><button data-device="mobile" aria-pressed="${device==='mobile'}" class="${device==='mobile'?'active':''}">Mobile</button></div><button class="link-button" id="reload-demo">Reset example ↻</button></div><div class="demo-canvas"><div class="iframe-shell"><iframe title="${esc(d.summary.demos.find(x=>x.id===selectedDemo)?.title)} interactive demo" sandbox="allow-scripts" src="${file('demo-'+selectedDemo+'.html')}"></iframe></div></div><p class="demo-footnote">The example runs in an isolated preview. External requests and storage are disabled. ${device==='mobile'?'390px touch-sized layout':'1440px desktop layout, scaled to fit'} · ${esc(d.validation?.scope||'')}</p>${d.validation&&!d.validation.passed?'<div class="notice warn" style="margin-top:15px">Some render checks need attention. Review the issues in Review & keep, then request changes.</div>':''}`;
|
|
48
|
+
$$('[data-demo]').forEach(b=>b.addEventListener('click',()=>{selectedDemo=Number(b.dataset.demo);renderTab();}));$$('[data-device]').forEach(b=>b.addEventListener('click',()=>{device=b.dataset.device;renderTab();}));$('#reload-demo').addEventListener('click',()=>{$('.iframe-shell iframe').src=file('demo-'+selectedDemo+'.html');});resizeObserver=new ResizeObserver(fitDemo);resizeObserver.observe($('.demo-canvas'));fitDemo();return;}
|
|
49
|
+
const editable=r.number===d.current_revision&&!active.includes(r.status);const canApprove=editable&&r.status==='review'&&d.validation?.passed&&!d.archived;
|
|
50
|
+
root.innerHTML=`${d.validation&&!d.validation.passed?`<div class="notice error"><b>Resolve these issues before approval</b><ul>${d.validation.checks.filter(c=>!c.passed).map(c=>`<li>Demo ${c.demo}, ${c.width}px: ${esc(c.issues.join('; '))}</li>`).join('')}</ul></div>`:''}<div class="review-grid"><div><section class="panel pad"><span class="eyebrow">ANOTHER PASS IS PART OF THE PROCESS</span><h2 style="margin-top:15px">What would make it better?</h2><p class="muted">Keep what works. Be specific about what doesn’t. Your feedback becomes the brief for a new revision.</p><form id="feedback-form"><label for="feedback">Your direction</label><textarea id="feedback" name="feedback" maxlength="5000" required placeholder="Keep the typography. Make the second example feel more tactile. The mobile layout needs more breathing room…" ${!editable?'disabled':''}></textarea><button class="button secondary" type="submit" ${!editable?'disabled':''}>${r.status==='approved'?'Start a new revision':'Revise with this feedback'} ↗</button><p class="fine">Reuses current captures; older captures are upgraded automatically. Every previous revision and approved release stays available.</p></form></section><div class="history"><h3>The story so far</h3>${d.events.slice(0,12).map(e=>`<div class="history-item">${esc(e.message)}<small>REVISION ${e.revision} · ${date(e.created_at)}</small></div>`).join('')}</div></div><aside><section class="panel pad"><span class="eyebrow">WHEN IT FEELS RIGHT</span><h2 style="margin-top:15px">Keep this language.</h2><p class="muted">Choose the example that best demonstrates how to use the design. It will travel with the skill.</p><label for="final-demo">Final example</label><select id="final-demo" ${!editable?'disabled':''}>${d.summary.demos.map(x=>`<option value="${x.id}" ${selectedDemo===x.id?'selected':''}>${esc(x.title)}</option>`).join('')}</select><button class="button wide" id="approve-button" ${!canApprove?'disabled':''}>${r.status==='approved'?'This version is approved':'Approve as a skill'} <span>↗</span></button><p class="fine">Includes SKILL.md, scoped tokens, the design specification, one working example, and the approval record.</p>${d.archived?'<p class="fine">Restore this reference before approving.</p>':''}</section><div class="side-note" style="margin-top:25px"><h3>Before you decide</h3><ul>${d.summary.limitations.map(x=>`<li>${esc(x)}</li>`).join('')}</ul></div>${editable?'<button class="link-button" id="recapture-button">Capture the live website again ↻</button>':''}</aside></div>`;
|
|
51
|
+
$('#feedback-form').addEventListener('submit',e=>{e.preventDefault();const feedback=new FormData(e.target).get('feedback').trim();if(feedback)newRevision(feedback);});$('#final-demo').addEventListener('change',e=>selectedDemo=Number(e.target.value));$('#approve-button').addEventListener('click',openApproval);$('#recapture-button')?.addEventListener('click',()=>newRevision(''));
|
|
52
|
+
}
|
|
53
|
+
function fitDemo(){const canvas=$('.demo-canvas'),shell=$('.iframe-shell'),frame=$('.iframe-shell iframe');if(!canvas||!frame)return;const width=device==='mobile'?390:1440,height=device==='mobile'?844:1000;const available=canvas.clientWidth-2*parseFloat(getComputedStyle(canvas).paddingLeft);const scale=Math.min(1,available/width);shell.style.width=width*scale+'px';shell.style.height=height*scale+'px';frame.style.width=width+'px';frame.style.height=height+'px';frame.style.transform=`scale(${scale})`;}
|
|
54
|
+
async function loadDetail(id,n=null){const generation=++routeGeneration;const result=await api(`/api/designs/${id}${n?'?revision='+n:''}`);if(generation!==routeGeneration)return;detail=result;revision=result.selectedRevision.number;pollFingerprint=JSON.stringify([result.updated_at,result.selectedRevision.status,result.selectedRevision.stage]);renderDetail();}
|
|
55
|
+
async function navigate(){resizeObserver?.disconnect();const hash=location.hash.slice(1)||'inbox';const parts=hash.split('/');try{if(parts[0]==='design'&&parts[1]){if(detail?.id!==parts[1]){tab='reference';selectedDemo=1;screenshot=0;}await loadDetail(parts[1],parts[2]?Number(parts[2]):null);}else{++routeGeneration;detail=null;search='';await refreshLists();renderList(['library','archive'].includes(parts[0])?parts[0]:'inbox');}}catch(e){main.innerHTML=`<div class="load-error">${esc(e.message)}<p><a href="#inbox">Return to your inbox</a></p></div>`;} }
|
|
56
|
+
async function mutate(fn){try{await fn();}catch(e){toast(e.message);}}
|
|
57
|
+
async function newRevision(feedback){await mutate(async()=>{const result=await api(`/api/designs/${detail.id}/revise`,{method:'POST',body:JSON.stringify({feedback,expectedRevision:detail.current_revision})});toast('New revision added to your desk');tab='reference';location.hash=`design/${detail.id}/${result.revision}`;});}
|
|
58
|
+
function openNew(url=''){if(session&&!session.provider.ready){openSetup();return;}$('#new-form').reset();$('#source-url').value=url;$('#new-error').textContent='';$('#new-dialog').showModal();$('#source-url').focus();}
|
|
59
|
+
function openApproval(){if(!detail||detail.selectedRevision.status!=='review')return;$('#approve-form').reset();$('#approve-error').textContent='';approvalTarget={id:detail.id,revision:detail.selectedRevision.number,selectedDemo};const demo=detail.summary.demos.find(x=>x.id===selectedDemo);$('#approve-description').textContent=`Approve revision ${detail.selectedRevision.number} of ${detail.name}, with “${demo.title}” as its final example.`;$('#approve-dialog').showModal();}
|
|
60
|
+
let installTarget=null,installGeneration=0;
|
|
61
|
+
main.addEventListener('click',e=>{const button=e.target.closest('[data-use-release]');if(button)openInstall(button.dataset.useRelease);});
|
|
62
|
+
async function openInstall(id){
|
|
63
|
+
const generation=++installGeneration;installTarget=null;$('#install-description').textContent='Choose where you want to use this approved design.';$('#install-content').innerHTML='<p class="muted">Checking installed editions…</p>';$('#install-dialog').showModal();
|
|
64
|
+
try{const data=await api(`/api/releases/${id}/install`);if(generation!==installGeneration||!$('#install-dialog').open)return;installTarget={...data,id,displayName:releases.find(r=>r.id===id)?.name||data.name};renderInstall();}
|
|
65
|
+
catch(e){$('#install-content').innerHTML=`<p class="error-text" role="alert">${esc(e.message)}</p><button class="button secondary" id="retry-install">Try again</button>`;$('#retry-install').onclick=()=>{$('#install-dialog').close();openInstall(id);};}
|
|
66
|
+
}
|
|
67
|
+
function skillPrompt(agent){const name=installTarget.name;return agent==='codex'?`Use $${name} to design my next interface. Read the specification, tokens, and approved demo first. Adapt the composition to my task.`:agent==='claude'?`/${name} Design my next interface using this design language. Read the specification, tokens, and approved demo first. Adapt the composition to my task.`:agent==='devin'?`Use @skills:${name} for this interface. Read .agents/skills/${name}/SKILL.md and its referenced specification, tokens, and approved demo. Preserve the aesthetic while adapting the layout and interactions to my task.`:`Read ${name}/SKILL.md from the attached or extracted skill package, including its referenced specification, tokens, and approved demo. Apply this design language to my interface, adapting the layout and interactions to the task.`;}
|
|
68
|
+
async function copySkillPrompt(agent){const prompt=skillPrompt(agent);try{await navigator.clipboard.writeText(prompt);toast('Prompt copied. Paste it into your agent.');}catch{$('#handoff-prompt').hidden=false;$('#handoff-prompt').value=prompt;$('#handoff-prompt').focus();$('#handoff-prompt').select();toast('Select and copy the prompt below.');}}
|
|
69
|
+
function renderInstall(){
|
|
70
|
+
const data=installTarget;$('#install-description').textContent=`${data.displayName||data.name} · approved version ${data.version}`;
|
|
71
|
+
$('#install-content').innerHTML=`<div class="install-local-label eyebrow">INSTALL ON THIS MAC · AVAILABLE ACROSS PROJECTS</div><div class="install-agents">${data.targets.map((t,i)=>`<section class="install-agent"><span class="agent-index">0${i+1}</span><div class="agent-description"><h3>${esc(t.label)}</h3><p>${t.status==='installed'?`Installed · v${t.version}`:t.status==='update'?`v${t.version} installed · v${data.version} ready`:t.status==='available'?'Specification, tokens & final demo':esc(t.message)}</p></div><button class="button small ${t.status==='installed'?'secondary':''}" data-install-agent="${t.id}" ${['conflict','newer'].includes(t.status)?'disabled':''}>${t.status==='installed'?'Copy prompt':t.status==='update'?`Update to v${data.version}`:['conflict','newer'].includes(t.status)?'Kept as is':'Install'} <span>${t.status==='installed'?'↗':'↓'}</span></button></section>`).join('')}</div><p class="fine install-footnote">Installs this approved edition. New approvals never replace it automatically. If a new skill doesn’t appear in your agent, start a new session.</p><div id="install-error" class="error-text" role="alert"></div><details class="install-remote"><summary>Devin Cloud & other agents <span>↗</span></summary><div class="remote-option"><h3>Devin Cloud</h3><p>Download the repository package. Extract it into your project root, then commit and push the included <code>.agents/skills/</code> folder to a repository connected to Devin.</p><div class="handoff-actions"><a class="button secondary small" href="/api/releases/${data.id}/download?target=devin">Get Devin package ↓</a><button class="link-button" data-copy-agent="devin">Copy Devin prompt ↗</button></div><p class="fine">This prepares the files; it does not install them into your cloud account.</p></div><div class="remote-option"><h3>Another agent</h3><p>Take the portable skill folder and ask your agent to read its instructions. Automatic discovery depends on the agent.</p><div class="handoff-actions"><a class="button secondary small" href="/api/releases/${data.id}/download">Download skill ↓</a><button class="link-button" data-copy-agent="other">Copy prompt ↗</button></div></div></details><details class="install-locations"><summary>Installation locations</summary>${data.targets.map(t=>`<p><b>${esc(t.label)}</b><code>${esc(t.path)}</code></p>`).join('')}</details><textarea id="handoff-prompt" aria-label="Prompt to copy" readonly hidden rows="5"></textarea>`;
|
|
72
|
+
$$('[data-install-agent]').forEach(button=>button.addEventListener('click',async()=>{
|
|
73
|
+
const agent=button.dataset.installAgent,target=installTarget.targets.find(t=>t.id===agent);if(target.status==='installed'){await copySkillPrompt(agent);return;}
|
|
74
|
+
button.disabled=true;button.textContent='Installing…';$('#install-error').textContent='';const id=installTarget.id;
|
|
75
|
+
try{await api(`/api/releases/${id}/install`,{method:'POST',body:JSON.stringify({target:agent})});const fresh=await api(`/api/releases/${id}/install`);if(installTarget?.id!==id)return;installTarget={...fresh,id,displayName:installTarget.displayName};renderInstall();$(`[data-install-agent="${agent}"]`)?.focus();toast(`Installed in ${target.label}. Copy the prompt to use it.`);}
|
|
76
|
+
catch(e){if(installTarget?.id!==id)return;renderInstall();$('#install-error').textContent=e.message;}
|
|
77
|
+
}));
|
|
78
|
+
$$('[data-copy-agent]').forEach(button=>button.addEventListener('click',()=>copySkillPrompt(button.dataset.copyAgent)));
|
|
79
|
+
}
|
|
80
|
+
$('#new-button').addEventListener('click',()=>openNew());$$('[data-close]').forEach(b=>b.addEventListener('click',()=>b.closest('dialog').close()));
|
|
81
|
+
$('#new-form').addEventListener('submit',async e=>{e.preventDefault();const button=e.target.querySelector('[type=submit]');button.disabled=true;$('#new-error').textContent='';try{const data=new FormData(e.target),result=await api('/api/designs',{method:'POST',body:JSON.stringify({url:data.get('url').trim(),notes:data.get('notes').trim()})});$('#new-dialog').close();toast('Reference added. We’ll prepare it for your eye.');await refreshLists();location.hash='design/'+result.id;}catch(error){$('#new-error').textContent=error.message;if(error.id){const a=document.createElement('a');a.href='#design/'+error.id;a.textContent=' Open existing reference';a.addEventListener('click',()=>$('#new-dialog').close());$('#new-error').append(a);}}finally{button.disabled=false;}});
|
|
82
|
+
$('#approve-form').addEventListener('submit',async e=>{e.preventDefault();const button=e.target.querySelector('[type=submit]');button.disabled=true;$('#approve-error').textContent='';try{const data=new FormData(e.target);await api(`/api/designs/${approvalTarget.id}/approve`,{method:'POST',body:JSON.stringify({revision:approvalTarget.revision,selectedDemo:approvalTarget.selectedDemo,note:data.get('note').trim(),checks:{reference:data.has('reference'),transfer:data.has('transfer'),limits:data.has('limits')}})});$('#approve-dialog').close();toast('A new skill, kept for your next project.');await refreshLists();await loadDetail(approvalTarget.id,approvalTarget.revision);}catch(error){$('#approve-error').textContent=error.message;}finally{button.disabled=false;}});
|
|
83
|
+
$('#settings-button').addEventListener('click',()=>openSetup());
|
|
84
|
+
window.addEventListener('hashchange',navigate);
|
|
85
|
+
async function poll(){if(polling||document.hidden||document.querySelector('dialog[open]'))return;polling=true;try{await refreshLists();if(detail){renderNav(detail.selectedRevision.status==='approved'?'library':'inbox');if(active.includes(detail.selectedRevision.status)){const result=await api(`/api/designs/${detail.id}?revision=${revision}`);const fingerprint=JSON.stringify([result.updated_at,result.selectedRevision.status,result.selectedRevision.stage]);if(fingerprint!==pollFingerprint){detail=result;pollFingerprint=fingerprint;renderDetail();}}}else if(listFingerprint!==JSON.stringify([designs,releases])&&!document.activeElement?.matches('input,textarea')){renderList(location.hash==='#archive'?'archive':location.hash==='#library'?'library':'inbox');}}catch{$('#connection').textContent='App connection interrupted';$('#connection').classList.add('off');}finally{polling=false;}}
|
|
86
|
+
(async()=>{try{session=await api('/api/session');renderConnection();await refreshLists();await navigate();setInterval(poll,2500);if(!session.provider.ready&&!designs.length)openSetup({welcome:true});}catch(e){main.innerHTML=`<div class="load-error">Antisketch couldn’t connect. Start it with <code>npm start</code>, then refresh.<p>${esc(e.message)}</p></div>`;}})();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><rect width="40" height="40" rx="6" fill="#f7f7f5"/><path d="M6 32 20 6l14 26M13 23h14" fill="none" stroke="#d83d27" stroke-width="3.2"/><path d="m5 12 5-1M30 7l3-3" stroke="#d83d27" stroke-width="1.5"/></svg>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
@font-face{font-family:'Space Grotesk';font-style:normal;font-weight:300 700;font-display:swap;src:url('/fonts/SpaceGrotesk.ttf') format('truetype')}
|
|
2
|
+
@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url('/fonts/InterVariable.woff2') format('woff2')}
|
|
3
|
+
:root{--paper:#f7f7f5;--ink:#1c1c1a;--muted:#70706a;--rule:#d5d5cf;--red:#d83d27;--display:'Space Grotesk',sans-serif;--sans:Inter,sans-serif;--mono:ui-monospace,SFMono-Regular,Menlo,monospace}
|
|
4
|
+
*{box-sizing:border-box}html{scroll-behavior:smooth;scroll-padding-top:28px}body{margin:0;background:var(--paper);color:var(--ink);font:14px/1.6 var(--sans);-webkit-font-smoothing:antialiased}h1,h2,h3,p,figure{margin:0}a{color:inherit;text-underline-offset:5px}button,input,textarea{font:inherit;color:inherit}button{cursor:pointer}button,a,input,textarea{-webkit-tap-highlight-color:transparent}button:focus-visible,a:focus-visible,input:focus-visible{outline:2px solid var(--ink);outline-offset:5px}img{display:block;max-width:100%;height:auto}button{border-radius:0}::selection{background:#f1c6b9}.wrap{width:min(1280px,calc(100% - 112px));margin-inline:auto}.mono,.eyebrow{font:10px/1.6 var(--mono);letter-spacing:.035em}.eyebrow{letter-spacing:.075em;color:var(--muted)}.red{color:var(--red)}.skip{position:fixed;top:-100px;left:20px;padding:12px;background:var(--ink);color:var(--paper);z-index:99}.skip:focus{top:10px}.masthead{display:flex;align-items:center;justify-content:space-between;padding:25px 4%;border-bottom:1px solid var(--rule);gap:30px}.brand{display:flex;align-items:center;gap:11px;font:600 27px/1 var(--display);letter-spacing:-1px;text-decoration:none}.brand img{width:37px;height:37px}.brand small{display:block;font:8px var(--mono);letter-spacing:.07em;text-transform:uppercase;margin-top:10px;color:var(--muted)}.masthead nav{display:flex;gap:28px;font:11px var(--mono)}.masthead nav a{text-decoration:none}.masthead nav a:hover{color:var(--red)}.back-link{font:11px var(--mono);text-decoration:none}.back-link span{margin-left:16px}.hero{display:grid;grid-template-columns:1.05fr 1fr;gap:36px;padding-top:86px;padding-bottom:72px;align-items:center;min-height:650px}.hero-copy{position:relative;z-index:2}.hero .eyebrow{display:flex;align-items:center;gap:10px}.hero .eyebrow i{width:5px;height:5px;border-radius:50%;background:var(--red)}.hero .eyebrow span{margin-left:8px;color:#999}.hero h1{font:500 clamp(70px,7.4vw,108px)/.99 var(--display);letter-spacing:-.065em;margin:30px 0 34px}.eye-word{position:relative;white-space:nowrap}.eye-word svg{position:absolute;bottom:-9px;left:0;width:100%;height:18px;fill:none;stroke:var(--red);stroke-width:1.5}.hero-intro{max-width:385px;font-size:15px;line-height:1.8;color:#575751}.text-link{display:inline-flex;align-items:center;gap:40px;font:12px var(--mono);margin-top:30px;text-decoration:none;border-bottom:1px solid var(--ink);padding-bottom:8px}.text-link:hover{color:var(--red);border-color:var(--red)}.study-stack{position:relative;height:450px;text-decoration:none;display:block}.stack-label{position:absolute;top:-10px;left:22px;color:var(--muted);font-size:8px}.source-slip{position:absolute;top:21px;left:10px;width:75%;background:#fff;border:1px solid var(--rule);padding:13px;transform:rotate(-5deg);box-shadow:3px 8px 15px #1c1c1a08;transition:transform .3s}.source-slip figcaption{display:flex;justify-content:space-between;font-size:7px;padding:0 0 11px}.source-window{height:175px;overflow:hidden;border:1px solid #ddd}.source-window img{width:100%}.slip-footer{display:flex;justify-content:space-between;font-size:7px;padding-top:10px}.type-slip{position:absolute;top:116px;right:0;background:#e9e6de;width:210px;padding:17px 20px;transform:rotate(5deg);box-shadow:3px 5px 10px #00000008;border:1px solid #d1cdc3;z-index:2}.type-slip>.mono{font-size:7px}.type-slip b{display:block;font:500 54px/1.3 var(--display);letter-spacing:-3px}.type-slip b span{font-size:22px;color:var(--red);vertical-align:middle}.type-slip small{font-size:8px}.mini-palette{display:flex;gap:4px;margin-top:12px}.mini-palette i{width:24px;height:7px;background:#f6f5f1}.mini-palette i:nth-child(2){background:#14161c}.mini-palette i:nth-child(3){background:#8a2d3b}.mini-palette i:nth-child(4){background:#e8ec21}.result-slip{position:absolute;top:245px;left:19%;width:70%;padding:12px;background:var(--paper);border:1px solid #aaa79f;transform:rotate(-1deg);box-shadow:4px 8px 12px #00000008;z-index:3}.result-slip figcaption{font-size:7px;padding-bottom:10px}.result-slip>img{width:100%;height:139px;object-fit:cover;object-position:top;border:1px solid #ddd}.margin-note{position:absolute;left:0;bottom:0;font:11px/1.45 var(--mono);color:var(--red);transform:rotate(-5deg)}.margin-note span{display:block;text-align:right;font-size:28px}.study-stack:hover .source-slip{transform:rotate(-3deg) translateY(-4px)}.chapter-strip{display:flex;justify-content:space-between;border-top:1px solid var(--ink);border-bottom:1px solid var(--rule);padding-block:15px;color:var(--muted)}.chapter-strip .mono{font-size:9px}.study,.toolkit,.in-use{padding-top:75px;padding-bottom:70px}.section-heading{display:flex;justify-content:space-between;align-items:flex-end;gap:48px;margin-bottom:36px}.section-heading h2{font:500 46px/1.08 var(--display);letter-spacing:-2px;margin-top:20px}.section-heading>p{max-width:330px;font-size:13px;line-height:1.85;color:var(--muted);padding-bottom:3px}.study-controls{display:flex;align-items:center;justify-content:space-between;border-top:1px solid var(--rule);padding:17px 0 22px;gap:20px}.observation-buttons{display:flex;gap:8px;flex-wrap:wrap}.observation-buttons button{padding:9px 13px;border:1px solid transparent;background:transparent;font:10px var(--mono);min-height:38px}.observation-buttons button[aria-pressed=true]{border-color:var(--ink);background:var(--ink);color:var(--paper)}.observation-buttons button:hover{border-color:var(--ink)}.annotation-toggle{border:0;background:none;font:10px var(--mono);white-space:nowrap;padding:10px 0}.annotation-toggle span{display:inline-block;margin-left:8px;padding:2px 6px;border:1px solid var(--red);color:var(--red);min-width:30px}.comparison{display:grid;grid-template-columns:1fr 1fr;gap:26px}.comparison article{min-width:0}.panel-top{display:flex;justify-content:space-between;padding:11px 0;font-size:9px;border-top:1px solid var(--ink)}.panel-top a{color:var(--muted);text-decoration:none}.screen{position:relative;border:1px solid var(--rule);background:#eeede8;overflow:hidden;aspect-ratio:1.38}.screen>img{width:100%;height:100%;object-fit:cover;object-position:top}.screen.result-screen>img{object-fit:contain}.observation-mark{position:absolute;top:12%;left:10%;border:1px solid var(--red);border-left:3px solid var(--red);width:80%;height:11%;font:9px var(--mono);color:var(--red);padding:3px 5px;background:#d83d2705;transition:top .25s,left .25s,width .25s,height .25s;pointer-events:none}.observation-mark:before{content:'';position:absolute;inset:-4px;border-left:1px solid var(--red);border-right:1px solid var(--red)}.comparison.annotations-hidden .observation-mark{visibility:hidden}.panel-caption{display:flex;justify-content:space-between;align-items:baseline;gap:15px;padding:13px 0}.panel-caption h3{font:500 18px var(--display);letter-spacing:-.5px}.panel-caption .mono{font-size:8px;color:var(--muted)}.study-bottom{display:grid;grid-template-columns:1.1fr 1fr;gap:70px;border-top:1px solid var(--rule);padding-top:25px;margin-top:12px}.study-note{display:flex;gap:17px}.note-number{font:12px var(--mono);color:var(--red);padding-top:4px}.study-note h3{font:500 20px var(--display);letter-spacing:-.5px;margin-bottom:13px}.study-note p{font-size:12px;color:var(--muted);line-height:1.8;margin-top:8px}.study-note b{font-weight:500;color:var(--ink)}.application-picker .eyebrow{font-size:8px}.application-picker>div{margin:12px 0}.application-picker button{display:flex;justify-content:space-between;align-items:center;gap:15px;width:100%;text-align:left;border:0;border-bottom:1px solid var(--rule);background:transparent;font-size:12px;padding:12px 10px}.application-picker button>span:first-child{color:var(--muted)}.application-picker button>span:last-child{margin-left:auto}.application-picker button[aria-pressed=true]{background:#ecebe5}.application-picker button[aria-pressed=true]>span:last-child{color:var(--red)}.application-picker p{font-size:11px;color:var(--muted);line-height:1.75;max-width:390px}.source-note{font-size:8px;color:var(--muted);margin-top:32px;max-width:850px}.belief{background:var(--ink);color:var(--paper);padding:60px 0 68px;position:relative;overflow:hidden}.belief>.wrap{position:relative}.belief .eyebrow{color:#b1b1a8}.belief h2{font:500 clamp(58px,7.5vw,108px)/1 var(--display);letter-spacing:-.055em;margin-top:40px}.belief h2>span{color:var(--red)}.belief-bottom{display:flex;justify-content:space-between;gap:55px;margin-top:32px}.belief-bottom>p{font:400 35px/1.15 var(--display);letter-spacing:-1.2px;color:#b4b4aa}.belief-bottom>div{max-width:330px}.belief-bottom .mono{font-size:8px;color:#b4b4aa}.belief-bottom>div p{font-size:12px;line-height:1.9;margin-top:12px;color:#d1d1c8}.belief-cross{position:absolute;right:6%;top:35px;color:var(--red);font:180px/1 var(--display)}.tool-row{display:grid;grid-template-columns:250px minmax(0,1fr);gap:40px;padding:35px 0;border-top:1px solid var(--rule)}.tool-label>.mono{font-size:9px;color:var(--muted)}.tool-label>p{font:400 21px/1.3 var(--display);letter-spacing:-.5px;margin-top:18px}.tool-label>small{display:block;font-size:11px;color:var(--muted);margin-top:18px;max-width:180px}.signatures{display:grid;grid-template-columns:1fr 1fr;gap:16px}.signature{min-height:155px;display:flex;justify-content:center;align-items:center;gap:10px;border:1px solid var(--rule);position:relative;padding:20px 10px 40px}.signature>img{width:34px;height:34px}.signature>b{font:600 clamp(22px,2.5vw,36px) var(--display);letter-spacing:-1.5px}.signature>.mono{position:absolute;bottom:13px;left:15px;font-size:7px;color:var(--muted)}.signature.dark{background:var(--ink);color:var(--paper)}.signature.dark>.mono{color:#b4b4aa}.asset-links{display:flex;gap:23px;flex-wrap:wrap;margin-top:20px;font:10px var(--mono)}.palette{display:grid;grid-template-columns:repeat(4,1fr);gap:16px}.palette button{padding:0;border:0;background:none;text-align:left}.palette i{display:block;background:var(--swatch);height:100px;border:1px solid #0001;transition:transform .2s}.palette button:hover i{transform:translateY(-4px)}.palette button>span{display:flex;justify-content:space-between;font-size:12px;margin-top:13px}.palette small{color:var(--muted)}.palette code{font:9px var(--mono);color:var(--muted)}.type-controls{display:flex;justify-content:space-between;gap:20px;align-items:center}.type-controls .mono{font-size:8px;color:var(--muted)}.type-controls label:last-child{display:flex;align-items:center;gap:6px;white-space:nowrap}.type-controls input{width:90px;accent-color:var(--ink);min-height:30px}#type-input{display:block;width:100%;background:none;border:0;border-bottom:1px solid var(--rule);padding:23px 0 12px;font:500 var(--specimen-size,56px)/1.15 var(--display);letter-spacing:-.045em;resize:vertical;min-height:135px;max-height:400px;border-radius:0}#type-input:focus{outline:none;border-bottom:2px solid var(--ink)}.type-pair{display:grid;grid-template-columns:1fr 1fr;gap:40px;margin-top:25px}.type-pair>div>.mono{font-size:8px;color:var(--muted)}.type-pair p{font-size:12px;line-height:1.8;margin-top:12px;max-width:330px}.type-pair p.mono{font-size:10px;color:var(--ink)}.annotation-specimens{display:grid;grid-template-columns:repeat(3,1fr);gap:25px}.annotation-specimens b{display:block;font:400 38px/1.6 var(--display);letter-spacing:-1px;width:fit-content;margin-bottom:20px}.bracket-symbol{color:var(--red)}.rule-symbol span{color:var(--red)}.underline-symbol{position:relative}.underline-symbol:after{content:'';position:absolute;bottom:5px;left:-2px;right:-2px;border-bottom:2px solid var(--red);transform:rotate(-3deg)}.annotation-specimens .mono{display:block;font-size:9px}.annotation-specimens p{font-size:11px;color:var(--muted);margin-top:8px;max-width:150px}.in-use{border-top:1px solid var(--ink);padding-top:55px}.artifacts{display:grid;grid-template-columns:1fr 1fr;gap:32px}.study-cover{background:#e9e6de;padding:28px 30px 22px;border:1px solid #d1cdc3;display:flex;flex-direction:column}.study-cover>.mono{display:flex;justify-content:space-between;font-size:8px}.study-cover h3{font:500 70px/.96 var(--display);letter-spacing:-4px;margin:40px 0 26px}.study-cover h3 span{color:var(--red)}.cover-crop{height:125px;overflow:hidden;border-top:1px solid #b4b0a7;border-bottom:1px solid #b4b0a7}.cover-crop img{width:100%;transform:translateY(-20%)}.study-cover footer{display:flex;justify-content:space-between;align-items:center;margin-top:25px}.study-cover footer .mono{font-size:8px}.study-cover footer img{width:36px;height:36px}.artifact-side{display:grid;gap:22px}.skill-card{border:1px solid var(--rule);padding:25px 26px}.skill-card>.mono,.readme-card>.mono{display:flex;align-items:center;gap:7px;font-size:8px}.skill-card>.mono>span:last-child,.readme-card>.mono>span{margin-left:auto}.status-dot{width:5px;height:5px;background:var(--red);border-radius:50%;display:inline-block}.skill-card h3{font:500 30px var(--display);letter-spacing:-1px;margin-top:21px}.skill-card p{font-size:12px;line-height:1.8;color:var(--muted);margin:10px 0 23px}.skill-card>a{display:flex;justify-content:space-between;padding-top:13px;border-top:1px solid var(--rule);font:10px var(--mono);text-decoration:none}.readme-card{background:var(--ink);color:#dddcd5;padding:24px 26px}.readme-card>.mono{color:#b4b4aa}.readme-card pre{font:10px/1.8 var(--mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:20px 0}.readme-card a{font:9px var(--mono);color:#c0c0b6}.artifact-caption{font-size:10px;color:var(--muted);margin-top:16px}.page-footer{border-top:1px solid var(--ink);padding-top:35px;padding-bottom:30px;display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:35px}.page-footer>div>p{font-size:11px;color:var(--muted);margin-top:12px}.closing-link{font:400 31px/1.2 var(--display);letter-spacing:-1px;text-decoration:none}.closing-link>span{color:var(--red)}.footer-colophon{display:flex;justify-content:space-between;flex-basis:100%;font-size:8px;color:var(--muted);margin-top:25px;border-top:1px solid var(--rule);padding-top:18px}.toast{position:fixed;left:50%;bottom:25px;transform:translate(-50%,20px);background:var(--ink);color:var(--paper);border:1px solid #555;padding:12px 20px;font:11px var(--mono);opacity:0;pointer-events:none;transition:opacity .2s,transform .2s;z-index:20;max-width:calc(100% - 30px)}.toast.visible{opacity:1;transform:translate(-50%,0)}
|
|
5
|
+
@media(min-width:1600px){.hero{min-height:700px}.hero h1{font-size:118px}.study-stack{height:470px}.source-window{height:195px}.result-slip{top:260px}.result-slip>img{height:155px}}
|
|
6
|
+
@media(max-width:1100px){.wrap{width:calc(100% - 72px)}.masthead nav{gap:18px}.hero{gap:30px;padding-top:65px;min-height:600px}.hero h1{font-size:78px}.hero-intro{font-size:14px}.study-stack{height:410px}.source-window{height:145px}.source-slip{left:0;width:83%}.type-slip{width:180px;top:100px;padding:14px}.type-slip b{font-size:46px}.result-slip{top:235px;width:81%;left:13%}.result-slip>img{height:122px}.margin-note{font-size:9px;bottom:-6px}.section-heading h2{font-size:39px}.tool-row{grid-template-columns:190px minmax(0,1fr);gap:28px}.study-bottom{gap:35px}.panel-caption{align-items:flex-start;flex-direction:column;gap:5px}.study-cover h3{font-size:60px}.type-controls{flex-wrap:wrap;gap:5px}.belief-cross{right:0;font-size:140px}}
|
|
7
|
+
@media(max-width:800px){.masthead nav{display:none}.hero{grid-template-columns:1fr 1fr;gap:12px;padding-top:55px;padding-bottom:50px;min-height:560px}.hero h1{font-size:65px}.hero .eyebrow{font-size:8px}.hero .eyebrow span{display:none}.study-stack{height:365px}.stack-label{font-size:6px}.source-window{height:130px}.type-slip{width:145px;top:105px;padding:12px}.type-slip>.mono{font-size:6px}.type-slip b{font-size:36px}.type-slip small{font-size:7px}.result-slip{top:225px;left:10%;width:90%}.result-slip>img{height:90px}.result-slip figcaption,.slip-footer{font-size:6px}.margin-note{display:none}.section-heading{gap:30px}.section-heading>p{max-width:240px}.section-heading h2{font-size:34px}.study-controls{align-items:flex-start;gap:10px}.observation-buttons{gap:3px}.observation-buttons button{padding:9px;font-size:9px}.comparison{gap:18px}.study-bottom{gap:25px}.tool-row{grid-template-columns:1fr;gap:24px}.tool-label{display:flex;justify-content:space-between;gap:25px;align-items:baseline}.tool-label p{margin:0;font-size:19px}.tool-label small{margin:0}.belief-cross{font-size:110px;top:55px}.belief-bottom>div{max-width:260px}.artifacts{gap:20px}.study-cover{padding:20px}.study-cover h3{font-size:52px}.skill-card,.readme-card{padding:20px}.readme-card pre{font-size:9px}}
|
|
8
|
+
@media(max-width:600px){.wrap{width:calc(100% - 40px)}.masthead{padding:22px 20px;gap:15px}.brand{font-size:23px;gap:8px}.brand img{width:29px;height:29px}.brand small{font-size:6px;margin-top:8px}.back-link{font-size:8px;max-width:80px;line-height:1.6}.back-link span{margin-left:4px}.hero{display:flex;flex-direction:column;align-items:stretch;padding-top:43px;padding-bottom:25px;gap:48px}.hero h1{font-size:clamp(64px,13vw,78px);margin:28px 0}.hero-intro{font-size:14px;max-width:360px}.text-link{margin-top:23px;font-size:10px}.study-stack{height:390px;max-width:400px;width:100%;align-self:center}.stack-label{font-size:7px;left:8px;top:-8px}.source-slip{width:80%;top:20px;left:8px}.source-window{height:150px}.source-slip figcaption{font-size:6px}.type-slip{top:115px;width:155px}.type-slip b{font-size:42px}.result-slip{width:77%;top:232px;left:18%}.result-slip>img{height:110px}.result-slip figcaption,.slip-footer{font-size:6px}.chapter-strip{gap:18px;padding-block:12px}.chapter-strip>.mono{font-size:7px}.chapter-strip>.mono:last-child{display:none}.study,.toolkit,.in-use{padding-top:43px;padding-bottom:42px}.section-heading{display:block;margin-bottom:25px}.section-heading h2{font-size:35px;letter-spacing:-1.5px;margin-top:15px}.section-heading>p{max-width:340px;font-size:12px;margin-top:20px}.study-controls{flex-wrap:wrap;gap:10px;padding:12px 0 16px}.observation-buttons{width:100%;display:grid;grid-template-columns:1fr 1fr;gap:6px}.observation-buttons button{font-size:10px;min-height:40px;text-align:left;border-color:var(--rule)}.annotation-toggle{font-size:9px;padding:5px 0}.comparison{grid-template-columns:1fr;gap:22px}.panel-top{font-size:8px}.screen{aspect-ratio:1.5}.panel-caption{flex-direction:row;align-items:center;gap:10px}.panel-caption h3{font-size:17px}.panel-caption .mono{font-size:7px;max-width:155px;text-align:right}.study-bottom{grid-template-columns:1fr;gap:26px;padding-top:20px;margin-top:18px}.study-note{gap:14px}.study-note h3{font-size:19px}.study-note p{font-size:12px}.application-picker{border-top:1px solid var(--rule);padding-top:20px}.application-picker button{min-height:48px}.source-note{font-size:8px;margin-top:24px;line-height:1.9}.belief{padding:38px 0}.belief h2{font-size:clamp(49px,10vw,65px);margin-top:32px}.belief-cross{font-size:53px;top:-9px;right:3px}.belief-bottom{display:block;margin-top:28px}.belief-bottom>p{font-size:28px}.belief-bottom>div{max-width:310px;margin-top:28px}.tool-row{padding:27px 0;gap:23px}.tool-label{display:block}.tool-label p{font-size:21px;margin-top:12px}.tool-label small{margin-top:12px;max-width:none}.signature{min-height:130px;padding-inline:6px;gap:6px}.signature>img{width:24px;height:24px}.signature>b{font-size:23px;letter-spacing:-1.2px}.signatures{gap:10px}.signature>.mono{font-size:6px;left:10px}.asset-links{gap:15px 20px;font-size:9px}.palette{gap:10px}.palette i{height:70px}.palette button>span{font-size:10px}.palette code{font-size:8px}.type-controls{gap:10px}.type-controls .mono{font-size:7px}#type-input{font-size:min(var(--specimen-size,56px),14vw);min-height:130px}.type-pair{gap:24px;grid-template-columns:1fr}.annotation-specimens{gap:15px}.annotation-specimens b{font-size:29px}.annotation-specimens .mono{font-size:8px}.annotation-specimens p{font-size:10px}.artifacts{grid-template-columns:1fr;gap:20px}.study-cover{padding:25px}.study-cover h3{font-size:66px}.cover-crop{height:120px}.readme-card pre{font-size:10px}.page-footer{padding-top:28px;gap:32px}.closing-link{font-size:27px}.footer-colophon{font-size:7px;gap:25px;line-height:1.8;margin-top:0}.footer-colophon>span:last-child{text-align:right}.eyebrow{font-size:9px}}
|
|
9
|
+
@media(max-width:360px){.signature>b{font-size:19px}.signature>img{width:20px}.signature{gap:4px}.hero h1{font-size:61px}.type-slip{width:140px}.result-slip{width:85%;left:11%}.palette small{display:none}.type-controls input{width:70px}}
|
|
10
|
+
@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}*,*:before,*:after{transition:none!important;animation:none!important}}
|
|
11
|
+
@media print{.masthead nav,.study-controls,.application-picker,.asset-links,.back-link,.toast{display:none}.wrap{width:100%}.hero{min-height:0}.study-stack{transform:scale(.8)}.belief{-webkit-print-color-adjust:exact;print-color-adjust:exact}.screen,.tool-row,.artifacts{break-inside:avoid}.page-footer{margin-top:20px}}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Antisketch",
|
|
3
|
+
"wordmark": "antisketch",
|
|
4
|
+
"descriptor": "A workshop for your eye.",
|
|
5
|
+
"purpose": "Turn references you love into design skills your agents can use.",
|
|
6
|
+
"tagline": "Give your agent an eye.",
|
|
7
|
+
"supportingLine": "A reference is a beginning. Your decisions make it yours.",
|
|
8
|
+
"workflow": [
|
|
9
|
+
"Observe",
|
|
10
|
+
"Experiment",
|
|
11
|
+
"Keep",
|
|
12
|
+
"Use"
|
|
13
|
+
],
|
|
14
|
+
"palette": {
|
|
15
|
+
"paper": "#f7f7f5",
|
|
16
|
+
"ink": "#1c1c1a",
|
|
17
|
+
"vermilion": "#d83d27",
|
|
18
|
+
"rule": "#d5d5cf",
|
|
19
|
+
"annotation": "#70706a"
|
|
20
|
+
},
|
|
21
|
+
"typography": {
|
|
22
|
+
"display": "Space Grotesk",
|
|
23
|
+
"body": "Inter",
|
|
24
|
+
"annotation": "ui-monospace, SFMono-Regular, Menlo, monospace"
|
|
25
|
+
},
|
|
26
|
+
"mark": "An open angular A, accompanied by two small exploratory strokes.",
|
|
27
|
+
"voice": [
|
|
28
|
+
"Curious and precise",
|
|
29
|
+
"Warm without being cute",
|
|
30
|
+
"Direct about what was observed and what was inferred",
|
|
31
|
+
"Respectful of the human's judgment"
|
|
32
|
+
],
|
|
33
|
+
"principles": [
|
|
34
|
+
"References provide the imagery",
|
|
35
|
+
"Give the work space",
|
|
36
|
+
"Use vermilion for a meaningful accent",
|
|
37
|
+
"Keep navigation plain and feedback specific",
|
|
38
|
+
"Preserve aesthetic relationships while adapting the task and composition"
|
|
39
|
+
],
|
|
40
|
+
"assets": {
|
|
41
|
+
"mark": "mark.svg",
|
|
42
|
+
"favicon": "favicon.svg"
|
|
43
|
+
},
|
|
44
|
+
"graphicLanguage": [
|
|
45
|
+
"Brackets identify an observation",
|
|
46
|
+
"Rules connect evidence and interpretation",
|
|
47
|
+
"Vermilion underlines mark a decision",
|
|
48
|
+
"Numbered captions preserve source and context"
|
|
49
|
+
]
|
|
50
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" fill="none"><path d="M6 32 20 6l14 26M13 23h14" stroke="#d83d27" stroke-width="3.2" stroke-linecap="square" stroke-linejoin="miter"/><path d="m5 12 5-1M30 7l3-3" stroke="#d83d27" stroke-width="1.5" stroke-linecap="square"/></svg>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
|
|
2
|
+
|
|
3
|
+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
4
|
+
This license is copied below, and is also available with a FAQ at:
|
|
5
|
+
http://scripts.sil.org/OFL
|
|
6
|
+
|
|
7
|
+
-----------------------------------------------------------
|
|
8
|
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
9
|
+
-----------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
PREAMBLE
|
|
12
|
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
13
|
+
development of collaborative font projects, to support the font creation
|
|
14
|
+
efforts of academic and linguistic communities, and to provide a free and
|
|
15
|
+
open framework in which fonts may be shared and improved in partnership
|
|
16
|
+
with others.
|
|
17
|
+
|
|
18
|
+
The OFL allows the licensed fonts to be used, studied, modified and
|
|
19
|
+
redistributed freely as long as they are not sold by themselves. The
|
|
20
|
+
fonts, including any derivative works, can be bundled, embedded,
|
|
21
|
+
redistributed and/or sold with any software provided that any reserved
|
|
22
|
+
names are not used by derivative works. The fonts and derivatives,
|
|
23
|
+
however, cannot be released under any other type of license. The
|
|
24
|
+
requirement for fonts to remain under this license does not apply
|
|
25
|
+
to any document created using the fonts or their derivatives.
|
|
26
|
+
|
|
27
|
+
DEFINITIONS
|
|
28
|
+
"Font Software" refers to the set of files released by the Copyright
|
|
29
|
+
Holder(s) under this license and clearly marked as such. This may
|
|
30
|
+
include source files, build scripts and documentation.
|
|
31
|
+
|
|
32
|
+
"Reserved Font Name" refers to any names specified as such after the
|
|
33
|
+
copyright statement(s).
|
|
34
|
+
|
|
35
|
+
"Original Version" refers to the collection of Font Software components as
|
|
36
|
+
distributed by the Copyright Holder(s).
|
|
37
|
+
|
|
38
|
+
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
39
|
+
or substituting -- in part or in whole -- any of the components of the
|
|
40
|
+
Original Version, by changing formats or by porting the Font Software to a
|
|
41
|
+
new environment.
|
|
42
|
+
|
|
43
|
+
"Author" refers to any designer, engineer, programmer, technical
|
|
44
|
+
writer or other person who contributed to the Font Software.
|
|
45
|
+
|
|
46
|
+
PERMISSION AND CONDITIONS
|
|
47
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
48
|
+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
49
|
+
redistribute, and sell modified and unmodified copies of the Font
|
|
50
|
+
Software, subject to the following conditions:
|
|
51
|
+
|
|
52
|
+
1) Neither the Font Software nor any of its individual components,
|
|
53
|
+
in Original or Modified Versions, may be sold by itself.
|
|
54
|
+
|
|
55
|
+
2) Original or Modified Versions of the Font Software may be bundled,
|
|
56
|
+
redistributed and/or sold with any software, provided that each copy
|
|
57
|
+
contains the above copyright notice and this license. These can be
|
|
58
|
+
included either as stand-alone text files, human-readable headers or
|
|
59
|
+
in the appropriate machine-readable metadata fields within text or
|
|
60
|
+
binary files as long as those fields can be easily viewed by the user.
|
|
61
|
+
|
|
62
|
+
3) No Modified Version of the Font Software may use the Reserved Font
|
|
63
|
+
Name(s) unless explicit written permission is granted by the corresponding
|
|
64
|
+
Copyright Holder. This restriction only applies to the primary font name as
|
|
65
|
+
presented to the users.
|
|
66
|
+
|
|
67
|
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
68
|
+
Software shall not be used to promote, endorse or advertise any
|
|
69
|
+
Modified Version, except to acknowledge the contribution(s) of the
|
|
70
|
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
71
|
+
permission.
|
|
72
|
+
|
|
73
|
+
5) The Font Software, modified or unmodified, in part or in whole,
|
|
74
|
+
must be distributed entirely under this license, and must not be
|
|
75
|
+
distributed under any other license. The requirement for fonts to
|
|
76
|
+
remain under this license does not apply to any document created
|
|
77
|
+
using the Font Software.
|
|
78
|
+
|
|
79
|
+
TERMINATION
|
|
80
|
+
This license becomes null and void if any of the above conditions are
|
|
81
|
+
not met.
|
|
82
|
+
|
|
83
|
+
DISCLAIMER
|
|
84
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
85
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
86
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
87
|
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
88
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
89
|
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
90
|
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
91
|
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
92
|
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Copyright 2017 The Playfair Display Project Authors (https://github.com/clauseggers/Playfair-Display), with Reserved Font Name "Playfair Display"
|
|
2
|
+
|
|
3
|
+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
4
|
+
This license is copied below, and is also available with a FAQ at:
|
|
5
|
+
http://scripts.sil.org/OFL
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
-----------------------------------------------------------
|
|
9
|
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
10
|
+
-----------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
PREAMBLE
|
|
13
|
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
14
|
+
development of collaborative font projects, to support the font creation
|
|
15
|
+
efforts of academic and linguistic communities, and to provide a free and
|
|
16
|
+
open framework in which fonts may be shared and improved in partnership
|
|
17
|
+
with others.
|
|
18
|
+
|
|
19
|
+
The OFL allows the licensed fonts to be used, studied, modified and
|
|
20
|
+
redistributed freely as long as they are not sold by themselves. The
|
|
21
|
+
fonts, including any derivative works, can be bundled, embedded,
|
|
22
|
+
redistributed and/or sold with any software provided that any reserved
|
|
23
|
+
names are not used by derivative works. The fonts and derivatives,
|
|
24
|
+
however, cannot be released under any other type of license. The
|
|
25
|
+
requirement for fonts to remain under this license does not apply
|
|
26
|
+
to any document created using the fonts or their derivatives.
|
|
27
|
+
|
|
28
|
+
DEFINITIONS
|
|
29
|
+
"Font Software" refers to the set of files released by the Copyright
|
|
30
|
+
Holder(s) under this license and clearly marked as such. This may
|
|
31
|
+
include source files, build scripts and documentation.
|
|
32
|
+
|
|
33
|
+
"Reserved Font Name" refers to any names specified as such after the
|
|
34
|
+
copyright statement(s).
|
|
35
|
+
|
|
36
|
+
"Original Version" refers to the collection of Font Software components as
|
|
37
|
+
distributed by the Copyright Holder(s).
|
|
38
|
+
|
|
39
|
+
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
40
|
+
or substituting -- in part or in whole -- any of the components of the
|
|
41
|
+
Original Version, by changing formats or by porting the Font Software to a
|
|
42
|
+
new environment.
|
|
43
|
+
|
|
44
|
+
"Author" refers to any designer, engineer, programmer, technical
|
|
45
|
+
writer or other person who contributed to the Font Software.
|
|
46
|
+
|
|
47
|
+
PERMISSION & CONDITIONS
|
|
48
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
49
|
+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
50
|
+
redistribute, and sell modified and unmodified copies of the Font
|
|
51
|
+
Software, subject to the following conditions:
|
|
52
|
+
|
|
53
|
+
1) Neither the Font Software nor any of its individual components,
|
|
54
|
+
in Original or Modified Versions, may be sold by itself.
|
|
55
|
+
|
|
56
|
+
2) Original or Modified Versions of the Font Software may be bundled,
|
|
57
|
+
redistributed and/or sold with any software, provided that each copy
|
|
58
|
+
contains the above copyright notice and this license. These can be
|
|
59
|
+
included either as stand-alone text files, human-readable headers or
|
|
60
|
+
in the appropriate machine-readable metadata fields within text or
|
|
61
|
+
binary files as long as those fields can be easily viewed by the user.
|
|
62
|
+
|
|
63
|
+
3) No Modified Version of the Font Software may use the Reserved Font
|
|
64
|
+
Name(s) unless explicit written permission is granted by the corresponding
|
|
65
|
+
Copyright Holder. This restriction only applies to the primary font name as
|
|
66
|
+
presented to the users.
|
|
67
|
+
|
|
68
|
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
69
|
+
Software shall not be used to promote, endorse or advertise any
|
|
70
|
+
Modified Version, except to acknowledge the contribution(s) of the
|
|
71
|
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
72
|
+
permission.
|
|
73
|
+
|
|
74
|
+
5) The Font Software, modified or unmodified, in part or in whole,
|
|
75
|
+
must be distributed entirely under this license, and must not be
|
|
76
|
+
distributed under any other license. The requirement for fonts to
|
|
77
|
+
remain under this license does not apply to any document created
|
|
78
|
+
using the Font Software.
|
|
79
|
+
|
|
80
|
+
TERMINATION
|
|
81
|
+
This license becomes null and void if any of the above conditions are
|
|
82
|
+
not met.
|
|
83
|
+
|
|
84
|
+
DISCLAIMER
|
|
85
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
86
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
87
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
88
|
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
89
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
90
|
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
91
|
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
92
|
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
93
|
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
Binary file
|