pyfrilet 0.1.1 → 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.1.1",
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
@@ -21,10 +21,6 @@
21
21
  (function () {
22
22
  'use strict';
23
23
 
24
- /* ── capture script element immediately (for self-fetch in download) ── */
25
- const _scriptEl = document.currentScript;
26
- const _scriptSrc = _scriptEl ? (_scriptEl.src || '') : '';
27
-
28
24
  /* ═══════════════════════════ CDN URLS ═══════════════════════════════ */
29
25
  const CDN = {
30
26
  p5 : 'https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.4/p5.min.js',
@@ -890,36 +886,7 @@ sys.modules["p5"] = m
890
886
  }
891
887
 
892
888
  /* ─────────────────── DOWNLOAD ───────────────── */
893
- async function download() {
894
- btnDl.style.opacity = '.4';
895
- btnDl.style.pointerEvents = 'none';
896
-
897
- let pfSrc = '';
898
-
899
- /* 1. try fetching from original <script src="..."> */
900
- if (_scriptSrc) {
901
- try { pfSrc = await (await fetch(_scriptSrc)).text(); } catch (_) {}
902
- }
903
-
904
- /* 2. fallback: look for inline <script data-pyfrilet> (present in self-downloaded files) */
905
- if (!pfSrc) {
906
- const inlineEl = document.querySelector('script[data-pyfrilet]');
907
- if (inlineEl) pfSrc = inlineEl.textContent;
908
- }
909
-
910
- const code = aceInst ? aceInst.getValue() : initialCode;
911
- const html = buildStandaloneHTML(code, pfSrc);
912
- const blob = new Blob([html], { type: 'text/html;charset=utf-8' });
913
- const url = URL.createObjectURL(blob);
914
- const a = Object.assign(document.createElement('a'), { href: url, download: 'sketch.html' });
915
- document.body.appendChild(a);
916
- a.click();
917
- document.body.removeChild(a);
918
- URL.revokeObjectURL(url);
919
-
920
- btnDl.style.opacity = '';
921
- btnDl.style.pointerEvents = '';
922
- }
889
+ const PYFRILET_CDN = 'https://cdn.jsdelivr.net/npm/pyfrilet@0.2.1/pyfrilet.min.js';
923
890
 
924
891
  const STANDALONE_TEMPLATE = `<!doctype html>
925
892
  <html lang="fr">
@@ -927,7 +894,7 @@ sys.modules["p5"] = m
927
894
  <meta charset="utf-8">
928
895
  <meta name="viewport" content="width=device-width, initial-scale=1">
929
896
  <title>export</title>
930
- <script src="FILLME-JS"><\/script>
897
+ <script src="${PYFRILET_CDN}"><\/script>
931
898
  </head>
932
899
  <body>
933
900
 
@@ -938,13 +905,16 @@ FILLME-PYTHON
938
905
  </body>
939
906
  </html>`;
940
907
 
941
- function buildStandaloneHTML(code, pfSrc) {
942
- const b64 = pfSrc
943
- ? 'data:text/javascript;base64,' + btoa(unescape(encodeURIComponent(pfSrc)))
944
- : '/* pyfrilet source unavailable */';
945
- return STANDALONE_TEMPLATE
946
- .replace('FILLME-JS', b64)
947
- .replace('FILLME-PYTHON', code);
908
+ function download() {
909
+ const code = aceInst ? aceInst.getValue() : initialCode;
910
+ const html = STANDALONE_TEMPLATE.replace('FILLME-PYTHON', code);
911
+ const blob = new Blob([html], { type: 'text/html;charset=utf-8' });
912
+ const url = URL.createObjectURL(blob);
913
+ const a = Object.assign(document.createElement('a'), { href: url, download: 'sketch.html' });
914
+ document.body.appendChild(a);
915
+ a.click();
916
+ document.body.removeChild(a);
917
+ URL.revokeObjectURL(url);
948
918
  }
949
919
 
950
920
  /* ─────────────────── BUTTON HANDLERS ────────── */
package/pyfrilet.min.js CHANGED
@@ -1 +1 @@
1
- !function(){"use strict";const e=document.currentScript,n=e&&e.src||"",t="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.4/p5.min.js",i="https://cdn.jsdelivr.net/pyodide/v0.26.4/full/pyodide.js",o="https://cdnjs.cloudflare.com/ajax/libs/ace/1.36.5/ace.min.js",s="https://cdnjs.cloudflare.com/ajax/libs/ace/1.36.5/mode-python.min.js",a="https://cdnjs.cloudflare.com/ajax/libs/ace/1.36.5/theme-monokai.min.js",r="https://cdnjs.cloudflare.com/ajax/libs/ace/1.36.5/ext-language_tools.min.js",d="https://cdnjs.cloudflare.com/ajax/libs/ace/1.36.5/ext-searchbox.min.js",l="\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",c='\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 e=document.querySelector('script[type="text/python"]')||document.querySelector("python");if(!e)return void console.warn('[pyfrilet] No <script type="text/python"> or <python> tag found.');const p=(e.getAttribute("data-sources")||e.getAttribute("sources")||"local").toLowerCase().trim(),u=(e.getAttribute("data-vendor")||e.getAttribute("vendor")||"vendor/").replace(/\/?$/,"/"),f="cdn"===p?{p5:t,pyodide:i,pyodideIndex:null,ace:o,acePython:s,aceMonokai:a,aceLangTools:r,aceSearchbox:d}:{p5:u+"p5.min.js",pyodide:u+"pyodide/pyodide.js",pyodideIndex:u+"pyodide/",ace:u+"ace.min.js",acePython:u+"mode-python.min.js",aceMonokai:u+"theme-monokai.min.js",aceLangTools:u+"ext-language_tools.min.js",aceSearchbox:u+"ext-searchbox.min.js"},m=e.textContent.replace(/^\n/,""),h="pyfrilet:"+location.pathname,y=(()=>{try{return localStorage.getItem(h)}catch(e){return null}})();!function(e,t,i,o){const s=document.createElement("style");s.textContent=l,document.head.appendChild(s),document.body.innerHTML=c;const a=document.getElementById("pf-app"),r=document.getElementById("pf-drawer"),d=document.getElementById("pf-handle"),p=document.getElementById("pf-sketch"),u=document.getElementById("pf-viewport"),f=document.getElementById("pf-loader"),m=document.getElementById("pf-loader-msg"),h=document.getElementById("pf-err"),y=document.getElementById("pf-btn-run"),_=document.getElementById("pf-btn-code"),b=document.getElementById("pf-btn-dl"),g=document.getElementById("pf-btn-reset"),v=document.getElementById("pf-btn-help"),x=document.getElementById("pf-grip"),w=document.getElementById("pf-handle-hint");let E=!1,k=Math.round(.56*window.innerHeight);function L(){document.documentElement.style.setProperty("--pf-drawer-h",k+"px")}function S(){E=!0,r.classList.add("pf-open"),_.classList.add("pf-active"),setTimeout(()=>{K(),V&&V.focus()},280)}function C(){E=!1,r.classList.remove("pf-open"),_.classList.remove("pf-active"),setTimeout(()=>{K();const e=H._p?.canvas;e?(e.setAttribute("tabindex","0"),e.focus()):a.focus()},280)}function j(){E?C():S()}L();let R=null;const z=5,I=120,P=document.createElement("div");function M(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;R={y:n,h:E?k:0,moved:!1},P.style.display="block",document.body.style.userSelect="none",e.cancelable&&e.preventDefault(),e.stopPropagation()}function T(e){if(!R)return;const n=e.touches?e.touches[0].clientY:e.clientY,t=R.y-n;if(Math.abs(t)>z&&(R.moved=!0),!R.moved)return;const i=Math.max(0,Math.min(window.innerHeight-50,R.h+t));i<I?(r.style.transition="none",r.style.height="32px"):(k=i,L(),E||S(),r.style.transition="none",r.style.height=k+"px"),K()}function B(e){if(!R)return;const n=R.moved,t=(e.changedTouches?e.changedTouches[0].clientY:e.clientY)??R.y,i=R.y-t,o=R.h+i;R=null,P.style.display="none",document.body.style.userSelect="",r.style.transition="",r.style.height="",n&&(o<I?C():(k=Math.max(I,Math.min(window.innerHeight-50,o)),L(),E||S()),K())}Object.assign(P.style,{position:"fixed",inset:"0",zIndex:"9999",cursor:"ns-resize",display:"none"}),document.body.appendChild(P),x.addEventListener("click",e=>{e.stopPropagation(),j()}),d.addEventListener("mousedown",M,!0),document.addEventListener("mousemove",T),document.addEventListener("mouseup",B),d.addEventListener("touchstart",M,{passive:!1}),document.addEventListener("touchmove",T,{passive:!0}),document.addEventListener("touchend",B);let O=0,A=0;function D(e){h.textContent=e,h.style.display="block",S()}function F(){h.textContent="",h.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=a.clientWidth,i=a.clientHeight,o=Math.min(t/e,i/n);u.style.transform=`scale(${o})`}function K(){if("fullscreen"===H._mode?H.size("max"):Y(),U&&"function"==typeof U.windowResized)try{U.windowResized()}catch(e){}V&&V.resize()}window.addEventListener("mousemove",e=>{O=e.clientX,A=e.clientY},{passive:!0}),window.addEventListener("touchmove",e=>{e.touches.length>0&&(O=e.touches[0].clientX,A=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[(O-n.left)*t,(A-n.top)*i]},window.addEventListener("resize",K);let U=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=a.clientWidth,this._h=a.clientHeight,void 0===i&&this._p.canvas?this._p.resizeCanvas(this._w,this._h):this._p.createCanvas(this._w,this._h,i),u.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){w.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 W(){if(U){try{U.remove()}catch(e){}U=null}p.innerHTML="",H._p=null,H._mode="fit",H._w=0,H._h=0,u.style.transform="scale(1)",w.textContent="Shift+Entrée → relancer  ·  Échap → ouvrir/fermer",Z&&(Z.destroy(),Z=null),ee&&(ee.destroy(),ee=null),ne&&(ne.destroy(),ne=null),te&&(te.destroy(),te=null)}window.p5py=H;let V=null;function X(){!o.ace.startsWith("vendor")&&o.ace.startsWith("http")||ace.config.set("basePath",o.ace.replace(/\/[^/]+$/,"/")),V=ace.edit("pf-ace"),V.session.setMode("ace/mode/python"),V.setTheme("ace/theme/monokai"),V.setValue(e,-1),V.setOptions({fontSize:"15px",showPrintMargin:!1,wrap:!1,useWorker:!1,tabSize:4,enableBasicAutocompletion:!0,enableLiveAutocompletion:!0,enableSnippets:!0}),V.commands.addCommand({name:"pfRun",bindKey:{win:"Shift-Enter",mac:"Shift-Enter"},exec:()=>{ie()}}),V.commands.addCommand({name:"pfClose",bindKey:{win:"Escape",mac:"Escape"},exec:C}),V.commands.addCommand({name:"pfSave",bindKey:{win:"Ctrl-S",mac:"Command-S"},exec:J}),V.commands.addCommand({name:"pfReset",bindKey:{win:"Ctrl-R",mac:"Command-R"},exec:()=>{confirm("Réinitialiser le code ? Les modifications seront perdues.")&&(V.setValue(t,-1),ie())}});let n=null;V.session.on("change",()=>{clearTimeout(n),n=setTimeout(J,350)})}function J(){try{localStorage.setItem(i,V?V.getValue():e)}catch(e){}}window.addEventListener("beforeunload",J);let N=null,q=null;async function G(){return q||(q=(async()=>{const e={};if(o.pyodideIndex&&(e.indexURL=o.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"),V){$(N.runPython("list(m.__all__)").toJs())}})(),q)}function $(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,V.completers=[...V.completers||[],t]}let Q=!1,Z=null,ee=null,ne=null,te=null;async function ie(){if(Q)return;Q=!0,y.classList.add("pf-running"),F(),W(),N||(m.textContent="Initialisation de Pyodide…",f.style.display="flex");try{await G()}catch(e){return f.style.display="none",D("Erreur Pyodide : "+e),Q=!1,void y.classList.remove("pf-running")}f.style.display="none";const n=V?V.getValue():e;N.globals.set("_USER_CODE",n);try{N.runPython("_ns = {}; exec(_USER_CODE, _ns, _ns)")}catch(e){return D(String(e)),Q=!1,void y.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 D(String(e)),Q=!1,void y.classList.remove("pf-running")}if(!i)return D("Le script doit définir au moins une fonction draw()."),Q=!1,void y.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");Z=t?a(()=>{try{t()}catch(e){D(String(e))}}):null,ee=a(()=>{try{d(l),i()}catch(e){D(String(e)),W()}}),ne=o?a(()=>{try{d(l),o()}catch(e){D(String(e))}}):null,te=s?a(()=>{try{d(l),s()}catch(e){D(String(e))}}):null;const c=r?a(()=>{try{r()}catch(e){D(String(e))}}):null;let u=!1;U=new p5(e=>{H._setP(e),e.setup=()=>{Z&&Z(),e.canvas||H.size(200,200),"function"==typeof e._updateMouseCoords&&e._updateMouseCoords({clientX:0,clientY:0}),e.windowResized(),u=!0},e.draw=()=>{u&&ee()},e.mousePressed=()=>{u&&ne&&ne()},e.keyPressed=()=>{u&&te&&te()},e.windowResized=()=>{"fullscreen"===H._mode?H.size("max"):Y(),c&&c()}},p),Q=!1,y.classList.remove("pf-running")}async function oe(){b.style.opacity=".4",b.style.pointerEvents="none";let t="";if(n)try{t=await(await fetch(n)).text()}catch(e){}if(!t){const e=document.querySelector("script[data-pyfrilet]");e&&(t=e.textContent)}const i=ae(V?V.getValue():e,t),o=new Blob([i],{type:"text/html;charset=utf-8"}),s=URL.createObjectURL(o),a=Object.assign(document.createElement("a"),{href:s,download:"sketch.html"});document.body.appendChild(a),a.click(),document.body.removeChild(a),URL.revokeObjectURL(s),b.style.opacity="",b.style.pointerEvents=""}const se='<!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="FILLME-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 ae(e,n){const t=n?"data:text/javascript;base64,"+btoa(unescape(encodeURIComponent(n))):"/* pyfrilet source unavailable */";return se.replace("FILLME-JS",t).replace("FILLME-PYTHON",e)}y.addEventListener("click",()=>ie()),_.addEventListener("click",()=>{E?C():(k=window.innerHeight-32,L(),S())}),b.addEventListener("click",oe);const re="https://codeberg.org/nopid/pyfrilet";function de(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)})}v.addEventListener("click",()=>window.open(re,"_blank")),g.addEventListener("click",()=>{V&&confirm("Réinitialiser le code ? Les modifications seront perdues.")&&(V.setValue(t,-1),ie())}),window.addEventListener("keydown",e=>{const n=E&&V&&V.isFocused&&V.isFocused();if(n||!["ArrowLeft","ArrowRight","ArrowUp","ArrowDown"].includes(e.key)){if("Enter"===e.key&&e.shiftKey)return e.preventDefault(),void ie();if("Escape"===e.key)return n?void setTimeout(()=>{E&&C()},0):(e.preventDefault(),e.stopPropagation(),void(E?C():S()));if(!n)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(V&&confirm("Réinitialiser le code ? Les modifications seront perdues.")&&(V.setValue(t,-1),ie()))):(e.preventDefault(),void J())}else e.preventDefault()},!0),(async()=>{m.textContent="Chargement des dépendances…",f.style.display="flex";try{await de(o.p5),await de(o.ace),await de(o.acePython),await de(o.aceMonokai),await de(o.aceLangTools),await de(o.aceSearchbox),await de(o.pyodide)}catch(e){return m.textContent="⚠ "+e.message,void(document.getElementById("pf-loader-bar").style.display="none")}X(),await ie(),f.style.display="none"})()}(y&&y.trim()?y:m,m,h,f)})}();
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)})}();