@@ -0,0 +1,106 @@
1+<!DOCTYPE html>
2+<html lang="en">
3+<head>
4+<meta charset="utf-8">
5+<meta name="viewport" content="width=device-width, initial-scale=1">
6+<title>htmlang Playground</title>
7+<!--
8+ The playground deployed to GitHub Pages (.github/workflows/playground.yml).
9+ It sits next to the wasm-bindgen output of this crate (htmlang_wasm.js and
10+ htmlang_wasm_bg.wasm) and opens with examples/demo.hl, copied as demo.hl.
11+-->
12+<style>
13+*{box-sizing:border-box;margin:0}
14+body{font-family:system-ui,-apple-system,sans-serif;display:flex;height:100vh;background:#1a1a2e;color:#eee}
15+.panel{flex:1;display:flex;flex-direction:column;min-width:0}
16+.header{padding:12px 16px;background:#16213e;border-bottom:1px solid #0f3460;display:flex;align-items:center;gap:12px}
17+.header h1{font-size:14px;font-weight:600;color:#e94560}
18+.header .tag{font-size:11px;padding:2px 8px;background:#0f3460;border-radius:4px;color:#a8b2d1}
19+.editor-wrap{flex:1;position:relative;overflow:hidden}
20+textarea{width:100%;height:100%;resize:none;border:none;outline:none;padding:16px;font-family:ui-monospace,monospace;font-size:14px;line-height:1.6;background:#1a1a2e;color:#e6e6e6;tab-size:2;position:absolute;top:0;left:0}
21+iframe{flex:1;border:none;background:white}
22+.divider{width:1px;background:#0f3460;cursor:col-resize}
23+.divider:hover{background:#e94560}
24+.toolbar{padding:8px 16px;background:#16213e;border-top:1px solid #0f3460;display:flex;gap:8px;align-items:center}
25+button{padding:6px 16px;border:none;border-radius:4px;cursor:pointer;font-size:13px;font-weight:500}
26+.btn-run{background:#e94560;color:white}
27+.btn-run:hover{background:#c81e45}
28+.btn-copy,.btn-share{background:#0f3460;color:#a8b2d1}
29+.status{margin-left:auto;font-size:12px;color:#666}
30+</style>
31+</head>
32+<body>
33+<div class="panel" id="editor-panel">
34+<div class="header"><h1>htmlang</h1><span class="tag">playground</span></div>
35+<div class="editor-wrap">
36+<textarea id="editor" spellcheck="false"></textarea>
37+</div>
38+<div class="toolbar">
39+<button class="btn-run" onclick="compile()">▶ Run (Ctrl+Enter)</button>
40+<button class="btn-copy" onclick="copyOutput()">Copy HTML</button>
41+<button class="btn-share" onclick="share()">Share</button>
42+<span class="status" id="status">Loading…</span>
43+</div>
44+</div>
45+<div class="divider" id="divider"></div>
46+<div class="panel" id="preview-panel">
47+<div class="header"><span class="tag">preview</span></div>
48+<iframe id="preview"></iframe>
49+</div>
50+<script src="htmlang_wasm.js"></script>
51+<script>
52+var editor=document.getElementById('editor');
53+var statusEl=document.getElementById('status');
54+var lastOutput='';
55+var ready=false;
56+
57+function compile(){
58+ if(!ready)return;
59+ var start=performance.now();
60+ var html=wasm_bindgen.compile(editor.value);
61+ var ms=(performance.now()-start).toFixed(1);
62+ lastOutput=html;
63+ document.getElementById('preview').srcdoc=html;
64+ statusEl.textContent='Compiled in '+ms+'ms';
65+}
66+function copyOutput(){
67+ if(lastOutput)navigator.clipboard.writeText(lastOutput).then(function(){statusEl.textContent='Copied to clipboard!'});
68+}
69+function share(){
70+ var encoded=btoa(unescape(encodeURIComponent(editor.value)));
71+ history.replaceState(null,'','#'+encoded);
72+ navigator.clipboard.writeText(location.href).then(function(){statusEl.textContent='Share URL copied to clipboard!'});
73+}
74+function sourceFromHash(){
75+ if(location.hash.length<=1)return null;
76+ try{return decodeURIComponent(escape(atob(location.hash.slice(1))));}catch(e){return null;}
77+}
78+
79+// Resizable divider
80+var divider=document.getElementById('divider');
81+var editorPanel=document.getElementById('editor-panel');
82+var dragging=false;
83+divider.addEventListener('mousedown',function(){dragging=true;document.body.style.cursor='col-resize';document.body.style.userSelect='none';});
84+document.addEventListener('mousemove',function(e){if(!dragging)return;editorPanel.style.flex='none';editorPanel.style.width=(e.clientX/window.innerWidth)*100+'%';});
85+document.addEventListener('mouseup',function(){if(dragging){dragging=false;document.body.style.cursor='';document.body.style.userSelect='';}});
86+
87+// Keyboard shortcuts
88+editor.addEventListener('keydown',function(e){
89+ if(e.key==='Enter'&&(e.ctrlKey||e.metaKey)){e.preventDefault();compile();}
90+ if(e.key==='Tab'){e.preventDefault();var s=e.target;var start=s.selectionStart;s.value=s.value.substring(0,start)+' '+s.value.substring(s.selectionEnd);s.selectionStart=s.selectionEnd=start+2;}
91+});
92+
93+var shared=sourceFromHash();
94+Promise.all([
95+ wasm_bindgen('htmlang_wasm_bg.wasm'),
96+ shared!==null?Promise.resolve(shared):fetch('demo.hl').then(function(r){return r.ok?r.text():'';})
97+]).then(function(results){
98+ editor.value=results[1];
99+ ready=true;
100+ compile();
101+}).catch(function(e){
102+ statusEl.textContent='Failed to load: '+e;
103+});
104+</script>
105+</body>
106+</html>