pyfrilet 0.2.0 → 0.2.1

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.
package/package.json CHANGED
@@ -1,15 +1,17 @@
1
1
  {
2
2
  "name": "pyfrilet",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
+ "type": "module",
4
5
  "main": "pyfrilet.js",
5
6
  "files": [
6
7
  "pyfrilet.js",
7
8
  "pyfrilet.min.js"
8
9
  ],
9
10
  "scripts": {
10
- "build": "terser pyfrilet.js -o pyfrilet.min.js --compress --mangle"
11
+ "build": "node build.js",
12
+ "prepublishOnly": "npm run build"
11
13
  },
12
14
  "devDependencies": {
13
- "terser": "^5.46.0"
15
+ "terser": "^5.0.0"
14
16
  }
15
17
  }
package/pyfrilet.js CHANGED
@@ -886,7 +886,7 @@ sys.modules["p5"] = m
886
886
  }
887
887
 
888
888
  /* ─────────────────── DOWNLOAD ───────────────── */
889
- const PYFRILET_CDN = 'https://cdn.jsdelivr.net/npm/pyfrilet@latest/pyfrilet.min.js';
889
+ const PYFRILET_CDN = 'https://cdn.jsdelivr.net/npm/pyfrilet@0.2.1/pyfrilet.min.js';
890
890
 
