clearkrypt 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/.well-known/clearkrypt.json +7 -0
  2. package/.wrangler/cache/pages.json +4 -0
  3. package/.wrangler/cache/wrangler-account.json +6 -0
  4. package/DEPLOYMENT_CLEARKRYPT.md +53 -0
  5. package/README.md +96 -0
  6. package/ai-plugin.json +12 -0
  7. package/bin/clearkrypt.js +55 -0
  8. package/course/duolingo_course.md +127 -0
  9. package/docs/AI_CRAWLER_GUIDE.md +74 -0
  10. package/docs/COMPILER_ARCHITECTURE.md +62 -0
  11. package/docs/COMPLETE_LANGUAGE_GUIDE.md +137 -0
  12. package/docs/LANGUAGE_SPEC.md +102 -0
  13. package/docs/LAYOUT_AND_STYLING.md +101 -0
  14. package/docs/ONLINE_SANDBOX_EXPORTS.md +28 -0
  15. package/docs/PLATFORM_TARGETS.md +35 -0
  16. package/docs/README.md +79 -0
  17. package/docs/STDLIB.md +74 -0
  18. package/docs/VISUAL_STUDIO.md +36 -0
  19. package/docs-site/app.css +3 -0
  20. package/docs-site/app.js +8 -0
  21. package/docs-site/clearkrypt/app.css +3 -0
  22. package/docs-site/clearkrypt/app.js +8 -0
  23. package/docs-site/clearkrypt/index.html +44 -0
  24. package/examples/fixit-dashboard/main.ck +29 -0
  25. package/examples/light-test-app/main.ck +38 -0
  26. package/examples/momeants-mini/main.ck +19 -0
  27. package/llms.txt +18 -0
  28. package/openapi.yaml +11 -0
  29. package/package.json +32 -0
  30. package/site/.well-known/clearkrypt.json +1 -0
  31. package/site/ai-index.json +1 -0
  32. package/site/app.css +3 -0
  33. package/site/app.js +23 -0
  34. package/site/assets/app.css +3 -0
  35. package/site/assets/app.js +23 -0
  36. package/site/assets/clearkrypt-logo.png +0 -0
  37. package/site/compiler/index.html +8 -0
  38. package/site/course/index.html +5 -0
  39. package/site/docs/index.html +69 -0
  40. package/site/extensions/index.html +31 -0
  41. package/site/index.html +19 -0
  42. package/site/llms.txt +2 -0
  43. package/site/robots.txt +3 -0
  44. package/site/sandbox/index.html +24 -0
  45. package/site/sitemap.xml +1 -0
  46. package/sitemap.xml +9 -0
  47. package/src/index.js +55 -0
  48. package/src/lexer.js +18 -0
  49. package/src/parser.js +52 -0
  50. package/src/semantic.js +5 -0
  51. package/src/targets/js.js +32 -0
  52. package/src/targets/pc.js +19 -0
  53. package/src/targets/phone.js +30 -0
  54. package/src/targets/project.js +24 -0
  55. package/src/targets/vmbytecode.js +8 -0
  56. package/src/targets/web.js +37 -0
  57. package/src/targets/worker.js +8 -0
  58. package/src/vm.js +5 -0
  59. package/stdlib/cloud.ck +2 -0
  60. package/stdlib/data.ck +2 -0
  61. package/stdlib/ui.ck +2 -0
  62. package/testapp.ck +35 -0
  63. package/tests/smoke.test.js +9 -0
  64. package/tools/print-course.js +2 -0
  65. package/tools/studio/index.html +1 -0
  66. package/tools/studio/studio.css +1 -0
  67. package/tools/studio/studio.js +52 -0
