const fs = require('fs'); const path = require('path'); const root = path.resolve(__dirname, '..'); const scenes = JSON.parse(fs.readFileSync(root + '/design/scenes.json')); const assetFiles = require('../design/asset-files.json'); const infoFiles = new Set(scenes.flatMap(s=>s.layers.filter(l=>l.asset&&/info/i.test(l.name)).map(l=>assetFiles[l.asset]))); const normalize = value => value.normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[’']/g, '').replace(/\s+/g, ' ').trim().toLowerCase(); const onboarding = ['2:2', '2:30', '677:64', '2:268', '2:334', '2:213', '2:431']; function compile(scene) { const watch = scene.section.endsWith('+ WATCH'); const section = watch ? 'ABONNEMENT IPHONE + WATCH' : 'ABONNEMENT IPHONE'; const available = scenes.filter(s => s.section === section && s.w === 402); const find = name => available.find(s => normalize(s.name) === normalize(name))?.id; const go = (name, label) => {const target = find(name);return target ? {type:'navigate', target, label} : undefined;}; const panels = scenes.filter(s => s.section === scene.section && s.w === 362 && Math.abs((s.canvasX + 181) - (scene.canvasX + 201)) < 35); const panel = panels.sort((a,b)=>Math.abs(a.canvasY-scene.canvasY)-Math.abs(b.canvasY-scene.canvasY))[0]; const family = normalize(scene.name).split(' - ')[0]; const result = () => { if (/mesure|hrv/.test(family)) return find('Résultat Mesure VFC'); if (family === 'ma coherence') return find('Ma Cohérence - Restitution'); if (family === 'coherence cardiaque') return find('Cohérence Cardiaque - Fin'); if (family.includes('4·4·4·4')) return find('4·4·4·4 - Respiration carré - Restitution (simple)'); if (family.includes('calme express')) return find('Calme express - Restitution (simple)'); if (/4.?7.?8|sommeil/.test(family)) return find('Respirer - Restitution (simple)'); if (/micro|apaiser|booster|endormissement/.test(family)) return find('Ancrer - Restitution'); if (/focus|concentrer|miroir/.test(family)) return find('Recentrer - Restitution'); if (/decouverte|presence|sensation|atterrissage|libre|meditation/.test(family)) return find('Méditation haptique - Restitution'); if (/sos/.test(family)) return find('SOS Stress - Fin'); }; const start = () => { if (/mesure/.test(family)) return find('Mesure HRV en cours'); if (family === 'ma coherence') return find('Ma Cohérence - En cours'); if (family.includes('4·7·8')) return find('4-7-8 - Séance'); if (family.includes('4·4·4·4')) return find('4·4·4·4 - Respiration carré - Séance'); return available.find(s => normalize(s.name).startsWith(family + ' - ') && /séance/i.test(s.name))?.id; }; const categories = { 'respirer':'Vos outils - respirez', 'respirez':'Vos outils - respirez', 'ancrer':'Vos outils - Ancrer', 'recentrer':'Vos outils - Recentrer', 'meditation haptique':'Vos outils - Méditation haptique', 'reveil intelligent':'Vos outils - Réveil intelligent', 'ma coherence':'Ma Cohérence - Préparation', 'coherence cardiaque':'Cohérence Cardiaque - Réglages', 'calme express':'Calme express - Préparation', '4·4·4·4 - equilibre':'4·4·4·4 - Équilibre - Préparation', '4·7·8 - sommeil':'4·7·8 - Sommeil - Préparation', 'micro-ancrage':'Micro-Ancrage - Préparation', 'apaiser':'Apaiser - Préparation', 'booster':'Booster - Préparation', 'endormissement':'Endormissement - Préparation', 'focus':'Focus - Préparation', 'concentrer':'Concentrer - Préparation', 'miroir cardiaque':'Miroir Cardiaque - Préparation', 'decouverte':'Découverte - Préparation', 'presence':'Présence - Préparation', 'sensation':'Sensation - Préparation', 'atterrissage':'Atterrissage - Préparation', 'libre':'Libre - Préparation', 'creer mon protocole':'Créer mon protocole', 'references scientifiques':'Références scientifiques', 'diagnostic matinal':'Accueil - Diagnostic matinal', }; function action(label, layer) { const l = normalize(label); const name = normalize(layer.name); if (panel && layer.asset && (/info/i.test(layer.name)||infoFiles.has(assetFiles[layer.asset]))) return {type:'overlay',target:panel.id,label:'En savoir plus'}; if (!l && /button/i.test(layer.name) && layer.y > 65 && layer.w <= 40 && panel) return {type:'overlay',target:panel.id,label:'En savoir plus'}; if (/informations personnelles/.test(l)) return {type:'navigate',target:'2:268',label}; if (/^equipement/.test(l)) return {type:'navigate',target:'2:334',label}; if (/guide des exercices/.test(l)) return go('Vos Outils',label); if (/^voir tout$|mes sessions/.test(l)) return go('Évolution - V1',label); if (!l && layer.asset && layer.y < 65) return layer.x > 300 ? go('Profil', 'Profil') : layer.x < 45 ? go('Vos Outils', 'Outils') : undefined; if (scene.section === 'ONBOARDING') { if (/^continuer$|^plus tard$|^connecter apple sante$/.test(l)) return {type:'navigate', target:onboarding[onboarding.indexOf(scene.id)+1], label}; if (l==='commencer') return {type:'finish-onboarding',label}; if (l==='iphone' || l==='iphone + apple watch') return {type:'variant',value:l==='iphone'?'phone':'watch',label}; if (/entrez votre prenom/.test(l)||name.includes('input')) return {type:'input',key:'firstName',label}; if (/selectionnez une annee/.test(l)) return {type:'input',key:'birthYear',label}; if (l==='homme'||l==='femme') return {type:'select',key:'gender',value:label,label}; } const tabs={'mon etat':'Accueil - Mon état actuel',outils:'Vos Outils','sos stress':'SOS Stress - Entrée',evolution:'Évolution - V1',profil:'Profil'}; if(tabs[l]) return go(tabs[l],label); if(/^annuler$|^retour$|^fermer$/.test(l))return {type:'back',label}; if (/mesurer mon etat|nouvelle mesure|mesurer a nouveau|refaire.*mesure/.test(l)) return go('Préparation Mesure mon etat',label); if (/mesurer.*vfc/.test(l)) return go('Préparation Mesure VFC',label); if (l==='commencer la session'&&/respirez/.test(scene.name)) return go('Ma Cohérence - Préparation',label); if (/^(commencer|demarrer|lancer|tester maintenant)/.test(l)) {const target=start();if(target)return {type:'navigate',target,label}; if(/sos/.test(l))return go('SOS Stress - Séance',label);} if (/^(arreter|terminer|fin de seance)/.test(l)){const target=result();return target?{type:'navigate',target,label}:go('Vos Outils',label);} if (/^pause$|^reprendre$/.test(l)) return {type:'pause',label}; if (/^(retour a|revenir a)/.test(l)) return go(/outil/.test(l)?'Vos Outils':'Accueil - Mon état actuel',label); if (/enregistrer/.test(l))return {type:'back',label}; if (/tester maintenant/.test(l)&&family==='creer mon protocole')return go('Cohérence Cardiaque - Séance',label); if (/mode ecran noir|escalade sonore|activer le reveil|vibration/.test(l) && l.length<50) return {type:'toggle',key:l,label}; if (/^\d+\s?min$|^autre$|^focus$|^guide$|^libre$|^semaine$|^mois$|^jour$|^7j$|^30j$|^90j$/.test(l) && (layer.t==='TEXT'||name.includes('button'))) { if(categories[l]&&/vos outils/.test(normalize(scene.name)))return go(categories[l],label); return {type:'select',key:/min|autre/.test(l)?'duration':scene.id+':selection',value:label,label}; } if (/^\d+s$|^\d+%$|^07$|^00$/.test(l) && /protocole|reveil/.test(family))return {type:'input',key:scene.id+':'+layer.id,label}; if (categories[l]) return go(categories[l],label); // Protocol cards have both a title and a description. Match the title prefix. if(name.includes('button')||name.includes('card')) for(const [title,target] of Object.entries(categories)) if(l.startsWith(title+' ')||l===title)return go(target,label); } for (const layer of scene.layers) { const children = scene.layers.filter(l => l.text!==undefined&&l.path.includes(layer.id)); const label = layer.text ?? children.map(l=>l.text).join(' '); if (layer.t==='TEXT'||/button|card|input|toggle|switch/i.test(layer.name)||layer.asset&&(layer.y<65||panel)) layer.action = action(label,layer); } // Resolve icon-only controls and broad card taps from the text they contain. for(const layer of scene.layers) if(/button/i.test(layer.name)&&!layer.action){const child=scene.layers.find(l=>l.path.includes(layer.id)&&l.action);if(child)layer.action=child.action;} for(const layer of scene.layers) if(layer.name==='Input'&&layer.h<=12&&layer.w>100){ const value=scene.layers.filter(l=>l.text&&/^\d+(s|%| min)$/.test(l.text.trim())&&l.yb.y-a.y)[0]; const label=scene.layers.filter(l=>l.text&&l.xb.y-a.y)[0]; if(value){const suffix=value.text.trim().replace(/^\d+/,'');layer.action={type:'slider',key:scene.id+':'+layer.id,value:parseInt(value.text,10).toString(),valueNode:value.id,maximum:suffix==='%'?100:suffix===' min'?60:20,suffix,label:label?.text||value.text};value.action=undefined;} } for(const layer of scene.layers) if(layer.r>=100&&layer.w>=30&&layer.w<=60&&layer.h>=18&&layer.h<=32){ const label=scene.layers.find(l=>l.action?.type==='toggle'&&l.text&&Math.abs(l.y-layer.y)<15&&l.xn+s.layers.filter(l=>l.action).length,0)}); module.exports={compile,normalize,onboarding};