891
891
  const STANDALONE_TEMPLATE = `<!doctype html>
892
892
  <html lang="fr">
package/pyfrilet.min.js CHANGED
@@ -1 +1 @@
1
- !function(){"use strict";const e="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.4/p5.min.js",n="https://cdn.jsdelivr.net/pyodide/v0.26.4/full/pyodide.js",t="https://cdnjs.cloudflare.com/ajax/libs/ace/1.36.5/ace.min.js",i="https://cdnjs.cloudflare.com/ajax/libs/ace/1.36.5/mode-python.min.js",o="https://cdnjs.cloudflare.com/ajax/libs/ace/1.36.5/theme-monokai.min.js",s="https://cdnjs.cloudflare.com/ajax/libs/ace/1.36.5/ext-language_tools.min.js",a="https://cdnjs.cloudflare.com/ajax/libs/ace/1.36.5/ext-searchbox.min.js",r="\nhtml, body {\n height: 100%; margin: 0; overflow: hidden;\n background: #111;\n}\n#pf-root {\n position: fixed; inset: 0;\n display: flex; flex-direction: column;\n font-family: ui-monospace, 'Cascadia Code', 'Fira Code', monospace;\n}\n\n/* ── app area ── */\n#pf-app {\n flex: 1; min-height: 0;\n position: relative;\n background: #111;\n display: flex; align-items: center; justify-content: center;\n overflow: hidden;\n}\n#pf-viewport {\n transform-origin: 50% 50%;\n will-change: transform;\n}\n#pf-viewport canvas {\n display: block;\n outline: none;\n}\n#pf-loader {\n position: absolute; inset: 0;\n display: flex; flex-direction: column;\n align-items: center; justify-content: center;\n gap: 14px;\n background: #111;\n color: #565f89;\n font-size: 13px;\n z-index: 50;\n pointer-events: none;\n}\n#pf-loader-bar {\n width: 160px; height: 2px;\n background: #2a2c3e;\n border-radius: 2px;\n overflow: hidden;\n}\n#pf-loader-bar::after {\n content: '';\n display: block;\n height: 100%;\n width: 40%;\n background: #7aa2f7;\n border-radius: 2px;\n animation: pf-slide 1.2s ease-in-out infinite;\n}\n@keyframes pf-slide {\n 0% { transform: translateX(-100%); }\n 100% { transform: translateX(350%); }\n}\n\n/* ── drawer (slide-up editor panel) ── */\n#pf-drawer {\n flex-shrink: 0;\n display: flex;\n flex-direction: column;\n background: #1a1b26;\n height: 32px; /* collapsed = handle only */\n transition: height 0.26s cubic-bezier(.4, 0, .2, 1);\n overflow: hidden;\n /* shadow cast upward onto the app */\n box-shadow: 0 -4px 20px rgba(0,0,0,.55);\n}\n#pf-drawer.pf-open {\n height: var(--pf-drawer-h, 56vh);\n}\n\n/* ── handle bar ── */\n#pf-handle {\n height: 32px;\n min-height: 32px;\n display: flex;\n align-items: center;\n padding: 0 8px 0 6px;\n background: #24283b;\n border-top: 1px solid #3d4166;\n cursor: ns-resize;\n user-select: none;\n gap: 6px;\n flex-shrink: 0;\n}\n/* grip zone: clickable to toggle, draggable to resize */\n#pf-grip {\n display: flex;\n flex-direction: column;\n gap: 3px;\n padding: 5px 6px;\n flex-shrink: 0;\n opacity: .5;\n border-radius: 4px;\n transition: opacity .15s, background .15s;\n cursor: pointer;\n}\n#pf-grip:hover { opacity: .85; background: rgba(255,255,255,.06); }\n#pf-grip span {\n display: block;\n width: 16px; height: 2px;\n background: #a9b1d6;\n border-radius: 1px;\n}\n#pf-handle-hint {\n flex: 1;\n color: #565f89;\n font-size: 10px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n#pf-handle-btns {\n display: flex;\n gap: 4px;\n flex-shrink: 0;\n}\n.pf-btn {\n height: 26px;\n min-width: 26px;\n padding: 0 5px;\n border: 0; border-radius: 5px;\n cursor: pointer;\n display: flex; align-items: center; justify-content: center;\n font-size: 13px; line-height: 1;\n white-space: nowrap;\n transition: background .15s, transform .1s, opacity .15s;\n outline: none;\n box-sizing: border-box;\n}\n.pf-btn:active { transform: scale(.88); }\n.pf-btn:focus-visible { outline: 2px solid #7aa2f7; outline-offset: 1px; }\n\n#pf-btn-run { background: #1a6b3a; color: #9ece6a; font-size: 11px; }\n#pf-btn-run:hover { background: #1f8447; color: #b9f27a; }\n#pf-btn-run.pf-running { opacity: .5; cursor: not-allowed; }\n\n#pf-btn-code { background: #2a2c3e; color: #7aa2f7; font-size: 14px; }\n#pf-btn-code:hover { background: #3d4166; color: #c0caf5; }\n#pf-btn-code.pf-active { background: #3d4166; color: #e0af68; }\n\n#pf-btn-dl { background: #2a2c3e; color: #9d7cd8; font-size: 14px; }\n#pf-btn-dl:hover { background: #3d4166; color: #bb9af7; }\n\n#pf-btn-reset { background: #2a2c3e; color: #e0af68; font-size: 16px; }\n#pf-btn-reset:hover { background: #3d4166; color: #ffc777; }\n\n/* ── editor area inside drawer ── */\n#pf-editor-wrap {\n flex: 1;\n min-height: 80px;\n position: relative;\n}\n#pf-ace { position: absolute; inset: 0; }\n\n/* ── error panel (below editor, never overlaps ACE) ── */\n#pf-err {\n flex-shrink: 0;\n max-height: 120px;\n overflow: auto;\n margin: 0; padding: 8px 13px;\n font-size: 11.5px; line-height: 1.45;\n background: rgba(13, 3, 3, .95);\n color: #f7768e;\n white-space: pre-wrap;\n display: none;\n border-top: 1px solid rgba(247, 118, 142, .35);\n}\n",d='\n<div id="pf-root">\n <div id="pf-app">\n <div id="pf-viewport"><div id="pf-sketch"></div></div>\n <div id="pf-loader">\n <span id="pf-loader-msg">Chargement…</span>\n <div id="pf-loader-bar"></div>\n </div>\n </div>\n <div id="pf-drawer">\n <div id="pf-handle">\n <div id="pf-grip" title="Clic → ouvrir/fermer"><span></span><span></span><span></span></div>\n <span id="pf-handle-hint">Clic ☰ → ouvrir/fermer &nbsp;·&nbsp; Shift+Entrée → relancer</span>\n <div id="pf-handle-btns">\n <button class="pf-btn" id="pf-btn-run" title="Relancer (Shift+Entrée)">&#9654;</button>\n <button class="pf-btn" id="pf-btn-code" title="Éditeur plein écran">&#9999;&#xFE0F;</button>\n <button class="pf-btn" id="pf-btn-dl" title="Télécharger HTML autonome">&#128190;</button>\n <button class="pf-btn" id="pf-btn-help" title="Aide">?</button>\n <button class="pf-btn" id="pf-btn-reset" title="Réinitialiser le code (Ctrl+R)">&#8635;</button>\n </div>\n </div>\n <div id="pf-editor-wrap">\n <div id="pf-ace"></div>\n </div>\n <pre id="pf-err"></pre>\n </div>\n</div>\n';document.addEventListener("DOMContentLoaded",function(){const l=document.querySelector('script[type="text/python"]')||document.querySelector("python");if(!l)return void console.warn('[pyfrilet] No <script type="text/python"> or <python> tag found.');const c=(l.getAttribute("data-sources")||l.getAttribute("sources")||"local").toLowerCase().trim(),p=(l.getAttribute("data-vendor")||l.getAttribute("vendor")||"vendor/").replace(/\/?$/,"/"),u="cdn"===c?{p5:e,pyodide:n,pyodideIndex:null,ace:t,acePython:i,aceMonokai:o,aceLangTools:s,aceSearchbox:a}:{p5:p+"p5.min.js",pyodide:p+"pyodide/pyodide.js",pyodideIndex:p+"pyodide/",ace:p+"ace.min.js",acePython:p+"mode-python.min.js",aceMonokai:p+"theme-monokai.min.js",aceLangTools:p+"ext-language_tools.min.js",aceSearchbox:p+"ext-searchbox.min.js"},f=l.textContent.replace(/^\n/,""),m="pyfrilet:"+location.pathname,h=(()=>{try{return localStorage.getItem(m)}catch(e){return null}})();!function(e,n,t,i){const o=document.createElement("style");o.textContent=r,document.head.appendChild(o),document.body.innerHTML=d;const s=document.getElementById("pf-app"),a=document.getElementById("pf-drawer"),l=document.getElementById("pf-handle"),c=document.getElementById("pf-sketch"),p=document.getElementById("pf-viewport"),u=document.getElementById("pf-loader"),f=document.getElementById("pf-loader-msg"),m=document.getElementById("pf-err"),h=document.getElementById("pf-btn-run"),y=document.getElementById("pf-btn-code"),_=document.getElementById("pf-btn-dl"),g=document.getElementById("pf-btn-reset"),b=document.getElementById("pf-btn-help"),v=document.getElementById("pf-grip"),x=document.getElementById("pf-handle-hint");let w=!1,k=Math.round(.56*window.innerHeight);function E(){document.documentElement.style.setProperty("--pf-drawer-h",k+"px")}function L(){w=!0,a.classList.add("pf-open"),y.classList.add("pf-active"),setTimeout(()=>{K(),W&&W.focus()},280)}function C(){w=!1,a.classList.remove("pf-open"),y.classList.remove("pf-active"),setTimeout(()=>{K();const e=H._p?.canvas;e?(e.setAttribute("tabindex","0"),e.focus()):s.focus()},280)}function S(){w?C():L()}E();let j=null;const z=5,R=120,P=document.createElement("div");function I(e){if(e.target.closest(".pf-btn"))return;if(e.target.closest("#pf-grip"))return;const n=e.touches?e.touches[0].clientY:e.clientY;j={y:n,h:w?k:0,moved:!1},P.style.display="block",document.body.style.userSelect="none",e.cancelable&&e.preventDefault(),e.stopPropagation()}function M(e){if(!j)return;const n=e.touches?e.touches[0].clientY:e.clientY,t=j.y-n;if(Math.abs(t)>z&&(j.moved=!0),!j.moved)return;const i=Math.max(0,Math.min(window.innerHeight-50,j.h+t));i<R?(a.style.transition="none",a.style.height="32px"):(k=i,E(),w||L(),a.style.transition="none",a.style.height=k+"px"),K()}function T(e){if(!j)return;const n=j.moved,t=(e.changedTouches?e.changedTouches[0].clientY:e.clientY)??j.y,i=j.y-t,o=j.h+i;j=null,P.style.display="none",document.body.style.userSelect="",a.style.transition="",a.style.height="",n&&(o<R?C():(k=Math.max(R,Math.min(window.innerHeight-50,o)),E(),w||L()),K())}Object.assign(P.style,{position:"fixed",inset:"0",zIndex:"9999",cursor:"ns-resize",display:"none"}),document.body.appendChild(P),v.addEventListener("click",e=>{e.stopPropagation(),S()}),l.addEventListener("mousedown",I,!0),document.addEventListener("mousemove",M),document.addEventListener("mouseup",T),l.addEventListener("touchstart",I,{passive:!1}),document.addEventListener("touchmove",M,{passive:!0}),document.addEventListener("touchend",T);let B=0,O=0;function A(e){m.textContent=e,m.style.display="block",L()}function D(){m.textContent="",m.style.display="none"}function Y(){if(!H._p||"fit"!==H._mode)return;const e=H._w,n=H._h;if(!e||!n)return;const t=s.clientWidth,i=s.clientHeight,o=Math.min(t/e,i/n);p.style.transform=`scale(${o})`}function K(){if("fullscreen"===H._mode?H.size("max"):Y(),F&&"function"==typeof F.windowResized)try{F.windowResized()}catch(e){}W&&W.resize()}window.addEventListener("mousemove",e=>{B=e.clientX,O=e.clientY},{passive:!0}),window.addEventListener("touchmove",e=>{e.touches.length>0&&(B=e.touches[0].clientX,O=e.touches[0].clientY)},{passive:!0}),window._pfMouse=()=>{const e=H._p?H._p.canvas:null;if(!e)return[0,0];const n=e.getBoundingClientRect(),t=H._w/n.width,i=H._h/n.height;return[(B-n.left)*t,(O-n.top)*i]},window.addEventListener("resize",K);let F=null;const H=new Proxy({_p:null,_mode:"fit",_w:0,_h:0,_setP(e){this._p=e},size(e,n,t){if(!this._p)return;const i=t??void 0;"max"===e||null==e?(this._mode="fullscreen",this._w=s.clientWidth,this._h=s.clientHeight,void 0===i&&this._p.canvas?this._p.resizeCanvas(this._w,this._h):this._p.createCanvas(this._w,this._h,i),p.style.transform="scale(1)"):(this._mode="fit",this._w=Math.max(1,0|e),this._h=Math.max(1,0|n),void 0===i&&this._p.canvas?this._p.resizeCanvas(this._w,this._h):this._p.createCanvas(this._w,this._h,i),Y())},noSmooth(){this._p?.noSmooth(),this._p?.canvas&&(this._p.canvas.style.imageRendering="pixelated")},smooth(){this._p?.smooth(),this._p?.canvas&&(this._p.canvas.style.imageRendering="auto")},sketchTitle(e){x.textContent=String(e)}},{get(e,n){if(n in e)return"function"==typeof e[n]?e[n].bind(e):e[n];if(e._p&&n in e._p){const t=e._p[n];return"function"==typeof t?t.bind(e._p):t}},set:(e,n,t)=>n.startsWith("_")?(e[n]=t,!0):(e._p&&(e._p[n]=t),!0)});function U(){if(F){try{F.remove()}catch(e){}F=null}c.innerHTML="",H._p=null,H._mode="fit",H._w=0,H._h=0,p.style.transform="scale(1)",x.textContent="Shift+Entrée → relancer  ·  Échap → ouvrir/fermer",Q&&(Q.destroy(),Q=null),Z&&(Z.destroy(),Z=null),ee&&(ee.destroy(),ee=null),ne&&(ne.destroy(),ne=null)}window.p5py=H;let W=null;function V(){!i.ace.startsWith("vendor")&&i.ace.startsWith("http")||ace.config.set("basePath",i.ace.replace(/\/[^/]+$/,"/")),W=ace.edit("pf-ace"),W.session.setMode("ace/mode/python"),W.setTheme("ace/theme/monokai"),W.setValue(e,-1),W.setOptions({fontSize:"15px",showPrintMargin:!1,wrap:!1,useWorker:!1,tabSize:4,enableBasicAutocompletion:!0,enableLiveAutocompletion:!0,enableSnippets:!0}),W.commands.addCommand({name:"pfRun",bindKey:{win:"Shift-Enter",mac:"Shift-Enter"},exec:()=>{te()}}),W.commands.addCommand({name:"pfClose",bindKey:{win:"Escape",mac:"Escape"},exec:C}),W.commands.addCommand({name:"pfSave",bindKey:{win:"Ctrl-S",mac:"Command-S"},exec:X}),W.commands.addCommand({name:"pfReset",bindKey:{win:"Ctrl-R",mac:"Command-R"},exec:()=>{confirm("Réinitialiser le code ? Les modifications seront perdues.")&&(W.setValue(n,-1),te())}});let t=null;W.session.on("change",()=>{clearTimeout(t),t=setTimeout(X,350)})}function X(){try{localStorage.setItem(t,W?W.getValue():e)}catch(e){}}window.addEventListener("beforeunload",X);let N=null,J=null;async function q(){return J||(J=(async()=>{const e={};if(i.pyodideIndex&&(e.indexURL=i.pyodideIndex),N=await loadPyodide(e),N.runPython("\nimport sys, types, js\nfrom js import p5py, _pfMouse\nfrom pyodide.ffi import JsProxy\n\n# ── Python builtins that must NOT be shadowed ──────────────────────\n_BLACKLIST = frozenset({\n 'abs','all','any','bin','bool','bytes','callable','chr','compile',\n 'delattr','dict','dir','divmod','enumerate','eval','exec',\n 'filter','float','format','frozenset','getattr','globals','hasattr',\n 'hash','help','hex','id','input','int','isinstance','issubclass',\n 'iter','len','list','locals','map','max','min','next','object',\n 'oct','open','ord','pow','print','property','range','repr',\n 'reversed','round','set','setattr','slice','sorted','staticmethod',\n 'str','sum','super','tuple','type','vars','zip',\n # p5 lifecycle hooks — user defines these, we don't import them\n 'setup','draw','preload',\n})\n\n# ── Introspect a hidden dummy p5 instance ─────────────────────────\n_dummy_node = js.document.createElement('div')\n_dummy = js.p5.new(lambda _: None, _dummy_node)\n\n_p5_functions = set() # names of callable JS members\n_p5_attributes = set() # names of scalar/readable members\n\nfor _n in dir(_dummy):\n if _n.startswith('_') or _n in _BLACKLIST:\n continue\n _v = getattr(_dummy, _n)\n if isinstance(_v, JsProxy):\n if callable(_v):\n _p5_functions.add(_n)\n # non-callable JsProxy (canvas, pixels…) → skip\n else:\n _p5_attributes.add(_n)\n\n# Read real initial values now, while dummy is still alive\n_attr_init = {}\nfor _n in _p5_attributes:\n try:\n _attr_init[_n] = getattr(_dummy, _n)\n except Exception:\n _attr_init[_n] = 0\n\n_dummy.remove()\ndel _dummy, _dummy_node\n\n# ── Build module ───────────────────────────────────────────────────\nm = types.ModuleType(\"p5\")\n\n# Generic function wrapper: delegates to live p5Bridge instance\nclass _FW:\n __slots__ = ('_n',)\n def __init__(self, n): self._n = n\n def __call__(self, *a): return getattr(p5py, self._n)(*a)\n def __repr__(self): return f'<p5 function {self._n}>'\n\nfor _n in _p5_functions:\n setattr(m, _n, _FW(_n))\n\n# ── Special overrides (our bridge has custom behaviour) ────────────\n# smooth/noSmooth exist on a real p5 instance so introspection finds\n# them — but our Proxy overrides them to also toggle CSS image-rendering.\n# size and sketchTitle are pyfrilet-only: NOT on a real p5 instance,\n# so introspection misses them — add them explicitly.\nfor _n in ('size', 'sketchTitle'):\n setattr(m, _n, _FW(_n))\n _p5_functions.add(_n) # keep __all__ consistent\n\n# mouseX / mouseY: override with our accurate coordinate calculator\n# (p5's own values are wrong when a CSS-transformed parent is used)\n_MOUSE_OVERRIDE = frozenset({'mouseX', 'mouseY'})\n\n# Initial values from the dummy instance — constants like WEBGL, DEGREES,\n# LEFT_ARROW… are correct from the very first setup() call.\nfor _n in _p5_attributes:\n if _n in _MOUSE_OVERRIDE:\n setattr(m, _n, 0.0)\n else:\n setattr(m, _n, _attr_init.get(_n, 0))\n\n# Build __all__ for import * (after all explicit additions)\nm.__all__ = sorted(_p5_functions | _p5_attributes)\n\n# ── _pf_refresh: called before every event callback ───────────────\ndef _pf_refresh(ns):\n # accurate mouse coords (bypasses p5's stale CSS-transform offset)\n mx, my = _pfMouse()\n\n # update all known scalar attributes from live instance\n for _k in _p5_attributes:\n if _k in _MOUSE_OVERRIDE:\n _v = mx if _k == 'mouseX' else my\n else:\n try:\n _v = getattr(p5py, _k)\n except Exception:\n continue\n setattr(m, _k, _v)\n if _k in ns:\n ns[_k] = _v\n\nsys.modules[\"p5\"] = m\n"),W){G(N.runPython("list(m.__all__)").toJs())}})(),J)}function G(e){const n=e.map(e=>({caption:e,value:e,meta:"p5",score:1e3})),t={getCompletions(e,t,i,o,s){s(null,o.length>0?n:[])}},i=ace.require("ace/ext/language_tools");i&&Array.isArray(i.completers)&&(i.completers=i.completers.filter(e=>!0!==e._pyfrilet)),t._pyfrilet=!0,W.completers=[...W.completers||[],t]}let $=!1,Q=null,Z=null,ee=null,ne=null;async function te(){if($)return;$=!0,h.classList.add("pf-running"),D(),U(),N||(f.textContent="Initialisation de Pyodide…",u.style.display="flex");try{await q()}catch(e){return u.style.display="none",A("Erreur Pyodide : "+e),$=!1,void h.classList.remove("pf-running")}u.style.display="none";const n=W?W.getValue():e;N.globals.set("_USER_CODE",n);try{N.runPython("_ns = {}; exec(_USER_CODE, _ns, _ns)")}catch(e){return A(String(e)),$=!1,void h.classList.remove("pf-running")}let t,i,o,s;try{t=N.runPython("_ns.get('setup')"),i=N.runPython("_ns.get('draw')"),o=N.runPython("_ns.get('mousePressed')"),s=N.runPython("_ns.get('keyPressed')")}catch(e){return A(String(e)),$=!1,void h.classList.remove("pf-running")}if(!i)return A("Le script doit définir au moins une fonction draw()."),$=!1,void h.classList.remove("pf-running");const{create_proxy:a}=N.pyimport("pyodide.ffi"),r=N.runPython("_ns.get('windowResized')"),d=N.globals.get("_pf_refresh"),l=N.globals.get("_ns");Q=t?a(()=>{try{t()}catch(e){A(String(e))}}):null,Z=a(()=>{try{d(l),i()}catch(e){A(String(e)),U()}}),ee=o?a(()=>{try{d(l),o()}catch(e){A(String(e))}}):null,ne=s?a(()=>{try{d(l),s()}catch(e){A(String(e))}}):null;const p=r?a(()=>{try{r()}catch(e){A(String(e))}}):null;let m=!1;F=new p5(e=>{H._setP(e),e.setup=()=>{Q&&Q(),e.canvas||H.size(200,200),"function"==typeof e._updateMouseCoords&&e._updateMouseCoords({clientX:0,clientY:0}),e.windowResized(),m=!0},e.draw=()=>{m&&Z()},e.mousePressed=()=>{m&&ee&&ee()},e.keyPressed=()=>{m&&ne&&ne()},e.windowResized=()=>{"fullscreen"===H._mode?H.size("max"):Y(),p&&p()}},c),$=!1,h.classList.remove("pf-running")}const ie='<!doctype html>\n<html lang="fr">\n<head>\n <meta charset="utf-8">\n <meta name="viewport" content="width=device-width, initial-scale=1">\n <title>export</title>\n <script src="https://cdn.jsdelivr.net/npm/pyfrilet@latest/pyfrilet.min.js"><\/script>\n</head>\n<body>\n\n<script type="text/python" data-sources="cdn">\nFILLME-PYTHON\n<\/script>\n\n</body>\n</html>';function oe(){const n=W?W.getValue():e,t=ie.replace("FILLME-PYTHON",n),i=new Blob([t],{type:"text/html;charset=utf-8"}),o=URL.createObjectURL(i),s=Object.assign(document.createElement("a"),{href:o,download:"sketch.html"});document.body.appendChild(s),s.click(),document.body.removeChild(s),URL.revokeObjectURL(o)}h.addEventListener("click",()=>te()),y.addEventListener("click",()=>{w?C():(k=window.innerHeight-32,E(),L())}),_.addEventListener("click",oe);const se="https://codeberg.org/nopid/pyfrilet";function ae(e){return new Promise((n,t)=>{const i=document.createElement("script");i.src=e,i.onload=n,i.onerror=()=>t(new Error("Impossible de charger : "+e)),document.head.appendChild(i)})}b.addEventListener("click",()=>window.open(se,"_blank")),g.addEventListener("click",()=>{W&&confirm("Réinitialiser le code ? Les modifications seront perdues.")&&(W.setValue(n,-1),te())}),window.addEventListener("keydown",e=>{const t=w&&W&&W.isFocused&&W.isFocused();if(t||!["ArrowLeft","ArrowRight","ArrowUp","ArrowDown"].includes(e.key)){if("Enter"===e.key&&e.shiftKey)return e.preventDefault(),void te();if("Escape"===e.key)return t?void setTimeout(()=>{w&&C()},0):(e.preventDefault(),e.stopPropagation(),void(w?C():L()));if(!t)return"s"!==e.key&&"S"!==e.key||!e.ctrlKey&&!e.metaKey?"r"!==e.key&&"R"!==e.key||!e.ctrlKey&&!e.metaKey||e.altKey?void 0:(e.preventDefault(),void(W&&confirm("Réinitialiser le code ? Les modifications seront perdues.")&&(W.setValue(n,-1),te()))):(e.preventDefault(),void X())}else e.preventDefault()},!0),(async()=>{f.textContent="Chargement des dépendances…",u.style.display="flex";try{await ae(i.p5),await ae(i.ace),await ae(i.acePython),await ae(i.aceMonokai),await ae(i.aceLangTools),await ae(i.aceSearchbox),await ae(i.pyodide)}catch(e){return f.textContent="⚠ "+e.message,void(document.getElementById("pf-loader-bar").style.display="none")}V(),await te(),u.style.display="none"})()}(h&&h.trim()?h:f,f,m,u)})}();
1
+ !function(){"use strict";const e="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.4/p5.min.js",n="https://cdn.jsdelivr.net/pyodide/v0.26.4/full/pyodide.js",t="https://cdnjs.cloudflare.com/ajax/libs/ace/1.36.5/ace.min.js",i="https://cdnjs.cloudflare.com/ajax/libs/ace/1.36.5/mode-python.min.js",o="https://cdnjs.cloudflare.com/ajax/libs/ace/1.36.5/theme-monokai.min.js",s="https://cdnjs.cloudflare.com/ajax/libs/ace/1.36.5/ext-language_tools.min.js",a="https://cdnjs.cloudflare.com/ajax/libs/ace/1.36.5/ext-searchbox.min.js",r="\nhtml, body {\n height: 100%; margin: 0; overflow: hidden;\n background: #111;\n}\n#pf-root {\n position: fixed; inset: 0;\n display: flex; flex-direction: column;\n font-family: ui-monospace, 'Cascadia Code', 'Fira Code', monospace;\n}\n\n/* ── app area ── */\n#pf-app {\n flex: 1; min-height: 0;\n position: relative;\n background: #111;\n display: flex; align-items: center; justify-content: center;\n overflow: hidden;\n}\n#pf-viewport {\n transform-origin: 50% 50%;\n will-change: transform;\n}\n#pf-viewport canvas {\n display: block;\n outline: none;\n}\n#pf-loader {\n position: absolute; inset: 0;\n display: flex; flex-direction: column;\n align-items: center; justify-content: center;\n gap: 14px;\n background: #111;\n color: #565f89;\n font-size: 13px;\n z-index: 50;\n pointer-events: none;\n}\n#pf-loader-bar {\n width: 160px; height: 2px;\n background: #2a2c3e;\n border-radius: 2px;\n overflow: hidden;\n}\n#pf-loader-bar::after {\n content: '';\n display: block;\n height: 100%;\n width: 40%;\n background: #7aa2f7;\n border-radius: 2px;\n animation: pf-slide 1.2s ease-in-out infinite;\n}\n@keyframes pf-slide {\n 0% { transform: translateX(-100%); }\n 100% { transform: translateX(350%); }\n}\n\n/* ── drawer (slide-up editor panel) ── */\n#pf-drawer {\n flex-shrink: 0;\n display: flex;\n flex-direction: column;\n background: #1a1b26;\n height: 32px; /* collapsed = handle only */\n transition: height 0.26s cubic-bezier(.4, 0, .2, 1);\n overflow: hidden;\n /* shadow cast upward onto the app */\n box-shadow: 0 -4px 20px rgba(0,0,0,.55);\n}\n#pf-drawer.pf-open {\n height: var(--pf-drawer-h, 56vh);\n}\n\n/* ── handle bar ── */\n#pf-handle {\n height: 32px;\n min-height: 32px;\n display: flex;\n align-items: center;\n padding: 0 8px 0 6px;\n background: #24283b;\n border-top: 1px solid #3d4166;\n cursor: ns-resize;\n user-select: none;\n gap: 6px;\n flex-shrink: 0;\n}\n/* grip zone: clickable to toggle, draggable to resize */\n#pf-grip {\n display: flex;\n flex-direction: column;\n gap: 3px;\n padding: 5px 6px;\n flex-shrink: 0;\n opacity: .5;\n border-radius: 4px;\n transition: opacity .15s, background .15s;\n cursor: pointer;\n}\n#pf-grip:hover { opacity: .85; background: rgba(255,255,255,.06); }\n#pf-grip span {\n display: block;\n width: 16px; height: 2px;\n background: #a9b1d6;\n border-radius: 1px;\n}\n#pf-handle-hint {\n flex: 1;\n color: #565f89;\n font-size: 10px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n#pf-handle-btns {\n display: flex;\n gap: 4px;\n flex-shrink: 0;\n}\n.pf-btn {\n height: 26px;\n min-width: 26px;\n padding: 0 5px;\n border: 0; border-radius: 5px;\n cursor: pointer;\n display: flex; align-items: center; justify-content: center;\n font-size: 13px; line-height: 1;\n white-space: nowrap;\n transition: background .15s, transform .1s, opacity .15s;\n outline: none;\n box-sizing: border-box;\n}\n.pf-btn:active { transform: scale(.88); }\n.pf-btn:focus-visible { outline: 2px solid #7aa2f7; outline-offset: 1px; }\n\n#pf-btn-run { background: #1a6b3a; color: #9ece6a; font-size: 11px; }\n#pf-btn-run:hover { background: #1f8447; color: #b9f27a; }\n#pf-btn-run.pf-running { opacity: .5; cursor: not-allowed; }\n\n#pf-btn-code { background: #2a2c3e; color: #7aa2f7; font-size: 14px; }\n#pf-btn-code:hover { background: #3d4166; color: #c0caf5; }\n#pf-btn-code.pf-active { background: #3d4166; color: #e0af68; }\n\n#pf-btn-dl { background: #2a2c3e; color: #9d7cd8; font-size: 14px; }\n#pf-btn-dl:hover { background: #3d4166; color: #bb9af7; }\n\n#pf-btn-reset { background: #2a2c3e; color: #e0af68; font-size: 16px; }\n#pf-btn-reset:hover { background: #3d4166; color: #ffc777; }\n\n/* ── editor area inside drawer ── */\n#pf-editor-wrap {\n flex: 1;\n min-height: 80px;\n position: relative;\n}\n#pf-ace { position: absolute; inset: 0; }\n\n/* ── error panel (below editor, never overlaps ACE) ── */\n#pf-err {\n flex-shrink: 0;\n max-height: 120px;\n overflow: auto;\n margin: 0; padding: 8px 13px;\n font-size: 11.5px; line-height: 1.45;\n background: rgba(13, 3, 3, .95);\n color: #f7768e;\n white-space: pre-wrap;\n display: none;\n border-top: 1px solid rgba(247, 118, 142, .35);\n}\n",d='\n<div id="pf-root">\n <div id="pf-app">\n <div id="pf-viewport"><div id="pf-sketch"></div></div>\n <div id="pf-loader">\n <span id="pf-loader-msg">Chargement…</span>\n <div id="pf-loader-bar"></div>\n </div>\n </div>\n <div id="pf-drawer">\n <div id="pf-handle">\n <div id="pf-grip" title="Clic → ouvrir/fermer"><span></span><span></span><span></span></div>\n <span id="pf-handle-hint">Clic ☰ → ouvrir/fermer &nbsp;·&nbsp; Shift+Entrée → relancer</span>\n <div id="pf-handle-btns">\n <button class="pf-btn" id="pf-btn-run" title="Relancer (Shift+Entrée)">&#9654;</button>\n <button class="pf-btn" id="pf-btn-code" title="Éditeur plein écran">&#9999;&#xFE0F;</button>\n <button class="pf-btn" id="pf-btn-dl" title="Télécharger HTML autonome">&#128190;</button>\n <button class="pf-btn" id="pf-btn-help" title="Aide">?</button>\n <button class="pf-btn" id="pf-btn-reset" title="Réinitialiser le code (Ctrl+R)">&#8635;</button>\n </div>\n </div>\n <div id="pf-editor-wrap">\n <div id="pf-ace"></div>\n </div>\n <pre id="pf-err"></pre>\n </div>\n</div>\n';document.addEventListener("DOMContentLoaded",function(){const l=document.querySelector('script[type="text/python"]')||document.querySelector("python");if(!l)return void console.warn('[pyfrilet] No <script type="text/python"> or <python> tag found.');const c=(l.getAttribute("data-sources")||l.getAttribute("sources")||"local").toLowerCase().trim(),p=(l.getAttribute("data-vendor")||l.getAttribute("vendor")||"vendor/").replace(/\/?$/,"/"),u="cdn"===c?{p5:e,pyodide:n,pyodideIndex:null,ace:t,acePython:i,aceMonokai:o,aceLangTools:s,aceSearchbox:a}:{p5:p+"p5.min.js",pyodide:p+"pyodide/pyodide.js",pyodideIndex:p+"pyodide/",ace:p+"ace.min.js",acePython:p+"mode-python.min.js",aceMonokai:p+"theme-monokai.min.js",aceLangTools:p+"ext-language_tools.min.js",aceSearchbox:p+"ext-searchbox.min.js"},f=l.textContent.replace(/^\n/,""),m="pyfrilet:"+location.pathname,h=(()=>{try{return localStorage.getItem(m)}catch(e){return null}})();!function(e,n,t,i){const o=document.createElement("style");o.textContent=r,document.head.appendChild(o),document.body.innerHTML=d;const s=document.getElementById("pf-app"),a=document.getElementById("pf-drawer"),l=document.getElementById("pf-handle"),c=document.getElementById("pf-sketch"),p=document.getElementById("pf-viewport"),u=document.getElementById("pf-loader"),f=document.getElementById("pf-loader-msg"),m=document.getElementById("pf-err"),h=document.getElementById("pf-btn-run"),y=document.getElementById("pf-btn-code"),_=document.getElementById("pf-btn-dl"),g=document.getElementById("pf-btn-reset"),b=document.getElementById("pf-btn-help"),v=document.getElementById("pf-grip"),x=document.getElementById("pf-handle-hint");let w=!1,k=Math.round(.56*window.innerHeight);function E(){document.documentElement.style.setProperty("--pf-drawer-h",k+"px")}function L(){w=!0,a.classList.add("pf-open"),y.classList.add("pf-active"),setTimeout(()=>{K(),W&&W.focus()},280)}function C(){w=!1,a.classList.remove("pf-open"),y.classList.remove("pf-active"),setTimeout(()=>{K();const e=H._p?.canvas;e?(e.setAttribute("tabindex","0"),e.focus()):s.focus()},280)}function S(){w?C():L()}E();let j=null;const z=5,R=120,P=document.createElement("div");function I(e){if(e.target.closest(".pf-btn"))return;if(e.target.closest("#pf-grip"))return;const n=e.touches?e.touches[0].clientY:e.clientY;j={y:n,h:w?k:0,moved:!1},P.style.display="block",document.body.style.userSelect="none",e.cancelable&&e.preventDefault(),e.stopPropagation()}function M(e){if(!j)return;const n=e.touches?e.touches[0].clientY:e.clientY,t=j.y-n;if(Math.abs(t)>z&&(j.moved=!0),!j.moved)return;const i=Math.max(0,Math.min(window.innerHeight-50,j.h+t));i<R?(a.style.transition="none",a.style.height="32px"):(k=i,E(),w||L(),a.style.transition="none",a.style.height=k+"px"),K()}function T(e){if(!j)return;const n=j.moved,t=(e.changedTouches?e.changedTouches[0].clientY:e.clientY)??j.y,i=j.y-t,o=j.h+i;j=null,P.style.display="none",document.body.style.userSelect="",a.style.transition="",a.style.height="",n&&(o<R?C():(k=Math.max(R,Math.min(window.innerHeight-50,o)),E(),w||L()),K())}Object.assign(P.style,{position:"fixed",inset:"0",zIndex:"9999",cursor:"ns-resize",display:"none"}),document.body.appendChild(P),v.addEventListener("click",e=>{e.stopPropagation(),S()}),l.addEventListener("mousedown",I,!0),document.addEventListener("mousemove",M),document.addEventListener("mouseup",T),l.addEventListener("touchstart",I,{passive:!1}),document.addEventListener("touchmove",M,{passive:!0}),document.addEventListener("touchend",T);let B=0,O=0;function A(e){m.textContent=e,m.style.display="block",L()}function D(){m.textContent="",m.style.display="none"}function Y(){if(!H._p||"fit"!==H._mode)return;const e=H._w,n=H._h;if(!e||!n)return;const t=s.clientWidth,i=s.clientHeight,o=Math.min(t/e,i/n);p.style.transform=`scale(${o})`}function K(){if("fullscreen"===H._mode?H.size("max"):Y(),F&&"function"==typeof F.windowResized)try{F.windowResized()}catch(e){}W&&W.resize()}window.addEventListener("mousemove",e=>{B=e.clientX,O=e.clientY},{passive:!0}),window.addEventListener("touchmove",e=>{e.touches.length>0&&(B=e.touches[0].clientX,O=e.touches[0].clientY)},{passive:!0}),window._pfMouse=()=>{const e=H._p?H._p.canvas:null;if(!e)return[0,0];const n=e.getBoundingClientRect(),t=H._w/n.width,i=H._h/n.height;return[(B-n.left)*t,(O-n.top)*i]},window.addEventListener("resize",K);let F=null;const H=new Proxy({_p:null,_mode:"fit",_w:0,_h:0,_setP(e){this._p=e},size(e,n,t){if(!this._p)return;const i=t??void 0;"max"===e||null==e?(this._mode="fullscreen",this._w=s.clientWidth,this._h=s.clientHeight,void 0===i&&this._p.canvas?this._p.resizeCanvas(this._w,this._h):this._p.createCanvas(this._w,this._h,i),p.style.transform="scale(1)"):(this._mode="fit",this._w=Math.max(1,0|e),this._h=Math.max(1,0|n),void 0===i&&this._p.canvas?this._p.resizeCanvas(this._w,this._h):this._p.createCanvas(this._w,this._h,i),Y())},noSmooth(){this._p?.noSmooth(),this._p?.canvas&&(this._p.canvas.style.imageRendering="pixelated")},smooth(){this._p?.smooth(),this._p?.canvas&&(this._p.canvas.style.imageRendering="auto")},sketchTitle(e){x.textContent=String(e)}},{get(e,n){if(n in e)return"function"==typeof e[n]?e[n].bind(e):e[n];if(e._p&&n in e._p){const t=e._p[n];return"function"==typeof t?t.bind(e._p):t}},set:(e,n,t)=>n.startsWith("_")?(e[n]=t,!0):(e._p&&(e._p[n]=t),!0)});function U(){if(F){try{F.remove()}catch(e){}F=null}c.innerHTML="",H._p=null,H._mode="fit",H._w=0,H._h=0,p.style.transform="scale(1)",x.textContent="Shift+Entrée → relancer  ·  Échap → ouvrir/fermer",Q&&(Q.destroy(),Q=null),Z&&(Z.destroy(),Z=null),ee&&(ee.destroy(),ee=null),ne&&(ne.destroy(),ne=null)}window.p5py=H;let W=null;function V(){!i.ace.startsWith("vendor")&&i.ace.startsWith("http")||ace.config.set("basePath",i.ace.replace(/\/[^/]+$/,"/")),W=ace.edit("pf-ace"),W.session.setMode("ace/mode/python"),W.setTheme("ace/theme/monokai"),W.setValue(e,-1),W.setOptions({fontSize:"15px",showPrintMargin:!1,wrap:!1,useWorker:!1,tabSize:4,enableBasicAutocompletion:!0,enableLiveAutocompletion:!0,enableSnippets:!0}),W.commands.addCommand({name:"pfRun",bindKey:{win:"Shift-Enter",mac:"Shift-Enter"},exec:()=>{te()}}),W.commands.addCommand({name:"pfClose",bindKey:{win:"Escape",mac:"Escape"},exec:C}),W.commands.addCommand({name:"pfSave",bindKey:{win:"Ctrl-S",mac:"Command-S"},exec:X}),W.commands.addCommand({name:"pfReset",bindKey:{win:"Ctrl-R",mac:"Command-R"},exec:()=>{confirm("Réinitialiser le code ? Les modifications seront perdues.")&&(W.setValue(n,-1),te())}});let t=null;W.session.on("change",()=>{clearTimeout(t),t=setTimeout(X,350)})}function X(){try{localStorage.setItem(t,W?W.getValue():e)}catch(e){}}window.addEventListener("beforeunload",X);let N=null,J=null;async function q(){return J||(J=(async()=>{const e={};if(i.pyodideIndex&&(e.indexURL=i.pyodideIndex),N=await loadPyodide(e),N.runPython("\nimport sys, types, js\nfrom js import p5py, _pfMouse\nfrom pyodide.ffi import JsProxy\n\n# ── Python builtins that must NOT be shadowed ──────────────────────\n_BLACKLIST = frozenset({\n 'abs','all','any','bin','bool','bytes','callable','chr','compile',\n 'delattr','dict','dir','divmod','enumerate','eval','exec',\n 'filter','float','format','frozenset','getattr','globals','hasattr',\n 'hash','help','hex','id','input','int','isinstance','issubclass',\n 'iter','len','list','locals','map','max','min','next','object',\n 'oct','open','ord','pow','print','property','range','repr',\n 'reversed','round','set','setattr','slice','sorted','staticmethod',\n 'str','sum','super','tuple','type','vars','zip',\n # p5 lifecycle hooks — user defines these, we don't import them\n 'setup','draw','preload',\n})\n\n# ── Introspect a hidden dummy p5 instance ─────────────────────────\n_dummy_node = js.document.createElement('div')\n_dummy = js.p5.new(lambda _: None, _dummy_node)\n\n_p5_functions = set() # names of callable JS members\n_p5_attributes = set() # names of scalar/readable members\n\nfor _n in dir(_dummy):\n if _n.startswith('_') or _n in _BLACKLIST:\n continue\n _v = getattr(_dummy, _n)\n if isinstance(_v, JsProxy):\n if callable(_v):\n _p5_functions.add(_n)\n # non-callable JsProxy (canvas, pixels…) → skip\n else:\n _p5_attributes.add(_n)\n\n# Read real initial values now, while dummy is still alive\n_attr_init = {}\nfor _n in _p5_attributes:\n try:\n _attr_init[_n] = getattr(_dummy, _n)\n except Exception:\n _attr_init[_n] = 0\n\n_dummy.remove()\ndel _dummy, _dummy_node\n\n# ── Build module ───────────────────────────────────────────────────\nm = types.ModuleType(\"p5\")\n\n# Generic function wrapper: delegates to live p5Bridge instance\nclass _FW:\n __slots__ = ('_n',)\n def __init__(self, n): self._n = n\n def __call__(self, *a): return getattr(p5py, self._n)(*a)\n def __repr__(self): return f'<p5 function {self._n}>'\n\nfor _n in _p5_functions:\n setattr(m, _n, _FW(_n))\n\n# ── Special overrides (our bridge has custom behaviour) ────────────\n# smooth/noSmooth exist on a real p5 instance so introspection finds\n# them — but our Proxy overrides them to also toggle CSS image-rendering.\n# size and sketchTitle are pyfrilet-only: NOT on a real p5 instance,\n# so introspection misses them — add them explicitly.\nfor _n in ('size', 'sketchTitle'):\n setattr(m, _n, _FW(_n))\n _p5_functions.add(_n) # keep __all__ consistent\n\n# mouseX / mouseY: override with our accurate coordinate calculator\n# (p5's own values are wrong when a CSS-transformed parent is used)\n_MOUSE_OVERRIDE = frozenset({'mouseX', 'mouseY'})\n\n# Initial values from the dummy instance — constants like WEBGL, DEGREES,\n# LEFT_ARROW… are correct from the very first setup() call.\nfor _n in _p5_attributes:\n if _n in _MOUSE_OVERRIDE:\n setattr(m, _n, 0.0)\n else:\n setattr(m, _n, _attr_init.get(_n, 0))\n\n# Build __all__ for import * (after all explicit additions)\nm.__all__ = sorted(_p5_functions | _p5_attributes)\n\n# ── _pf_refresh: called before every event callback ───────────────\ndef _pf_refresh(ns):\n # accurate mouse coords (bypasses p5's stale CSS-transform offset)\n mx, my = _pfMouse()\n\n # update all known scalar attributes from live instance\n for _k in _p5_attributes:\n if _k in _MOUSE_OVERRIDE:\n _v = mx if _k == 'mouseX' else my\n else:\n try:\n _v = getattr(p5py, _k)\n except Exception:\n continue\n setattr(m, _k, _v)\n if _k in ns:\n ns[_k] = _v\n\nsys.modules[\"p5\"] = m\n"),W){G(N.runPython("list(m.__all__)").toJs())}})(),J)}function G(e){const n=e.map(e=>({caption:e,value:e,meta:"p5",score:1e3})),t={getCompletions(e,t,i,o,s){s(null,o.length>0?n:[])}},i=ace.require("ace/ext/language_tools");i&&Array.isArray(i.completers)&&(i.completers=i.completers.filter(e=>!0!==e._pyfrilet)),t._pyfrilet=!0,W.completers=[...W.completers||[],t]}let $=!1,Q=null,Z=null,ee=null,ne=null;async function te(){if($)return;$=!0,h.classList.add("pf-running"),D(),U(),N||(f.textContent="Initialisation de Pyodide…",u.style.display="flex");try{await q()}catch(e){return u.style.display="none",A("Erreur Pyodide : "+e),$=!1,void h.classList.remove("pf-running")}u.style.display="none";const n=W?W.getValue():e;N.globals.set("_USER_CODE",n);try{N.runPython("_ns = {}; exec(_USER_CODE, _ns, _ns)")}catch(e){return A(String(e)),$=!1,void h.classList.remove("pf-running")}let t,i,o,s;try{t=N.runPython("_ns.get('setup')"),i=N.runPython("_ns.get('draw')"),o=N.runPython("_ns.get('mousePressed')"),s=N.runPython("_ns.get('keyPressed')")}catch(e){return A(String(e)),$=!1,void h.classList.remove("pf-running")}if(!i)return A("Le script doit définir au moins une fonction draw()."),$=!1,void h.classList.remove("pf-running");const{create_proxy:a}=N.pyimport("pyodide.ffi"),r=N.runPython("_ns.get('windowResized')"),d=N.globals.get("_pf_refresh"),l=N.globals.get("_ns");Q=t?a(()=>{try{t()}catch(e){A(String(e))}}):null,Z=a(()=>{try{d(l),i()}catch(e){A(String(e)),U()}}),ee=o?a(()=>{try{d(l),o()}catch(e){A(String(e))}}):null,ne=s?a(()=>{try{d(l),s()}catch(e){A(String(e))}}):null;const p=r?a(()=>{try{r()}catch(e){A(String(e))}}):null;let m=!1;F=new p5(e=>{H._setP(e),e.setup=()=>{Q&&Q(),e.canvas||H.size(200,200),"function"==typeof e._updateMouseCoords&&e._updateMouseCoords({clientX:0,clientY:0}),e.windowResized(),m=!0},e.draw=()=>{m&&Z()},e.mousePressed=()=>{m&&ee&&ee()},e.keyPressed=()=>{m&&ne&&ne()},e.windowResized=()=>{"fullscreen"===H._mode?H.size("max"):Y(),p&&p()}},c),$=!1,h.classList.remove("pf-running")}const ie='<!doctype html>\n<html lang="fr">\n<head>\n <meta charset="utf-8">\n <meta name="viewport" content="width=device-width, initial-scale=1">\n <title>export</title>\n <script src="https://cdn.jsdelivr.net/npm/pyfrilet@0.2.1/pyfrilet.min.js"><\/script>\n</head>\n<body>\n\n<script type="text/python" data-sources="cdn">\nFILLME-PYTHON\n<\/script>\n\n</body>\n</html>';function oe(){const n=W?W.getValue():e,t=ie.replace("FILLME-PYTHON",n),i=new Blob([t],{type:"text/html;charset=utf-8"}),o=URL.createObjectURL(i),s=Object.assign(document.createElement("a"),{href:o,download:"sketch.html"});document.body.appendChild(s),s.click(),document.body.removeChild(s),URL.revokeObjectURL(o)}h.addEventListener("click",()=>te()),y.addEventListener("click",()=>{w?C():(k=window.innerHeight-32,E(),L())}),_.addEventListener("click",oe);const se="https://codeberg.org/nopid/pyfrilet";function ae(e){return new Promise((n,t)=>{const i=document.createElement("script");i.src=e,i.onload=n,i.onerror=()=>t(new Error("Impossible de charger : "+e)),document.head.appendChild(i)})}b.addEventListener("click",()=>window.open(se,"_blank")),g.addEventListener("click",()=>{W&&confirm("Réinitialiser le code ? Les modifications seront perdues.")&&(W.setValue(n,-1),te())}),window.addEventListener("keydown",e=>{const t=w&&W&&W.isFocused&&W.isFocused();if(t||!["ArrowLeft","ArrowRight","ArrowUp","ArrowDown"].includes(e.key)){if("Enter"===e.key&&e.shiftKey)return e.preventDefault(),void te();if("Escape"===e.key)return t?void setTimeout(()=>{w&&C()},0):(e.preventDefault(),e.stopPropagation(),void(w?C():L()));if(!t)return"s"!==e.key&&"S"!==e.key||!e.ctrlKey&&!e.metaKey?"r"!==e.key&&"R"!==e.key||!e.ctrlKey&&!e.metaKey||e.altKey?void 0:(e.preventDefault(),void(W&&confirm("Réinitialiser le code ? Les modifications seront perdues.")&&(W.setValue(n,-1),te()))):(e.preventDefault(),void X())}else e.preventDefault()},!0),(async()=>{f.textContent="Chargement des dépendances…",u.style.display="flex";try{await ae(i.p5),await ae(i.ace),await ae(i.acePython),await ae(i.aceMonokai),await ae(i.aceLangTools),await ae(i.aceSearchbox),await ae(i.pyodide)}catch(e){return f.textContent="⚠ "+e.message,void(document.getElementById("pf-loader-bar").style.display="none")}V(),await te(),u.style.display="none"})()}(h&&h.trim()?h:f,f,m,u)})}();