@@ -39,11 +39,22 @@ <h1>Real Python in your browser tab.<br><span class="accent">No server.</span></
3939 < a class ="btn ghostBtn " href ="https://github.com/eddmpython/pyproc "> GitHub</ a >
4040 < code class ="chip " id ="installChip " title ="click to copy "> npm install pyproc</ code >
4141 </ div >
42- < div class ="term heroTerm " aria-hidden ="true "> < span class ="dim "> # the machine demo, in 5 lines</ span >
43- < span class ="ok "> >>></ span > data = train_for_ten_minutes()
44- < span class ="dim "> # close the tab. reopen tomorrow.</ span >
45- < span class ="ok "> >>></ span > data.score() < span class ="dim "> # still here. nothing re-ran.</ span >
46- < span class ="ok "> >>></ span > %undo < span class ="dim "> # time-travel the heap, ~1ms</ span > </ div >
42+
43+ <!-- 히어로 데모는 그림이 아니라 진짜다: 누르면 이 탭에서 CPython이 부팅되고, 아래 숫자(부팅 ms,
44+ 복원 ms, 값)는 전부 그 자리에서 측정된다. 엔진은 누르기 전에 내려받지 않는다(동적 import):
45+ 랜딩의 첫 로드는 파이썬 없이 가볍고, 증거가 필요한 사람만 비용을 낸다. -->
46+ < div class ="panel heroDemo ">
47+ < div class ="term " id ="heroTerm " aria-live ="polite "> < span class ="dim "> # checkpoint, wreck it, time-travel back. live, in this tab.</ span >
48+ < span class ="ok "> >>></ span > data = list(range(1_000_000)) < span class ="dim "> # the prepared state</ span >
49+ < span class ="ok "> >>></ span > cp = checkpoint() < span class ="dim "> # a point to return to</ span >
50+ < span class ="ok "> >>></ span > data.clear() < span class ="dim "> # an agent wrecks it</ span >
51+ < span class ="ok "> >>></ span > restore(cp) < span class ="dim "> # changed pages only</ span >
52+ < span class ="ok "> >>></ span > len(data) < span class ="dim "> # whole again, no re-run</ span > </ div >
53+ < div class ="row heroRun ">
54+ < button id ="runHero "> Run this in your browser</ button >
55+ < span class ="status " id ="heroStatus "> Real CPython, booted here. Nothing downloads until you press it.</ span >
56+ </ div >
57+ </ div >
4758 </ section >
4859
4960 < section class ="stats " aria-label ="measured results ">
@@ -112,11 +123,76 @@ <h2 id="install">Install</h2>
112123 < span > < a href ="https://github.com/eddmpython/pyproc "> GitHub</ a > · < a href ="https://www.npmjs.com/package/pyproc "> npm</ a > · < a href ="https://buymeacoffee.com/eddmpython "> support</ a > </ span >
113124 </ footer >
114125 </ div >
115- < script >
116- // 설치 칩 클릭 = 클립보드 복사(랜딩의 유일한 JS. 실패해도 무해).
126+ < script type =" module " >
127+ // 설치 칩 클릭 = 클립보드 복사(실패해도 무해).
117128 document . getElementById ( "installChip" ) . addEventListener ( "click" , async ( e ) => {
118129 try { await navigator . clipboard . writeText ( "npm install pyproc" ) ; e . target . textContent = "copied!" ; setTimeout ( ( ) => { e . target . textContent = "npm install pyproc" ; } , 1200 ) ; } catch ( err ) { }
119130 } ) ;
131+
132+ // 히어로 라이브 데모. index.js는 배포 루트에 있다(pages.yml이 올리고, 로컬 serve.mjs는 "/"를
133+ // 이 페이지로 매핑한다). import를 클릭 시점으로 미루는 게 핵심이다: 엔진(수 MB)은 증거를
134+ // 보겠다고 누른 사람만 받는다. 숫자는 하나도 하드코딩하지 않는다. 전부 지금 여기서 잰다.
135+ const term = document . getElementById ( "heroTerm" ) ;
136+ const status = document . getElementById ( "heroStatus" ) ;
137+ const runBtn = document . getElementById ( "runHero" ) ;
138+ const gateMode = new URLSearchParams ( location . search ) . has ( "gate" ) ;
139+ const print = ( html ) => { term . insertAdjacentHTML ( "beforeend" , "\n" + html ) ; } ;
140+ const gateReport = ( ok ) => {
141+ if ( ! gateMode ) return ;
142+ fetch ( "/gateReport" , {
143+ method : "POST" , headers : { "Content-Type" : "application/json" } ,
144+ body : JSON . stringify ( { ok : ! ! ok , checks : [ { name : document . title , pass : ! ! ok , info : term . textContent . slice ( - 200 ) } ] } ) ,
145+ } ) . catch ( ( ) => { } ) ;
146+ } ;
147+
148+ async function runHeroDemo ( ) {
149+ runBtn . disabled = true ;
150+ status . textContent = "Downloading CPython (WebAssembly)..." ;
151+ try {
152+ const t0 = performance . now ( ) ;
153+ const { boot } = await import ( "./index.js" ) ;
154+ const rt = await boot ( ) ;
155+ const bootMs = Math . round ( performance . now ( ) - t0 ) ;
156+ const version = rt . run ( "import sys; sys.version.split()[0]" ) ;
157+ status . innerHTML = `CPython <b class="ok">${ version } </b> booted in this tab in <b class="ok">${ bootMs } ms</b>` ;
158+ print ( "" ) ;
159+ print ( `<span class="dim"># live now, in this tab</span>` ) ;
160+
161+ const reactive = rt . enableReactive ( ) ;
162+ rt . run ( "data = list(range(1_000_000))" ) ;
163+ const sp = reactive . stackSave ( ) ;
164+ const cp = reactive . checkpoint ( ) ;
165+ const prepared = rt . run ( "len(data)" ) ;
166+ print ( `<span class="ok">>>></span> len(data) <span class="ok">${ prepared . toLocaleString ( ) } </span> <span class="dim"># prepared, checkpoint saved</span>` ) ;
167+
168+ rt . run ( "data.clear()" ) ;
169+ const wrecked = rt . run ( "len(data)" ) ;
170+ print ( `<span class="ok">>>></span> len(data) <span class="err">${ wrecked } </span> <span class="dim"># the agent wrecked it</span>` ) ;
171+
172+ reactive . checkpoint ( ) ; // 실행 경계를 닫는다(복원을 건전하게 만드는 계약)
173+ const tr = performance . now ( ) ;
174+ reactive . restoreLive ( cp . index , sp ) ;
175+ const restoreMs = performance . now ( ) - tr ;
176+ const restored = rt . run ( "len(data)" ) ;
177+ print ( `<span class="ok">>>></span> len(data) <span class="ok">${ restored . toLocaleString ( ) } </span> <span class="dim"># restored in ${ restoreMs . toFixed ( 1 ) } ms, no re-run</span>` ) ;
178+
179+ const ok = prepared === 1000000 && wrecked === 0 && restored === 1000000 ;
180+ status . innerHTML += ok
181+ ? ` · heap time-travel in <b class="ok">${ restoreMs . toFixed ( 1 ) } ms</b>`
182+ : ` · <span class="err">unexpected result</span>` ;
183+ runBtn . textContent = "Run again" ;
184+ runBtn . disabled = false ;
185+ runBtn . onclick = ( ) => location . reload ( ) ;
186+ gateReport ( ok ) ;
187+ } catch ( e ) {
188+ status . innerHTML = `<span class="err">Failed: ${ e } </span> (needs Chromium/Edge)` ;
189+ runBtn . disabled = false ;
190+ gateReport ( false ) ;
191+ }
192+ }
193+
194+ runBtn . addEventListener ( "click" , runHeroDemo , { once : true } ) ;
195+ if ( gateMode ) runHeroDemo ( ) ; // 예제 게이트가 랜딩도 사람처럼 연다(?gate=1)
120196 </ script >
121197</ body >
122198</ html >
0 commit comments