Files
app/my/index.html
T

161 lines
11 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<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;--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 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>