feat: iphone app

This commit is contained in:
leon
2026-09-08 09:07:27 +02:00
parent dcecd07d56
commit 8666f6eb8f
388 changed files with 35960 additions and 1 deletions
+109
View File
@@ -0,0 +1,109 @@
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.y<layer.y&&layer.y-l.y<55).sort((a,b)=>b.y-a.y)[0];
const label=scene.layers.filter(l=>l.text&&l.x<layer.x+100&&l.y<layer.y&&layer.y-l.y<55).sort((a,b)=>b.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.x<layer.x);
if(label)layer.action={...label.action};
}
return {...scene, resultTarget:result(), startTarget:start()};
}
const output=scenes.map(compile);
fs.mkdirSync(root+'/src/design',{recursive:true});fs.writeFileSync(root+'/src/design/scenes.json',JSON.stringify(output));
console.log({scenes:output.length,actions:output.reduce((n,s)=>n+s.layers.filter(l=>l.action).length,0)});
module.exports={compile,normalize,onboarding};