Explore intent-first Phast composer
This commit is contained in:
1 parent
84c1925743
commit
d48719d0b7
1 file changed
+148
-19
+148
-19
@@ -3,29 +3,158 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>My Phast</title>
|
||||
<meta name="theme-color" content="#f5f3ee">
|
||||
<meta name="description" content="Compose something useful, then make it real.">
|
||||
<title>my phast</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Manrope:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root{--paper:#f5f3ee;--ink:#121211;--muted:#77746c;--line:rgba(18,18,17,.15);--signal:#ff4d00}
|
||||
*{box-sizing:border-box}body{margin:0;background:var(--paper);color:var(--ink);font-family:ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
|
||||
main{width:min(920px,calc(100% - 40px));margin:0 auto;padding:42px 0 70px}
|
||||
.top{display:flex;justify-content:space-between;align-items:center;gap:20px}.brand{font-size:30px;font-weight:750;letter-spacing:-.06em}.dot{color:var(--signal)}
|
||||
.badge{font:12px ui-monospace,monospace;border:1px solid var(--line);padding:7px 10px;border-radius:999px;color:var(--muted)}
|
||||
h1{font-size:clamp(48px,8vw,92px);letter-spacing:-.07em;line-height:.9;margin:80px 0 18px}.lede{font-size:18px;color:var(--muted);max-width:640px;line-height:1.55}
|
||||
.grid{display:grid;grid-template-columns:1fr 1fr;gap:18px;margin-top:48px}.card{border:1px solid var(--line);padding:24px;min-height:180px}.card h2{margin:0 0 12px;font-size:18px}.card p{color:var(--muted);line-height:1.5}
|
||||
code{font-family:ui-monospace,monospace;font-size:13px}.footer{margin-top:42px;padding-top:18px;border-top:1px solid var(--line);color:var(--muted);font:12px ui-monospace,monospace}
|
||||
@media(max-width:700px){.grid{grid-template-columns:1fr}}
|
||||
:root{--paper:#f5f3ee;--ink:#121211;--muted:#77746c;--line:rgba(18,18,17,.15);--signal:#ff4d00;--soft:#ebe8e1;--pad:clamp(22px,3vw,48px)}
|
||||
*{box-sizing:border-box}html,body{min-height:100%}body{margin:0;background:var(--paper);color:var(--ink);font-family:"Manrope",system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;-webkit-font-smoothing:antialiased}
|
||||
button,textarea,input{font:inherit}button{cursor:pointer}a{color:inherit;text-decoration:none}
|
||||
.shell{min-height:100svh;padding:var(--pad);display:grid;grid-template-rows:auto 1fr auto;gap:40px}
|
||||
.masthead,.footer{display:flex;align-items:center;justify-content:space-between;gap:24px}
|
||||
.brand{font-size:clamp(27px,2.2vw,38px);font-weight:700;letter-spacing:-.075em;line-height:1}.brand-dot{color:var(--signal)}
|
||||
.wordmark{font-family:"DM Mono",ui-monospace,monospace;font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:var(--muted);text-align:right}.wordmark strong{display:block;color:var(--ink);font-weight:500;margin-top:3px}
|
||||
.canvas{align-self:center;width:min(1080px,100%);margin:0 auto;display:grid;grid-template-columns:minmax(0,1.05fr) minmax(330px,.95fr);gap:clamp(36px,6vw,88px);align-items:start}
|
||||
.eyebrow,.kicker,.footer,.micro{font-family:"DM Mono",ui-monospace,monospace;text-transform:uppercase;letter-spacing:.07em}
|
||||
.eyebrow{margin:0 0 14px;font-size:11px;color:var(--muted)}h1{margin:0;font-size:clamp(58px,8vw,112px);line-height:.84;letter-spacing:-.078em;font-weight:500;max-width:7ch}
|
||||
.lede{margin:22px 0 0;max-width:570px;color:var(--muted);font-size:15px;line-height:1.65}
|
||||
.composer{margin-top:38px;border-top:1px solid var(--ink);padding-top:18px}
|
||||
.kicker{font-size:10px;color:var(--muted);margin:0 0 10px}
|
||||
.prompt{width:100%;min-height:128px;resize:vertical;border:1px solid var(--line);background:transparent;padding:16px;color:var(--ink);font-size:17px;line-height:1.5;outline:none;transition:border-color .15s ease}
|
||||
.prompt:focus{border-color:var(--ink)}.prompt::placeholder{color:#99958d}
|
||||
.actions{display:flex;align-items:center;justify-content:space-between;gap:14px;margin-top:12px}.micro{font-size:9px;color:var(--muted)}
|
||||
.primary{border:1px solid var(--ink);background:var(--ink);color:var(--paper);padding:12px 16px;font-size:13px;font-weight:650;transition:transform .15s ease,background .15s ease,border-color .15s ease}
|
||||
.primary:hover{background:var(--signal);border-color:var(--signal);transform:translateY(-2px)}
|
||||
.recipe{border-top:1px solid var(--ink)}.recipe-head{padding:18px 0 16px;display:flex;justify-content:space-between;align-items:end;gap:20px}.recipe-head h2{margin:2px 0 0;font-size:27px;letter-spacing:-.045em}.state{font-family:"DM Mono",ui-monospace,monospace;font-size:9px;text-transform:uppercase;letter-spacing:.06em;color:var(--signal)}
|
||||
.rows{border-bottom:1px solid var(--ink)}.row{display:grid;grid-template-columns:110px 1fr;gap:18px;padding:14px 0;border-top:1px solid var(--line)}.row:first-child{border-top:0}.label{font-family:"DM Mono",ui-monospace,monospace;font-size:9px;text-transform:uppercase;letter-spacing:.07em;color:var(--muted)}.value{font-size:13px;line-height:1.45}
|
||||
.chips{display:flex;flex-wrap:wrap;gap:7px}.chip{border:1px solid var(--line);background:transparent;padding:7px 9px;font-family:"DM Mono",ui-monospace,monospace;font-size:9px;color:var(--muted)}button.chip:hover,button.chip.active{border-color:var(--signal);color:var(--ink);background:rgba(255,77,0,.055)}
|
||||
.recipe-actions{display:flex;flex-wrap:wrap;gap:8px;padding-top:14px}.secondary{border:1px solid var(--line);background:transparent;padding:10px 12px;font-size:11px}.secondary:hover{border-color:var(--ink)}
|
||||
.note{margin:14px 0 0;color:var(--muted);font-size:11px;line-height:1.55}.note strong{color:var(--ink)}
|
||||
.footer{font-size:9px;color:var(--muted)}.footer span:last-child{color:var(--ink)}
|
||||
.toast{position:fixed;left:50%;bottom:28px;transform:translate(-50%,10px);opacity:0;pointer-events:none;background:var(--ink);color:var(--paper);padding:9px 12px;font-family:"DM Mono",ui-monospace,monospace;font-size:10px;text-transform:uppercase;letter-spacing:.06em;transition:.15s ease}.toast.show{opacity:1;transform:translate(-50%,0)}
|
||||
@media(max-width:850px){.canvas{grid-template-columns:1fr;gap:54px}h1{max-width:8ch}.recipe{margin-top:4px}}
|
||||
@media(max-width:520px){.wordmark span{display:none}.actions{align-items:flex-end}.row{grid-template-columns:82px 1fr}.footer{align-items:flex-end;text-align:right}}
|
||||
@media(prefers-reduced-motion:reduce){*{transition:none!important}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="top"><a class="brand" href="https://phast.app">phast<span class="dot">.</span></a><span class="badge">read-only prototype</span></div>
|
||||
<h1>My Phast</h1>
|
||||
<p class="lede">Owner control-plane prototype. Phast now lives in <code>matcha/phast</code> and is served from the Teahub Gitea/Pages machine instead of the shared Boring runtime.</p>
|
||||
<div class="grid">
|
||||
<section class="card"><h2>Apps</h2><p>No apps are registered yet.</p></section>
|
||||
<section class="card"><h2>Control-plane readiness</h2><p>The previous runtime-backed mutation path has been retired. A live MCP/control backend should be reintroduced only from the canonical Phast source, not by embedding it in Boring.</p></section>
|
||||
</div>
|
||||
<div class="footer">human surface: my.phast.app · source: matcha/phast</div>
|
||||
<main class="shell">
|
||||
<header class="masthead">
|
||||
<a class="brand" href="https://phast.app" aria-label="Phast home">phast<span class="brand-dot">.</span></a>
|
||||
<div class="wordmark"><span>personal composer</span><strong>my.phast.app</strong></div>
|
||||
</header>
|
||||
|
||||
<section class="canvas">
|
||||
<div>
|
||||
<p class="eyebrow">connect → compose → launch</p>
|
||||
<h1>Make the idea executable.</h1>
|
||||
<p class="lede">Describe what you want. Phast turns intent into a small app recipe: intelligence, capabilities, runtime, and surfaces. Oracle can discover what exists; Crystal can find a route; Factory can do the heavy building. Phast keeps the composition.</p>
|
||||
|
||||
<div class="composer">
|
||||
<p class="kicker">What do you want to make?</p>
|
||||
<textarea class="prompt" id="prompt" placeholder="A tiny app that watches a mailbox, extracts receipts, and gives me a weekly total."></textarea>
|
||||
<div class="actions">
|
||||
<span class="micro">draft first · authority later</span>
|
||||
<button class="primary" id="compose">Compose draft ↗</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside class="recipe" aria-live="polite">
|
||||
<div class="recipe-head">
|
||||
<div><p class="kicker">Draft composition</p><h2 id="recipeName">blank canvas</h2></div>
|
||||
<span class="state" id="state">uncomposed</span>
|
||||
</div>
|
||||
<div class="rows">
|
||||
<div class="row"><div class="label">intent</div><div class="value" id="intent">Describe something useful on the left.</div></div>
|
||||
<div class="row"><div class="label">intelligence</div><div class="value"><div class="chips" id="intelligence">
|
||||
<button class="chip active" data-kind="intelligence" data-value="connected agent">connected agent</button>
|
||||
<button class="chip" data-kind="intelligence" data-value="Codex">Codex</button>
|
||||
<button class="chip" data-kind="intelligence" data-value="ChatGPT">ChatGPT</button>
|
||||
<button class="chip" data-kind="intelligence" data-value="other MCP client">other MCP</button>
|
||||
</div></div></div>
|
||||
<div class="row"><div class="label">capabilities</div><div class="value"><div class="chips" id="capabilities">
|
||||
<button class="chip active" data-kind="capability" data-value="discover via Oracle">discover via Oracle</button>
|
||||
<button class="chip" data-kind="capability" data-value="GitHub">GitHub</button>
|
||||
<button class="chip" data-kind="capability" data-value="Gmail">Gmail</button>
|
||||
<button class="chip" data-kind="capability" data-value="Cloudflare">Cloudflare</button>
|
||||
</div></div></div>
|
||||
<div class="row"><div class="label">shape</div><div class="value">Python + Prefab + MCP · human <code>/</code> · agent <code>/mcp</code></div></div>
|
||||
<div class="row"><div class="label">route</div><div class="value">Oracle indexes → Crystal routes → Factory builds → <strong>Phast composes</strong></div></div>
|
||||
</div>
|
||||
<div class="recipe-actions">
|
||||
<button class="secondary" id="copy">Copy recipe</button>
|
||||
<a class="secondary" href="https://xn--sj8h.teahub.xyz/oracle/">Explore Oracle</a>
|
||||
</div>
|
||||
<p class="note"><strong>This is intentionally a composer, not a fake deployment button.</strong> A later iteration can make “launch” real once the authority + runtime path is worth keeping.</p>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<footer class="footer"><span>idea → composition → app</span><span>small enough to remake</span></footer>
|
||||
</main>
|
||||
<div class="toast" id="toast">recipe copied</div>
|
||||
<script>
|
||||
const promptEl=document.querySelector('#prompt');
|
||||
const nameEl=document.querySelector('#recipeName');
|
||||
const intentEl=document.querySelector('#intent');
|
||||
const stateEl=document.querySelector('#state');
|
||||
const toast=document.querySelector('#toast');
|
||||
let intelligence='connected agent';
|
||||
let capabilities=new Set(['discover via Oracle']);
|
||||
|
||||
function slugWords(text){
|
||||
const stop=new Set(['a','an','the','that','this','to','for','and','or','of','my','me','with','from','into','app','tiny']);
|
||||
const words=(text.toLowerCase().match(/[a-z0-9]+/g)||[]).filter(x=>!stop.has(x)).slice(0,3);
|
||||
return words.length?words.join(' '):'new composition';
|
||||
}
|
||||
function compose(){
|
||||
const text=promptEl.value.trim();
|
||||
if(!text){promptEl.focus(); return;}
|
||||
nameEl.textContent=slugWords(text);
|
||||
intentEl.textContent=text;
|
||||
stateEl.textContent='draft';
|
||||
}
|
||||
function recipe(){
|
||||
const text=promptEl.value.trim()||'unwritten intent';
|
||||
return {
|
||||
kind:'phast.app.draft',
|
||||
name:nameEl.textContent,
|
||||
intent:text,
|
||||
intelligence,
|
||||
capabilities:[...capabilities],
|
||||
composition:{
|
||||
runtime:'python',
|
||||
human:'/',
|
||||
agent:'/mcp',
|
||||
ui:'prefab',
|
||||
discovery:'oracle',
|
||||
routing:'crystal',
|
||||
build:'factory'
|
||||
},
|
||||
authority:'not granted',
|
||||
status:'draft'
|
||||
};
|
||||
}
|
||||
document.querySelector('#compose').addEventListener('click',compose);
|
||||
promptEl.addEventListener('keydown',e=>{if((e.metaKey||e.ctrlKey)&&e.key==='Enter')compose()});
|
||||
document.querySelectorAll('[data-kind="intelligence"]').forEach(btn=>btn.addEventListener('click',()=>{
|
||||
document.querySelectorAll('[data-kind="intelligence"]').forEach(x=>x.classList.remove('active'));
|
||||
btn.classList.add('active'); intelligence=btn.dataset.value;
|
||||
}));
|
||||
document.querySelectorAll('[data-kind="capability"]').forEach(btn=>btn.addEventListener('click',()=>{
|
||||
const v=btn.dataset.value;
|
||||
if(capabilities.has(v)){capabilities.delete(v);btn.classList.remove('active')}
|
||||
else{capabilities.add(v);btn.classList.add('active')}
|
||||
}));
|
||||
document.querySelector('#copy').addEventListener('click',async()=>{
|
||||
const text=JSON.stringify(recipe(),null,2);
|
||||
try{await navigator.clipboard.writeText(text);toast.textContent='recipe copied'}
|
||||
catch{toast.textContent='copy unavailable'}
|
||||
toast.classList.add('show');setTimeout(()=>toast.classList.remove('show'),1200);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user