PageSourceSearch

https://info-luxe.com/wp-content/plugins/powerai/js/powerai.js?ver=2.9.6.1790072024

js info-luxe.com collected 2026-09-25 02:45:51 UTC 13,302 bytes, 194 lines download raw bytes

1// recettes / tutos : multiplicateur de portions, cases à cocher, mode cuisine (recette v2)
2document.addEventListener('DOMContentLoaded', function() {
3    var countInput = document.querySelector('.count-ingredient');
4
5    /* ---------------- multiplicateur de portions ---------------- */
6    if (countInput) {
7        var basePersonnes = parseInt(countInput.value, 10) || 1;
8
9        // Affichage "cuisine" : entiers tels quels, fractions usuelles (¼ ⅓ ½ ⅔ ¾), sinon 1 décimale.
10        // Le multiplicateur relit toujours data-base-quantite (jamais le texte affiché), donc l'affichage
11        // en fraction ne casse pas les calculs suivants.
12        var FRACTIONS = [[0.25, '¼'], [0.33, '⅓'], [0.5, '½'], [0.66, '⅔'], [0.75, '¾']];
13        function formatQty(q) {
14            if (Math.abs(q - Math.round(q)) < 0.02) return String(Math.round(q));
15            var entier = Math.floor(q), reste = q - entier;
16            for (var i = 0; i < FRACTIONS.length; i++) {
17                if (Math.abs(reste - FRACTIONS[i][0]) < 0.04) return (entier ? entier + ' ' : '') + FRACTIONS[i][1];
18            }
19            return (Math.round(q * 10) / 10).toString().replace('.', ',');
20        }
21        function parseQty(text) {
22            // accepte "0.5", "0,5", "1/2", "1 1/2", "½"
23            var t = (text || '').trim().replace(',', '.');
24            var uni = {'¼': 0.25, '⅓': 0.333, '½': 0.5, '⅔': 0.666, '¾': 0.75};
25            var m = t.match(/^(\d+)?\s*([¼⅓½⅔¾])$/);
26            if (m) return (parseInt(m[1] || '0', 10)) + uni[m[2]];
27            m = t.match(/^(\d+)\s+(\d+)\/(\d+)$/);
28            if (m) return parseInt(m[1], 10) + parseInt(m[2], 10) / parseInt(m[3], 10);
29            m = t.match(/^(\d+)\/(\d+)$/);
30            if (m) return parseInt(m[1], 10) / parseInt(m[2], 10);
31            var f = parseFloat(t);
32            return isNaN(f) ? null : f;
33        }
34        function updateQuantities(factor) {
35            document.querySelectorAll('.ingredient .chiffre-ingredient').forEach(function(element) {
36                var base = element.dataset.baseQuantite;
37                if (base === undefined) {
38                    var parsed = parseQty(element.textContent);
39                    element.dataset.baseQuantite = parsed === null ? '' : String(parsed);
40                    base = element.dataset.baseQuantite;
41                }
42                if (base === '') return;
43                var q = parseFloat(base) * factor / basePersonnes;
44                element.textContent = formatQty(q);
45                // unité au pluriel/singulier selon la quantité (recette v2 : data-sing / data-plur posés par le rendu)
46                var unite = element.parentNode ? element.parentNode.querySelector('.pai-unite[data-plur]') : null;
47                if (unite) unite.textContent = q > 1 ? unite.dataset.plur : unite.dataset.sing;
48            });
49        }
50        function setCount(n) {
51            n = Math.max(1, parseInt(n, 10) || 1);
52            countInput.value = n;
53            updateQuantities(n);
54        }
55        document.querySelectorAll('.fa-plus,.pai-plus').forEach(function(b) { b.addEventListener('click', function() { setCount(parseInt(countInput.value, 10) + 1); }); });
56        document.querySelectorAll('.fa-minus,.pai-moins').forEach(function(b) { b.addEventListener('click', function() { setCount(parseInt(countInput.value, 10) - 1); }); });
57        countInput.addEventListener('change', function() { setCount(this.value); });
58        updateQuantities(basePersonnes); // affichage "cuisine" dès le chargement (0.5 → ½)
59    }
60
61    document.querySelectorAll('.ingredient input[type=checkbox]').forEach(function(checkbox) {
62        checkbox.addEventListener('click', function() { this.nextElementSibling.classList.toggle('barre'); });
63    });
64
65    /* ---------------- mode cuisine (recette v2 : étapes ancrées #etape-n) ---------------- */
66    var prepH2 = document.getElementById('preparation');
67    var stepHeadings = Array.prototype.slice.call(document.querySelectorAll('h3[id^="etape-"]'));
68    if (!prepH2 || !stepHeadings.length) return;
69
70    var lang = (document.documentElement.lang || 'en').substring(0, 2).toLowerCase();
71    var L = {
72        fr: { on: 'Mode cuisine', off: 'Quitter le mode cuisine', activate: 'Activer', desc: 'Écran qui reste allumé, étapes à cocher, minuteur à chaque étape : pensé pour cuisiner avec le téléphone à côté de la planche.', timer: 'Minuteur', start: '
72Lancer', stop: 'Stop', done: 'Terminé !', reset: 'Réinitialiser' },
73        en: { on: 'Cooking mode', off: 'Exit cooking mode', activate: 'Turn on', desc: 'Screen stays on, steps you can tick off, a timer for each step: made for cooking with your phone next to the board.', timer: 'Timer', start: 'Start', stop: 'Stop', done: 'Done!', reset: 'Reset' },
74        it: { on: 'Modalità cucina', off: 'Esci dalla modalità cucina', activate: 'Attiva', desc: 'Schermo sempre acceso, passaggi da spuntare, timer per ogni passaggio: pensata per cucinare con il telefono accanto al tagliere.', timer: 'Timer', start: 'Avvia', stop: 'Stop', done: 'Fatto!', reset: 'Azzera' },
75        de: { on: 'Kochmodus', off: 'Kochmodus beenden', activate: 'Einschalten', desc: 'Bildschirm bleibt an, Schritte zum Abhaken, Timer für jeden Schritt: zum Kochen mit dem Handy neben dem Brett.', timer: 'Timer', start: 'Start', stop: 'Stopp', done: 'Fertig!', reset: 'Zurücksetzen' },
76        es: { on: 'Modo cocina', off: 'Salir del modo cocina', activate: 'Activar', desc: 'Pantalla siempre encendida, pasos para marcar, temporizador en cada paso: pensado para cocinar con el móvil junto a la tabla.', timer: 'Temporizador', start: 'Iniciar', stop: 'Parar', done: '¡Listo!', reset: 'Reiniciar' },
77        pl: { on: 'Tryb gotowania', off: 'Wyjdź z trybu gotowania', activate: 'Włącz', desc: 'Ekran nie gaśnie, kroki do odhaczania, minutnik przy każdym kroku: do gotowania z telefonem obok deski.', timer: 'Minutnik', start: 'Start', stop: 'Stop', done: 'Gotowe!', reset: 'Resetuj' }
78    }[lang] || null;
79    if (!L) L = { on: 'Cooking mode', off: 'Exit cooking mode', activate: 'Turn on', desc: 'Screen stays on, steps you can tick off, a timer for each step.', timer: 'Timer', start: 'Start', stop: 'Stop', done: 'Done!', reset: 'Reset' };
80
81    // 1) regroupe chaque étape (h3 + ses frères jusqu'au prochain h2/h3) dans un div.pai-etape
82    var storeKey = 'pai-cook-' + location.pathname;
83    var doneSteps = [];
84    try { doneSteps = JSON.parse(localStorage.getItem(storeKey) || '[]'); } catch (e) { doneSteps = []; }
85
86    stepHeadings.forEach(function(h3) {
87        var wrap = document.createElement('div');
88        wrap.className = 'pai-etape';
89        wrap.dataset.step = h3.id;
90        h3.parentNode.insertBefore(wrap, h3);
91        var node = h3;
92        while (node && !(node.nodeType === 1 && /^H[23]$/.test(node.tagName) && node !== h3)) {
93            var next = node.nextSibling;
94            wrap.appendChild(node);
95            node = next;
96        }
97        // case à cocher "étape faite"
98        var cb = document.createElement('input');
99        cb.type = 'checkbox'; cb.className = 'pai-etape-check'; cb.setAttribute('aria-label', h3.textContent);
100        cb.checked = doneSteps.indexOf(h3.id) !== -1;
101        if (cb.checked) wrap.classList.add('pai-etape-done');
102        cb.addEventListener('change', function() {
103            wrap.classList.toggle('pai-etape-done', cb.checked);
104            var idx = doneSteps.indexOf(h3.id);
105            if (cb.checked && idx === -1) doneSteps.push(h3.id);
106            if (!cb.checked && idx !== -1) doneSteps.splice(idx, 1);
107            try { localStorage.setItem(storeKey, JSON.stringify(doneSteps)); } catch (e) {}
108            // étape courante = première non cochée
109            var current = document.querySelector('.pai-etape:not(.pai-etape-done)');
110            document.querySelectorAll('.pai-etape').forEach(function(w) { w.classList.toggle('pai-etape-current', w === current); });
111        });
112        h3.insertBefore(cb, h3.firstChild);
113
114        // minuteur sur les étapes qui ont une durée
115        var duree = wrap.querySelector('.pai-etape-duree');
116        if (duree) {
117            var minutes = parseInt((duree.textContent.match(/(\d+)/) || [0, 0])[1], 10);
118            if (minutes > 0) buildTimer(duree, minutes);
119        }
120    });
121    var firstOpen = document.querySelector('.pai-etape:not(.pai-etape-done)');
122    if (firstOpen) firstOpen.classList.add('pai-etape-current');
123
124    // 2) encart "mode cuisine" sous le H2 Préparation : une carte couleur thème qui explique ce que ça fait + bouton
125    var box = document.createElement('div');
126    box.className = 'pai-cards';
127    box.innerHTML = '<div class="pai-card pai-card-primary pai-card-full pai-cookmode-box"><div class="pai-cookmode-text"><small>👨‍🍳 ' + L.on + '</small><p></p></div></div>';
128    box.querySelector('p').textContent = L.desc;
129    var btn = document.createElement('button');
130    btn.type = 'button'; btn.className = 'pai-cookmode-btn'; btn.textContent = L.activate;
131    box.firstChild.appendChild(btn);
132    prepH2.parentNode.insertBefore(box, prepH2.nextSibling);
133
134    var wakeLock = null;
135    function requestWakeLock() {
136        if (!('wakeLock' in navigator)) return;
137        navigator.wakeLock.request('screen').then(function(lock) { wakeLock = lock; }).catch(function() {});
138    }
139    function releaseWakeLock() { if (wakeLock) { wakeLock.release().catch(function() {}); wakeLock = null; } }
140    document.addEventListener('visibilitychange', function() {
141        if (document.body.classList.contains('pai-cookmode') && document.visibilityState === 'visible') requestWakeLock();
142    });
143    btn.addEventListener('click', function() {
144        var on = document.body.classList.toggle('pai-cookmode');
145        btn.textContent = on ? '✕ ' + L.off : L.activate;
146        if (on) { requestWakeLock(); var cur = document.querySelector('.pai-etape-current'); if (cur) cur.scrollIntoView({ behavior: 'smooth', block: 'start' }); }
147        else releaseWakeLock();
148    });
149
150    // 3) minuteur : compte à rebours + bip (Web Audio, pas de fichier) + notification si autorisée
151    function beep() {
152        try {
153            var ctx = new (window.AudioContext || window.webkitAudioContext)();
154            [0, 0.35, 0.7].forEach(function(t) {
155                var o = ctx.createOscillator(), g = ctx.createGain();
156                o.frequency.value = 880; o.connect(g); g.connect(ctx.destination);
157                g.gain.setValueAtTime(0.0001, ctx.currentTime + t);
158                g.gain.exponentialRampToValueAtTime(0.3, ctx.currentTime + t + 0.02);
159                g.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + t + 0.25);
160                o.start(ctx.currentTime + t); o.stop(ctx.currentTime + t + 0.3);
161            });
162        } catch (e) {}
163    }
164    function buildTimer(anchorEl, minutes) {
165        var box = document.createElement('span');
166        box.className = 'pai-timer';
167        var display = document.createElement('span'); display.className = 'pai-timer-display'; display.textContent = pad(minutes) + ':00';
168        var start = document.createElement('button'); start.type = 'button'; start.className = 'pai-timer-btn'; start.textContent = '▶ ' + L.start;
169        box.appendChild(display); box.appendChild(start);
170        anchorEl.parentNode.insertBefore(box, anchorEl.nextSibling);
171        var remaining = minutes * 60, handle = null;
172        function render() { display.textContent = pad(Math.floor(remaining / 60)) + ':' + pad(remaining % 60); }
173        function stop() { if (handle) clearInterval(handle); handle = null; start.textContent = remaining === minutes * 60 ? '▶ ' + L.start : '↺ ' + L.reset; box.classList.remove('pai-timer-running'); }
174        start.addEventListener('click', function() {
175            if (handle) { stop(); return; }
176            if (remaining <= 0) { remaining = minutes * 60; render(); stop(); return; }
177            if (start.textContent.indexOf('↺') === 0) { remaining = minutes * 60; render(); start.textContent = '▶ ' + L.start; return; }
178            if ('Notification' in window && Notification.permission === 'default') { try { Notification.requestPermission(); } catch (e) {} }
179            box.classList.add('pai-timer-running'); start.textContent = '■ ' + L.stop;
180            handle = setInterval(function() {
181                remaining--; render();
182                if (remaining <= 0) {
183                    clearInterval(handle); handle = null;
184                    box.classList.remove('pai-timer-running'); box.classList.add('pai-timer-done');
185                    display.textContent = L.done; start.textContent = '↺ ' + L.reset;
186                    beep();
187                    if ('Notification' in window && Notification.permission === 'granted') { try { new Notification(L.timer + ' — ' + L.done); } catch (e) {} }
188                    setTimeout(function() { box.classList.remove('pai-timer-done'); }, 4000);
189                }
190            }, 1000);
191        });
192    }
193    function pad(n) { return (n < 10 ? '0' : '') + n; }
194});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.