2026-10-04 02:37:30 +00:00
<!doctype html>
< html lang = "en" >
< head >
< meta charset = "utf-8" >
< meta name = "viewport" content = "width=device-width,initial-scale=1" >
2026-10-04 02:46:49 +00:00
< 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" >
2026-10-04 02:37:30 +00:00
< style >
2026-10-04 02:46:49 +00:00
: root { --paper : #f5f3ee ; --ink : #121211 ; --muted : #77746c ; --line : rgba ( 18 , 18 , 17 , .15 ); --signal : #ff4d00 ; --soft : #ebe8e1 ; --pad : clamp ( 22 px , 3 vw , 48 px )}
* { 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 : 100 svh ; padding : var ( -- pad ); display : grid ; grid-template-rows : auto 1 fr auto ; gap : 40 px }
. masthead , . footer { display : flex ; align-items : center ; justify-content : space-between ; gap : 24 px }
. brand { font-size : clamp ( 27 px , 2.2 vw , 38 px ); font-weight : 700 ; letter-spacing : -.075 em ; line-height : 1 }. brand-dot { color : var ( -- signal )}
. wordmark { font-family : "DM Mono" , ui-monospace , monospace ; font-size : 10 px ; text-transform : uppercase ; letter-spacing : .08 em ; color : var ( -- muted ); text-align : right }. wordmark strong { display : block ; color : var ( -- ink ); font-weight : 500 ; margin-top : 3 px }
. canvas { align-self : center ; width : min ( 1080 px , 100 % ); margin : 0 auto ; display : grid ; grid-template-columns : minmax ( 0 , 1.05 fr ) minmax ( 330 px , .95 fr ); gap : clamp ( 36 px , 6 vw , 88 px ); align-items : start }
. eyebrow , . kicker , . footer , . micro { font-family : "DM Mono" , ui-monospace , monospace ; text-transform : uppercase ; letter-spacing : .07 em }
. eyebrow { margin : 0 0 14 px ; font-size : 11 px ; color : var ( -- muted )} h1 { margin : 0 ; font-size : clamp ( 58 px , 8 vw , 112 px ); line-height : .84 ; letter-spacing : -.078 em ; font-weight : 500 ; max-width : 7 ch }
. lede { margin : 22 px 0 0 ; max-width : 570 px ; color : var ( -- muted ); font-size : 15 px ; line-height : 1.65 }
. composer { margin-top : 38 px ; border-top : 1 px solid var ( -- ink ); padding-top : 18 px }
. kicker { font-size : 10 px ; color : var ( -- muted ); margin : 0 0 10 px }
. prompt { width : 100 % ; min-height : 128 px ; resize : vertical ; border : 1 px solid var ( -- line ); background : transparent ; padding : 16 px ; color : var ( -- ink ); font-size : 17 px ; line-height : 1.5 ; outline : none ; transition : border-color .15 s ease }
. prompt : focus { border-color : var ( -- ink )}. prompt :: placeholder { color : #99958d }
. actions { display : flex ; align-items : center ; justify-content : space-between ; gap : 14 px ; margin-top : 12 px }. micro { font-size : 9 px ; color : var ( -- muted )}
. primary { border : 1 px solid var ( -- ink ); background : var ( -- ink ); color : var ( -- paper ); padding : 12 px 16 px ; font-size : 13 px ; font-weight : 650 ; transition : transform .15 s ease , background .15 s ease , border-color .15 s ease }
. primary : hover { background : var ( -- signal ); border-color : var ( -- signal ); transform : translateY ( -2 px )}
. recipe { border-top : 1 px solid var ( -- ink )}. recipe-head { padding : 18 px 0 16 px ; display : flex ; justify-content : space-between ; align-items : end ; gap : 20 px }. recipe-head h2 { margin : 2 px 0 0 ; font-size : 27 px ; letter-spacing : -.045 em }. state { font-family : "DM Mono" , ui-monospace , monospace ; font-size : 9 px ; text-transform : uppercase ; letter-spacing : .06 em ; color : var ( -- signal )}
. rows { border-bottom : 1 px solid var ( -- ink )}. row { display : grid ; grid-template-columns : 110 px 1 fr ; gap : 18 px ; padding : 14 px 0 ; border-top : 1 px solid var ( -- line )}. row : first-child { border-top : 0 }. label { font-family : "DM Mono" , ui-monospace , monospace ; font-size : 9 px ; text-transform : uppercase ; letter-spacing : .07 em ; color : var ( -- muted )}. value { font-size : 13 px ; line-height : 1.45 }
. chips { display : flex ; flex-wrap : wrap ; gap : 7 px }. chip { border : 1 px solid var ( -- line ); background : transparent ; padding : 7 px 9 px ; font-family : "DM Mono" , ui-monospace , monospace ; font-size : 9 px ; 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 : 8 px ; padding-top : 14 px }. secondary { border : 1 px solid var ( -- line ); background : transparent ; padding : 10 px 12 px ; font-size : 11 px }. secondary : hover { border-color : var ( -- ink )}
. note { margin : 14 px 0 0 ; color : var ( -- muted ); font-size : 11 px ; line-height : 1.55 }. note strong { color : var ( -- ink )}
. footer { font-size : 9 px ; color : var ( -- muted )}. footer span : last-child { color : var ( -- ink )}
. toast { position : fixed ; left : 50 % ; bottom : 28 px ; transform : translate ( -50 % , 10 px ); opacity : 0 ; pointer-events : none ; background : var ( -- ink ); color : var ( -- paper ); padding : 9 px 12 px ; font-family : "DM Mono" , ui-monospace , monospace ; font-size : 10 px ; text-transform : uppercase ; letter-spacing : .06 em ; transition : .15 s ease }. toast . show { opacity : 1 ; transform : translate ( -50 % , 0 )}
@ media ( max-width : 850px ) {. canvas { grid-template-columns : 1 fr ; gap : 54 px } h1 { max-width : 8 ch }. recipe { margin-top : 4 px }}
@ media ( max-width : 520px ) {. wordmark span { display : none }. actions { align-items : flex-end }. row { grid-template-columns : 82 px 1 fr }. footer { align-items : flex-end ; text-align : right }}
@ media ( prefers-reduced-motion : reduce ) { * { transition : none !important }}
2026-10-04 02:37:30 +00:00
</ style >
</ head >
< body >
2026-10-04 02:46:49 +00:00
< 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 >
2026-10-04 02:37:30 +00:00
</ main >
2026-10-04 02:46:49 +00:00
< 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 >
2026-10-04 02:37:30 +00:00
</ body >
</ html >