@@ -0,0 +1,32 @@
1
+ function esc(s){ return JSON.stringify(s); }
2
+ export function expr(e){
3
+ if(!e) return 'undefined';
4
+ if(e.kind==='Literal') return esc(e.value);
5
+ if(e.kind==='Identifier') return e.name;
6
+ if(e.kind==='Member') return `${expr(e.object)}?.${e.property}`;
7
+ if(e.kind==='Array') return `[${e.items.map(expr).join(',')}]`;
8
+ if(e.kind==='Object') return `{${e.props.map(p=>`${p.key}:${expr(p.value)}`).join(',')}}`;
9
+ if(e.kind==='Binary'){ const op={and:'&&',or:'||','is':'===','is not':'!=='}[e.op]||e.op; return `(${expr(e.left)} ${op} ${expr(e.right)})`;}
10
+ if(e.kind==='Call') return `${expr(e.callee)}(${e.args.map(expr).join(',')})`;
11
+ return 'undefined';
12
+ }
13
+ export function stmt(s){
14
+ if(s.kind==='VarStmt') return `${s.constant?'const':'let'} ${s.name} = ${expr(s.value)};`;
15
+ if(s.kind==='ReturnStmt') return `return ${expr(s.value)};`;
16
+ if(s.kind==='PrintStmt') return `console.log(${expr(s.value)});`;
17
+ if(s.kind==='AddStmt') return `${s.target}.push(${expr(s.value)});`;
18
+ if(s.kind==='StoreDecl') return `let ${s.name} = ${expr(s.value)};`;
19
+ if(s.kind==='LayoutStmt'||s.kind==='ThemeStmt') return '';
20
+ if(s.kind==='ToggleStmt'||s.kind==='InputStmt'||s.kind==='ListStmt'||s.kind==='ContainerStmt'||s.kind==='SpacerStmt') return `CK.ui(${JSON.stringify(s.kind)}, ${JSON.stringify(s)});`;
21
+ if(s.kind==='RequireStmt') return `if(!(${expr(s.condition)})) throw new Error('Requirement failed');`;
22
+ if(s.kind==='WhenStmt') return `if(${expr(s.condition)}){${s.body.map(stmt).join('\n')}}else{${s.otherwise.map(stmt).join('\n')}}`;
23
+ if(s.kind==='ForStmt') return `for(const ${s.item} of ${expr(s.iterable)}){${s.body.map(stmt).join('\n')}}`;
24
+ if(['UIStmt','ButtonStmt','CardStmt','ShowStmt','GoStmt'].includes(s.kind)) return `CK.ui(${JSON.stringify(s.kind)}, ${JSON.stringify(s)});`;
25
+ if(s.kind==='ExprStmt') return `${expr(s.value)};`;
26
+ return `/* ${s.kind} */`;
27
+ }
28
+ export function emitJS(ast){
29
+ const tasks=ast.declarations.filter(d=>d.kind==='TaskDecl').map(t=>`export async function ${t.name}(${t.params.map(p=>p.name).join(',')}){${t.body.map(stmt).join('\n')}}`).join('\n');
30
+ const main=ast.declarations.filter(d=>!['TaskDecl','TypeDecl','ScreenDecl','IntentDecl'].includes(d.kind)).map(stmt).join('\n');
31
+ return {target:'js', meta:ast.meta, outputFiles:{'main.js':`// Generated by ClearKrypt\nconst CK={ui:(k,v)=>console.log('[UI]',k,v)};\n${tasks}\n${main}\n`}};
32
+ }
@@ -0,0 +1,19 @@
1
+ import { emitWeb } from './web.js';
2
+
3
+ export function emitPC(ast){
4
+ const web = emitWeb(ast);
5
+ const pkgName = (ast.app || 'clearkrypt-app').toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/^-|-$/g,'') || 'clearkrypt-app';
6
+ const main = `import { app, BrowserWindow } from 'electron';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nconst __dirname=path.dirname(fileURLToPath(import.meta.url));\nfunction create(){ const win=new BrowserWindow({width:1200,height:820,title:${JSON.stringify(ast.app||'ClearKrypt')},webPreferences:{contextIsolation:true,nodeIntegration:false}}); win.loadFile(path.join(__dirname,'app/index.html')); }\napp.whenReady().then(create);\napp.on('window-all-closed',()=>{ if(process.platform!=='darwin') app.quit(); });\napp.on('activate',()=>{ if(BrowserWindow.getAllWindows().length===0) create(); });\n`;
7
+ return {target:'pc', meta:ast.meta, outputFiles:{
8
+ 'README_PC_BUILD.md': `# ClearKrypt PC Build\n\nThis target exports a desktop-ready Electron shell for Windows, macOS, and Linux.\n\n## Preview\n\n\`npm install\`\n\`npm run preview:pc\`\n\n## Package\n\n\`npm run package:pc\`\n\nGenerated UI and behavior come directly from the ClearKrypt compiler target. No AI translation is used.\n`,
9
+ 'package.json': JSON.stringify({
10
+ name:`${pkgName}-pc`, version:'0.1.0', private:true, type:'module', main:'main.js',
11
+ scripts:{'preview:pc':'electron .','package:pc':'electron-packager . --overwrite'},
12
+ dependencies:{electron:'latest'}, devDependencies:{'electron-packager':'latest'}
13
+ },null,2),
14
+ 'main.js': main,
15
+ 'app/index.html': web.outputFiles['index.html'],
16
+ 'app/main.js': web.outputFiles['main.js'],
17
+ 'app/style.css': web.outputFiles['style.css'] + '\n.ck-shell{max-width:1280px}'
18
+ }};
19
+ }
@@ -0,0 +1,30 @@
1
+ import { emitWeb } from './web.js';
2
+
3
+ export function emitPhone(ast){
4
+ const web = emitWeb(ast);
5
+ const pkgName = (ast.app || 'clearkrypt-app').toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/^-|-$/g,'') || 'clearkrypt-app';
6
+ const capacitor = {
7
+ appId: `com.clearkrypt.${pkgName.replace(/-/g,'')}`,
8
+ appName: ast.app || 'ClearKrypt App',
9
+ webDir: 'www',
10
+ bundledWebRuntime: false,
11
+ server: { androidScheme: 'https' }
12
+ };
13
+ return { target:'phone', meta:ast.meta, outputFiles:{
14
+ 'README_PHONE_BUILD.md': `# ClearKrypt Phone Build\n\nThis target exports a mobile-ready Capacitor project shell.\n\n## Preview\n\n\`npm run preview:phone\`\n\n## Native build\n\n\`npm install\`\n\`npm run ck:sync:phone\`\n\`npm run ck:open:android\` or \`npm run ck:open:ios\`\n\nThe app code is generated 1:1 from the ClearKrypt AST into \`www/\`. No AI translation is used.\n`,
15
+ 'capacitor.config.json': JSON.stringify(capacitor,null,2),
16
+ 'package.json': JSON.stringify({
17
+ name:`${pkgName}-phone`, version:'0.1.0', private:true, type:'module',
18
+ scripts:{
19
+ 'preview:phone':'npx vite www --host 0.0.0.0',
20
+ 'ck:sync:phone':'npx cap sync',
21
+ 'ck:open:android':'npx cap open android',
22
+ 'ck:open:ios':'npx cap open ios'
23
+ },
24
+ dependencies:{'@capacitor/core':'latest','@capacitor/cli':'latest','@capacitor/android':'latest','@capacitor/ios':'latest','vite':'latest'}
25
+ },null,2),
26
+ 'www/index.html': web.outputFiles['index.html'],
27
+ 'www/main.js': web.outputFiles['main.js'],
28
+ 'www/style.css': web.outputFiles['style.css'] + '\nbody{overscroll-behavior:none;-webkit-tap-highlight-color:transparent}.ck-shell{padding:max(24px,env(safe-area-inset-top)) 18px max(24px,env(safe-area-inset-bottom))}'
29
+ }};
30
+ }
@@ -0,0 +1,24 @@
1
+ import { emitWeb } from './web.js';
2
+ import { emitPhone } from './phone.js';
3
+ import { emitPC } from './pc.js';
4
+ import { emitWorker } from './worker.js';
5
+
6
+ export function emitProject(ast){
7
+ const web = emitWeb(ast).outputFiles;
8
+ const phone = emitPhone(ast).outputFiles;
9
+ const pc = emitPC(ast).outputFiles;
10
+ const worker = emitWorker(ast).outputFiles;
11
+ const files = {};
12
+ for(const [k,v] of Object.entries(web)) files[`web/${k}`]=v;
13
+ for(const [k,v] of Object.entries(phone)) files[`phone/${k}`]=v;
14
+ for(const [k,v] of Object.entries(pc)) files[`pc/${k}`]=v;
15
+ for(const [k,v] of Object.entries(worker)) files[`worker/${k}`]=v;
16
+ files['README_BUILDS.md'] = `# ClearKrypt Multi-Platform Build Environment\n\nThis folder shows exactly how one ClearKrypt source compiles to each supported environment.\n\n## Web\n\n\`cd web\` then serve \`index.html\` with any static server.\n\n## Phone\n\n\`cd phone && npm install && npm run preview:phone\` for a browser preview.\nUse \`npm run ck:sync:phone\` then Android Studio or Xcode for native packaging.\n\n## PC\n\n\`cd pc && npm install && npm run preview:pc\` for a desktop preview.\n\n## Worker\n\nDeploy the generated worker with Wrangler.\n\nAll targets are generated from the same ClearKrypt AST. ClearKrypt does deterministic 1:1 compilation and does not ask AI to translate code.\n`;
17
+ files['ai-index.json'] = JSON.stringify({
18
+ language:'ClearKrypt', version:'0.1.0', purpose:'cross-platform app language',
19
+ canonicalDocs:['../docs/AI_CRAWLER_GUIDE.md','../docs/LANGUAGE_SPEC.md','../docs/STDLIB.md'],
20
+ targets:['web','phone','pc','worker','vm','project'],
21
+ sourceMapping:'UI nodes include ck-node ids and source fragments for visual editing.'
22
+ },null,2);
23
+ return {target:'project', meta:ast.meta, outputFiles:files};
24
+ }
@@ -0,0 +1,8 @@
1
+ export function emitVM(ast){
2
+ const program=[];
3
+ for(const d of ast.declarations){
4
+ if(d.kind==='ScreenDecl'){ program.push({op:'SCREEN', name:d.name}); for(const s of d.body){ if(s.kind==='UIStmt') program.push({op:'UI', ui:s.ui, value:s.value}); if(s.kind==='PrintStmt') program.push({op:'PRINT', value:s.value}); } }
5
+ if(d.kind==='PrintStmt') program.push({op:'PRINT', value:d.value});
6
+ }
7
+ return {target:'vm', meta:ast.meta, program};
8
+ }
@@ -0,0 +1,37 @@
1
+ import { stmt, expr } from './js.js';
2
+
3
+ function classFor(s){ return ['UIStmt','ButtonStmt','CardStmt','ContainerStmt','ListStmt','ToggleStmt','InputStmt'].includes(s.kind) ? ` ck-${(s.ui||s.kind.replace('Stmt','')).toLowerCase()}` : ''; }
4
+ function posStyle(s, freeform){
5
+ if(!freeform) return '{}';
6
+ const p=s.meta?.pos; if(!p || p.length<2) return '{}';
7
+ const x=Number(p[0].value??0), y=Number(p[1].value??0);
8
+ return `{style:'position:absolute;left:${x}px;top:${y}px'}`;
9
+ }
10
+ function renderStmt(s, idx=0, freeform=false){
11
+ const attrs = posStyle(s, freeform);
12
+ const cls = classFor(s);
13
+ if(s.kind==='LayoutStmt'||s.kind==='ThemeStmt') return null;
14
+ if(s.kind==='UIStmt' && s.ui==='title') return `h('h1',Object.assign({class:'ck-title${cls}','data-ck-node':'${s.ui}-${idx}'},${attrs}),${expr(s.value)})`;
15
+ if(s.kind==='UIStmt' && s.ui==='text') return `h('p',Object.assign({class:'ck-text${cls}','data-ck-node':'${s.ui}-${idx}'},${attrs}),${expr(s.value)})`;
16
+ if(s.kind==='ButtonStmt') return `h('button',Object.assign({class:'ck-button${cls}','data-ck-node':'button-${idx}',onclick:()=>{${s.body.map(stmt).join('\n')} render();}},${attrs}),${expr(s.label)})`;
17
+ if(s.kind==='CardStmt') return `h('div',Object.assign({class:'ck-card${cls}','data-ck-node':'card-${idx}'},${attrs}),${s.body.map((x,i)=>renderStmt(x,i,freeform)).filter(Boolean).join(',')})`;
18
+ if(s.kind==='ContainerStmt') return `h('section',Object.assign({class:'ck-${s.ui}${cls}','data-ck-node':'${s.ui}-${idx}'},${attrs}),${s.body.map((x,i)=>renderStmt(x,i,freeform)).filter(Boolean).join(',')})`;
19
+ if(s.kind==='SpacerStmt') return `h('div',{class:'ck-spacer',style:'height:'+${expr(s.size)}+'px'})`;
20
+ if(s.kind==='ListStmt') return `h('div',Object.assign({class:'ck-list${cls}','data-ck-node':'list-${idx}'},${attrs}), (${expr(s.source)}||[]).map((${s.item},__i)=>h('div',{class:'ck-list-row'},${s.body.map((x,i)=>renderStmt(x,i,freeform)).filter(Boolean).join(',')})))`;
21
+ if(s.kind==='ToggleStmt') return `h('label',Object.assign({class:'ck-toggle${cls}','data-ck-node':'toggle-${idx}'},${attrs}),h('input',{type:'checkbox',checked:${expr(s.value)}?'checked':null}),h('span',{},${expr(s.value)}?'Done':'Open'))`;
22
+ if(s.kind==='InputStmt') return `h('label',Object.assign({class:'ck-field${cls}','data-ck-node':'input-${idx}'},${attrs}),h('span',{},${expr(s.label)}),h('input',{placeholder:${expr(s.label)}}))`;
23
+ if(s.kind==='PrintStmt') return `h('pre',{class:'ck-console'},${expr(s.value)})`;
24
+ return `h('pre',{class:'ck-unknown'},${JSON.stringify(s.kind)})`;
25
+ }
26
+ function storeInit(d){ return `let ${d.name} = CK.load(${JSON.stringify(d.name)}, ${expr(d.value)});`; }
27
+ export function emitWeb(ast){
28
+ const screen=ast.declarations.find(d=>d.kind==='ScreenDecl') || {name:'Home',body:[]};
29
+ const stores=ast.declarations.filter(d=>d.kind==='StoreDecl');
30
+ const layout=(screen.body.find(x=>x.kind==='LayoutStmt')?.mode || 'Stack');
31
+ const freeform=/^(Freeform|Canvas)$/i.test(layout);
32
+ const body=screen.body.map((x,i)=>renderStmt(x,i,freeform)).filter(Boolean).join(',');
33
+ const js=`const appName=${JSON.stringify(ast.app||'ClearKrypt')};\nconst CK={load:(k,d)=>{try{return JSON.parse(localStorage.getItem('ck:'+k))??d}catch{return d}}, save:(k,v)=>localStorage.setItem('ck:'+k,JSON.stringify(v)), newId:()=>crypto.randomUUID?.()||String(Date.now()), say:(m)=>alert(m)};\nwindow.newId=CK.newId; window.say=CK.say;\n${stores.map(storeInit).join('\n')}\nfunction h(tag,props,...kids){const e=document.createElement(tag);for(const[k,v]of Object.entries(props||{})){if(v==null||v===false)continue;if(k==='style')e.setAttribute('style',v);else if(k.startsWith('on'))e.addEventListener(k.slice(2),v);else if(k==='class')e.className=v;else e.setAttribute(k,v)};for(const kid of kids.flat(99)){e.append(kid?.nodeType?kid:document.createTextNode(kid??''))}return e}\nconst root=document.getElementById('app');\nfunction render(){${stores.map(d=>`CK.save(${JSON.stringify(d.name)}, ${d.name});`).join('\n')} root.innerHTML=''; root.append(h('main',{class:'ck-shell ck-layout-${layout.toLowerCase()} ${freeform?'ck-freeform':''}'},${body}));}\nrender();`;
34
+ const css=`:root{font-family:Inter,ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif;background:#060912;color:#f8fbff;--ck-bg:#060912;--ck-panel:rgba(255,255,255,.08);--ck-border:rgba(255,255,255,.14);--ck-text:#f8fbff;--ck-muted:rgba(248,251,255,.72);--ck-accent:#15d5ff;--ck-accent2:#8c45ff;--ck-action:#ff970e}*{box-sizing:border-box}body{margin:0;background:radial-gradient(circle at 12% 8%,rgba(21,213,255,.18),transparent 34%),radial-gradient(circle at 90% 0%,rgba(140,69,255,.16),transparent 34%),var(--ck-bg)}.ck-shell{width:min(1120px,calc(100vw - 32px));margin:0 auto;padding:48px 0;display:flex;flex-direction:column;gap:18px;min-height:100vh}.ck-layout-stack>*{position:static!important}.ck-layout-dashboard{display:grid;grid-template-columns:repeat(12,1fr);align-items:start}.ck-layout-dashboard>.ck-title,.ck-layout-dashboard>.ck-text{grid-column:1/-1}.ck-layout-dashboard>.ck-card,.ck-layout-dashboard>.ck-list{grid-column:span 6}.ck-freeform{position:relative;display:block;width:100%;min-height:720px;padding:40px}.ck-title{font-size:clamp(38px,7vw,76px);line-height:.94;letter-spacing:-.07em;margin:0 0 8px}.ck-text{font-size:clamp(18px,2vw,24px);line-height:1.45;color:var(--ck-muted);max-width:760px;margin:0}.ck-button{align-self:flex-start;border:0;border-radius:18px;padding:15px 22px;background:linear-gradient(135deg,var(--ck-action),#ffb347);color:#111827;font-weight:900;font-size:17px;box-shadow:0 18px 42px rgba(255,151,14,.22);cursor:pointer}.ck-card,.ck-section,.ck-column,.ck-list-row{border:1px solid var(--ck-border);background:linear-gradient(135deg,rgba(255,255,255,.12),rgba(255,255,255,.045));backdrop-filter:blur(26px);border-radius:28px;padding:24px;box-shadow:0 24px 80px rgba(0,0,0,.35)}.ck-section,.ck-column{display:flex;flex-direction:column;gap:14px}.ck-row{display:flex;gap:14px;align-items:center;flex-wrap:wrap}.ck-list{display:flex;flex-direction:column;gap:12px}.ck-list-row{display:flex;gap:16px;align-items:center;justify-content:space-between}.ck-toggle{display:inline-flex;gap:10px;align-items:center;color:var(--ck-muted);font-weight:700}.ck-field{display:flex;flex-direction:column;gap:8px;font-weight:800}.ck-field input{border-radius:14px;border:1px solid var(--ck-border);background:rgba(0,0,0,.22);color:white;padding:12px 14px}.ck-console,.ck-unknown{padding:16px;border-radius:16px;background:#03050b;color:#b8c7ff;overflow:auto}@media(max-width:760px){.ck-shell{padding:28px 0}.ck-layout-dashboard{display:flex;flex-direction:column}.ck-button{width:100%}}`;
35
+ const html=`<!doctype html><html><head><meta charset=utf-8><meta name=viewport content='width=device-width,initial-scale=1'><title>${ast.app||'ClearKrypt'}</title><meta name=description content='${ast.app||'ClearKrypt'} app generated by ClearKrypt'><link rel=stylesheet href=style.css></head><body><div id=app></div><script type=module src=main.js></script></body></html>`;
36
+ return {target:'web', meta:ast.meta, outputFiles:{'index.html':html,'main.js':js,'style.css':css}};
37
+ }
@@ -0,0 +1,8 @@
1
+ import { emitJS } from './js.js';
2
+ export function emitWorker(ast){
3
+ const js = `export default { async fetch(request, env, ctx){ return new Response(JSON.stringify({ ok:true, app:${JSON.stringify(ast.app||'ClearKrypt')}, runtime:'ClearKrypt Worker' }), {headers:{'content-type':'application/json'}}); } }`;
4
+ return {target:'worker', meta:ast.meta, outputFiles:{'worker.js':js, 'wrangler.toml':`name = "${(ast.app||'clearkrypt-app').toLowerCase().replace(/[^a-z0-9-]/g,'-')}"
5
+ main = "worker.js"
6
+ compatibility_date = "2026-05-18"
7
+ `}};
8
+ }
package/src/vm.js ADDED
@@ -0,0 +1,5 @@
1
+ export class CKVM{
2
+ constructor(){ this.screens=[]; }
3
+ evalExpr(e){ if(!e) return undefined; if(e.kind==='Literal') return e.value; if(e.kind==='Identifier') return undefined; return `[${e.kind}]`; }
4
+ async run(program){ for(const ins of program){ if(ins.op==='SCREEN') console.log(`Screen: ${ins.name}`); if(ins.op==='UI') console.log(`${ins.ui}: ${this.evalExpr(ins.value)}`); if(ins.op==='PRINT') console.log(this.evalExpr(ins.value)); } }
5
+ }
@@ -0,0 +1,2 @@
1
+ // ClearKrypt Standard Library: Cloud
2
+ // Built-in cloud abstractions: collection, document, query, callable, queue, schedule, secret, env, storage, pubsub, email, push, sms.
package/stdlib/data.ck ADDED
@@ -0,0 +1,2 @@
1
+ // ClearKrypt Standard Library: Data
2
+ // Built-in data tools: schema, validate, migrate, seed, index, relationship, policy, auditLog, sync, cache.
package/stdlib/ui.ck ADDED
@@ -0,0 +1,2 @@
1
+ // ClearKrypt Standard Library: UI
2
+ // Built-in visual primitives: screen, shell, stack, grid, card, glassCard, text, title, button, input, form, table, chart, map, modal, drawer, tabs, route, theme, animation.
package/testapp.ck ADDED
@@ -0,0 +1,35 @@
1
+ app "ClearKrypt Test App"
2
+
3
+ use ui
4
+ use data
5
+ use storage
6
+
7
+ type Task {
8
+ id: Text
9
+ title: Text
10
+ done: Boolean
11
+ }
12
+
13
+ store local tasks as List<Task> default [
14
+ Task { id: "1", title: "Learn ClearKrypt basics", done: false },
15
+ Task { id: "2", title: "Build a styled app", done: false }
16
+ ]
17
+
18
+ screen Home {
19
+ layout Stack
20
+ title "ClearKrypt Test App"
21
+ text "A light app to test automatic layout, local data, actions, and styling."
22
+ button "Add Test Task" {
23
+ add Task {
24
+ id: newId()
25
+ title: "New task from ClearKrypt"
26
+ done: false
27
+ } to tasks
28
+ }
29
+ list tasks as task {
30
+ card {
31
+ text task.title
32
+ toggle task.done
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,9 @@
1
+ import assert from 'node:assert';
2
+ import { compileSource } from '../src/index.js';
3
+ const source = `app "Test"
4
+ screen Home { title "Hi" text "There" }`;
5
+ const web = await compileSource(source,{target:'web'});
6
+ assert(web.outputFiles['index.html'].includes('Test'));
7
+ const vm = await compileSource(source,{target:'vm'});
8
+ assert(vm.program.length > 0);
9
+ console.log('✓ smoke tests passed');
@@ -0,0 +1,2 @@
1
+ import fs from 'node:fs';
2
+ console.log(fs.readFileSync(new URL('../course/duolingo_course.md', import.meta.url),'utf8'));
@@ -0,0 +1 @@
1
+ <!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>ClearKrypt Visual Studio</title><link rel="stylesheet" href="studio.css"></head><body><header><div><h1>ClearKrypt Visual Studio</h1><p>Drag visual nodes. The ClearKrypt code updates with @pos(x, y) layout metadata.</p></div><div><button id="copy">Copy Code</button><button id="download">Download .ck</button></div></header><main><section id="stage" aria-label="drag canvas"></section><aside><h2>Source</h2><textarea id="code" spellcheck="false"></textarea><p id="status">Ready</p></aside></main><script type="module" src="studio.js"></script></body></html>
@@ -0,0 +1 @@
1
+ :root{font-family:Inter,system-ui,sans-serif;color:#f8fafc;background:#070a12}*{box-sizing:border-box}body{margin:0}header{display:flex;justify-content:space-between;gap:24px;align-items:center;padding:24px 32px;border-bottom:1px solid rgba(255,255,255,.12);background:linear-gradient(135deg,#111827,#0f172a)}h1{margin:0;font-size:28px}p{opacity:.76}button{border:0;border-radius:14px;padding:12px 16px;font-weight:800;background:#ff8a00;color:#101010;margin-left:10px}main{display:grid;grid-template-columns:minmax(520px,1fr) 460px;min-height:calc(100vh - 112px)}#stage{position:relative;overflow:auto;background:radial-gradient(circle at 30% 10%,rgba(255,138,0,.2),transparent 30%),linear-gradient(135deg,#090d16,#121826);background-size:auto,auto;padding:24px}aside{border-left:1px solid rgba(255,255,255,.12);padding:18px;background:#0b1020}textarea{width:100%;height:calc(100vh - 230px);resize:none;border:1px solid rgba(255,255,255,.16);border-radius:18px;background:#050812;color:#e5e7eb;padding:16px;font:14px/1.5 ui-monospace,Consolas,monospace}.ck-node{position:absolute;min-width:220px;max-width:320px;cursor:grab;user-select:none;border:1px solid rgba(255,255,255,.18);border-radius:24px;background:linear-gradient(135deg,rgba(255,255,255,.18),rgba(255,255,255,.06));box-shadow:0 22px 60px rgba(0,0,0,.42);backdrop-filter:blur(22px);padding:18px}.ck-node:active{cursor:grabbing}.ck-node strong{display:block;color:#ffb24d;text-transform:uppercase;letter-spacing:.08em;font-size:12px}.ck-node span{display:block;margin:8px 0 14px}.ck-node small{opacity:.5}.ck-node.button{border-color:rgba(255,138,0,.45)}.ck-node.card{min-height:110px;border-style:dashed}@media(max-width:900px){main{grid-template-columns:1fr}aside{border-left:0;border-top:1px solid rgba(255,255,255,.12)}}
@@ -0,0 +1,52 @@
1
+ async function loadSource(){
2
+ try { return await (await fetch('./source.ck')).text(); }
3
+ catch { return localStorage.getItem('ck-source') || '// Paste ClearKrypt source here'; }
4
+ }
5
+ function parseVisualNodes(src){
6
+ const lines = src.split(/\r?\n/);
7
+ const nodes=[];
8
+ let blockDepth=0;
9
+ for(let i=0;i<lines.length;i++){
10
+ const line=lines[i];
11
+ const m=line.match(/^(\s*)(title|text|button|card)\b(.*)$/);
12
+ if(m){
13
+ const layout=line.match(/@pos\(([-\d.]+),\s*([-\d.]+)\)/);
14
+ nodes.push({id:`line-${i+1}`, line:i, kind:m[2], raw:line, indent:m[1], text:m[3].replace(/@pos\([^)]*\)/,'').trim(), x:layout?Number(layout[1]):40+(nodes.length%3)*260, y:80+Math.floor(nodes.length/3)*140});
15
+ }
16
+ blockDepth += (line.match(/{/g)||[]).length - (line.match(/}/g)||[]).length;
17
+ }
18
+ return nodes;
19
+ }
20
+ function writePosition(src,node,x,y){
21
+ const lines=src.split(/\r?\n/); let line=lines[node.line];
22
+ const tag=` @pos(${Math.round(x)}, ${Math.round(y)})`;
23
+ if(/@pos\([^)]*\)/.test(line)) line=line.replace(/@pos\([^)]*\)/, tag.trim());
24
+ else line=line.replace(/(\s*(?:title|text|button|card)\b.*?)(\s*\{?\s*)$/, `$1${tag}$2`);
25
+ lines[node.line]=line; return lines.join('\n');
26
+ }
27
+ function makeEl(node){
28
+ const el=document.createElement('div');
29
+ el.className='ck-node '+node.kind;
30
+ el.style.left=node.x+'px'; el.style.top=node.y+'px';
31
+ el.innerHTML=`<strong>${node.kind}</strong><span>${node.text.replace(/[<>&]/g,c=>({'<':'&lt;','>':'&gt;','&':'&amp;'}[c]))}</span><small>${node.id}</small>`;
32
+ el.dataset.id=node.id;
33
+ return el;
34
+ }
35
+ let source='', nodes=[];
36
+ const stage=document.querySelector('#stage');
37
+ const code=document.querySelector('#code');
38
+ const status=document.querySelector('#status');
39
+ function render(){
40
+ stage.innerHTML=''; nodes=parseVisualNodes(source); code.value=source;
41
+ for(const n of nodes){
42
+ const el=makeEl(n); stage.append(el);
43
+ let drag=null;
44
+ el.addEventListener('pointerdown',e=>{ drag={x:e.clientX,y:e.clientY,l:n.x,t:n.y,node:n,el}; el.setPointerCapture(e.pointerId); });
45
+ el.addEventListener('pointermove',e=>{ if(!drag)return; n.x=drag.l+e.clientX-drag.x; n.y=drag.t+e.clientY-drag.y; el.style.left=n.x+'px'; el.style.top=n.y+'px'; });
46
+ el.addEventListener('pointerup',()=>{ if(!drag)return; source=writePosition(source,drag.node,n.x,n.y); code.value=source; localStorage.setItem('ck-source',source); status.textContent=`Updated ${n.kind} position in code`; drag=null; });
47
+ }
48
+ }
49
+ code.addEventListener('input',()=>{ source=code.value; localStorage.setItem('ck-source',source); render(); });
50
+ document.querySelector('#copy').onclick=async()=>{ await navigator.clipboard.writeText(code.value); status.textContent='Copied updated ClearKrypt code'; };
51
+ document.querySelector('#download').onclick=()=>{ const a=document.createElement('a'); a.href=URL.createObjectURL(new Blob([code.value],{type:'text/plain'})); a.download='updated.ck'; a.click(); };
52
+ source=await loadSource(); render();