Télécommande LED pour chambre d'enfant via Home Assistant
- Package HA : 8 scripts d'effets (calmes et « boum ») et une automatisation webhook local_only avec liste blanche stricte - Page web autonome (HTML/CSS/JS ES5) tenant sur un écran de lecteur MP3 - Mini APK Android (WebView verrouillée) compilé sans Gradle - Modèles + config.local.env non versionné : aucune valeur réelle (IP, ID du webhook, entité, prénom) dans le dépôt ; deploy.sh génère et déploie, android/build.sh injecte la configuration Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 7.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
@@ -0,0 +1,19 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<defs>
|
||||
<linearGradient id="ciel" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#2a3370"/>
|
||||
<stop offset="1" stop-color="#11163a"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="512" height="512" rx="112" fill="url(#ciel)"/>
|
||||
<g fill="none" stroke-width="34" transform="translate(0 40)">
|
||||
<path d="M86 300a170 170 0 0 1 340 0" stroke="#ff3b3b"/>
|
||||
<path d="M120 300a136 136 0 0 1 272 0" stroke="#ff9a1f"/>
|
||||
<path d="M154 300a102 102 0 0 1 204 0" stroke="#ffe11f"/>
|
||||
<path d="M188 300a68 68 0 0 1 136 0" stroke="#3be05a"/>
|
||||
<path d="M222 300a34 34 0 0 1 68 0" stroke="#7a4dff"/>
|
||||
</g>
|
||||
<circle cx="412" cy="104" r="12" fill="#fff"/>
|
||||
<circle cx="104" cy="128" r="9" fill="#fff"/>
|
||||
<circle cx="440" cy="196" r="7" fill="#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 844 B |
@@ -0,0 +1,447 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<title>Ma lumière</title>
|
||||
<meta name="theme-color" content="#141a3a">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-title" content="Ma lumière">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="icon-192.png">
|
||||
<link rel="apple-touch-icon" href="icon-180.png">
|
||||
<style>
|
||||
* { -webkit-box-sizing: border-box; box-sizing: border-box; margin: 0; padding: 0; }
|
||||
html, body { background: #141a3a; }
|
||||
html { height: 100%; }
|
||||
body {
|
||||
min-height: 100%;
|
||||
color: #fff;
|
||||
font-family: "Trebuchet MS", "Roboto", "Droid Sans", sans-serif;
|
||||
font-weight: bold;
|
||||
background: #141a3a;
|
||||
background: -webkit-linear-gradient(top, #1d2452 0%, #141a3a 60%, #0d1129 100%);
|
||||
background: linear-gradient(to bottom, #1d2452 0%, #141a3a 60%, #0d1129 100%);
|
||||
background-attachment: fixed;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-touch-callout: none;
|
||||
}
|
||||
/* Tout doit tenir sur un écran d'environ 340 x 530 px (Fanvace M36) */
|
||||
.page { max-width: 420px; margin: 0 auto; padding: 10px 8px 12px; }
|
||||
|
||||
button {
|
||||
font: inherit; color: inherit; border: 0; cursor: pointer;
|
||||
-webkit-appearance: none; appearance: none;
|
||||
touch-action: manipulation;
|
||||
-webkit-transition: -webkit-transform .12s ease-out;
|
||||
transition: transform .12s ease-out;
|
||||
}
|
||||
button:focus { outline: none; }
|
||||
button.press, button:active { -webkit-transform: scale(.9); transform: scale(.9); }
|
||||
|
||||
.clear:after { content: ""; display: table; clear: both; }
|
||||
.bloc { margin-top: 8px; }
|
||||
|
||||
/* ---- En-tête : titre, puis Veilleuse + Éteindre ---- */
|
||||
.title { font-size: 16px; text-align: center; padding: 0 0 4px; }
|
||||
.title span { color: #ffd38a; }
|
||||
.pill {
|
||||
display: block; width: 100%; height: 50px; border-radius: 25px;
|
||||
background: #2b335f; border: 3px solid #48528a;
|
||||
font-size: 16px; line-height: 44px; text-align: center; white-space: nowrap;
|
||||
}
|
||||
.pill svg { width: 24px; height: 24px; vertical-align: middle; margin: -3px 6px 0 0; }
|
||||
.pill-nuit { background: #0b0f2a; border-color: #333c78; }
|
||||
.pill-nuit small { font-size: 12px; color: #ffd38a; }
|
||||
|
||||
/* ---- Pastilles de couleur (5 + 4) ---- */
|
||||
.cell5 { float: left; width: 20%; padding: 5px; }
|
||||
.rang2 { padding: 0 10%; }
|
||||
.rang2 .cell5 { width: 25%; }
|
||||
.sw {
|
||||
display: block; width: 100%; height: 0; padding: 0 0 100% 0;
|
||||
border-radius: 50%; position: relative;
|
||||
box-shadow: inset 0 -5px 0 rgba(0,0,0,.18), 0 3px 8px rgba(0,0,0,.35);
|
||||
}
|
||||
/* reflet */
|
||||
.sw:before {
|
||||
content: ""; position: absolute; left: 20%; top: 12%; width: 32%; height: 22%;
|
||||
border-radius: 50%; background: rgba(255,255,255,.35);
|
||||
}
|
||||
|
||||
/* ---- Luminosité (4 boutons) ---- */
|
||||
.cell4 { float: left; width: 25%; padding: 4px; }
|
||||
.lum {
|
||||
display: block; width: 100%; height: 56px; border-radius: 16px;
|
||||
background: #2b335f; border: 3px solid #48528a; position: relative;
|
||||
}
|
||||
.lum svg { position: absolute; left: 50%; top: 50%; }
|
||||
.l1 svg { width: 16px; height: 16px; margin: -8px 0 0 -8px; opacity: .45; }
|
||||
.l2 svg { width: 24px; height: 24px; margin: -12px 0 0 -12px; opacity: .65; }
|
||||
.l3 svg { width: 32px; height: 32px; margin: -16px 0 0 -16px; opacity: .85; }
|
||||
.l4 svg { width: 40px; height: 40px; margin: -20px 0 0 -20px; }
|
||||
|
||||
/* ---- Effets : 4 calmes + 3 « boum » ---- */
|
||||
.cell3 { float: left; width: 33.333%; padding: 4px; }
|
||||
.cell2 { float: left; width: 50%; padding: 4px; }
|
||||
.fx {
|
||||
display: block; width: 100%; height: 92px; border-radius: 18px;
|
||||
position: relative; overflow: hidden; text-align: center;
|
||||
box-shadow: inset 0 -5px 0 rgba(0,0,0,.2), 0 3px 8px rgba(0,0,0,.35);
|
||||
}
|
||||
.fx svg { position: absolute; left: 50%; top: 10px; margin-left: -30px; width: 60px; height: 42px; }
|
||||
.fx b {
|
||||
position: absolute; left: 2px; right: 2px; bottom: 9px;
|
||||
font-size: 14px; text-shadow: 0 1px 3px rgba(0,0,0,.6);
|
||||
}
|
||||
/* tuiles étroites (4 par rangée) : nom sur 2 lignes possible */
|
||||
.cell4 .fx svg { width: 52px; height: 38px; margin-left: -26px; }
|
||||
.cell4 .fx b { font-size: 12px; line-height: 14px; bottom: 7px; }
|
||||
.fx-arc { background: #3a6fd8; background: -webkit-linear-gradient(top, #5fa8ff, #2f5fc8); background: linear-gradient(to bottom, #5fa8ff, #2f5fc8); }
|
||||
.fx-resp { background: #6b3fb8; background: -webkit-linear-gradient(top, #9a62e6, #5a2fa0); background: linear-gradient(to bottom, #9a62e6, #5a2fa0); }
|
||||
.fx-sun { background: #e0653a; background: -webkit-linear-gradient(top, #ffb347, #f0607a 55%, #7b3fa8); background: linear-gradient(to bottom, #ffb347, #f0607a 55%, #7b3fa8); }
|
||||
.fx-sea { background: #1477b8; background: -webkit-linear-gradient(top, #2fc4d8, #1a5fb4 60%, #12357a); background: linear-gradient(to bottom, #2fc4d8, #1a5fb4 60%, #12357a); }
|
||||
/* tuiles néon (boum) */
|
||||
.fx-neon { background: #150a2e; border: 3px solid #ff4fc0; box-shadow: 0 0 12px rgba(255,79,192,.45), inset 0 -5px 0 rgba(0,0,0,.3); }
|
||||
.fx-disco { -webkit-animation: neon 3s steps(1) infinite; animation: neon 3s steps(1) infinite; }
|
||||
.fx-boum { border-color: #29b8ff; box-shadow: 0 0 12px rgba(41,184,255,.5), inset 0 -5px 0 rgba(0,0,0,.3); }
|
||||
.fx-tourb { border-color: #ffd91f; box-shadow: 0 0 12px rgba(255,217,31,.45), inset 0 -5px 0 rgba(0,0,0,.3); }
|
||||
.fx-tourb svg { -webkit-animation: tourne 4s linear infinite; animation: tourne 4s linear infinite; }
|
||||
@-webkit-keyframes neon {
|
||||
0% { border-color: #ff4fc0; } 25% { border-color: #29b8ff; }
|
||||
50% { border-color: #ffd91f; } 75% { border-color: #a14dff; }
|
||||
}
|
||||
@keyframes neon {
|
||||
0% { border-color: #ff4fc0; } 25% { border-color: #29b8ff; }
|
||||
50% { border-color: #ffd91f; } 75% { border-color: #a14dff; }
|
||||
}
|
||||
@-webkit-keyframes tourne { to { -webkit-transform: rotate(360deg); } }
|
||||
@keyframes tourne { to { transform: rotate(360deg); } }
|
||||
|
||||
/* bulle qui respire */
|
||||
.breath {
|
||||
position: absolute; left: 50%; top: 10px; width: 42px; height: 42px; margin-left: -21px;
|
||||
border-radius: 50%; background: #fff;
|
||||
box-shadow: 0 0 16px 5px rgba(255,255,255,.7);
|
||||
-webkit-animation: breath 6s ease-in-out infinite;
|
||||
animation: breath 6s ease-in-out infinite;
|
||||
}
|
||||
@-webkit-keyframes breath {
|
||||
0%, 100% { -webkit-transform: scale(.55); opacity: .35; }
|
||||
50% { -webkit-transform: scale(1); opacity: 1; }
|
||||
}
|
||||
@keyframes breath {
|
||||
0%, 100% { transform: scale(.55); opacity: .35; }
|
||||
50% { transform: scale(1); opacity: 1; }
|
||||
}
|
||||
|
||||
/* ---- Retour visuel après envoi ---- */
|
||||
.ok { -webkit-animation: ok .6s ease-out; animation: ok .6s ease-out; }
|
||||
@-webkit-keyframes ok {
|
||||
0% { box-shadow: 0 0 0 0 rgba(255,255,255,.9); }
|
||||
100% { box-shadow: 0 0 0 14px rgba(255,255,255,0); }
|
||||
}
|
||||
@keyframes ok {
|
||||
0% { box-shadow: 0 0 0 0 rgba(255,255,255,.9); }
|
||||
100% { box-shadow: 0 0 0 14px rgba(255,255,255,0); }
|
||||
}
|
||||
.err { -webkit-animation: err .4s linear; animation: err .4s linear; }
|
||||
@-webkit-keyframes err {
|
||||
0%, 100% { -webkit-transform: translateX(0); }
|
||||
25% { -webkit-transform: translateX(-8px); }
|
||||
75% { -webkit-transform: translateX(8px); }
|
||||
}
|
||||
@keyframes err {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
25% { transform: translateX(-8px); }
|
||||
75% { transform: translateX(8px); }
|
||||
}
|
||||
|
||||
.toast {
|
||||
position: fixed; left: 16px; right: 16px; bottom: 16px;
|
||||
padding: 14px; border-radius: 16px; text-align: center; font-size: 16px;
|
||||
background: #d9415e; box-shadow: 0 6px 18px rgba(0,0,0,.4);
|
||||
opacity: 0; visibility: hidden;
|
||||
-webkit-transition: opacity .25s, visibility .25s;
|
||||
transition: opacity .25s, visibility .25s;
|
||||
}
|
||||
.toast.show { opacity: 1; visibility: visible; }
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.breath, .fx-disco, .fx-tourb svg { -webkit-animation: none; animation: none; }
|
||||
.breath { opacity: .8; }
|
||||
}
|
||||
@media (max-width: 300px) {
|
||||
.pill { font-size: 14px; }
|
||||
.fx b, .cell4 .fx b { font-size: 11px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body ontouchstart="">
|
||||
<div class="page">
|
||||
|
||||
<h1 class="title">La lumière de <span>__PRENOM__</span></h1>
|
||||
<div class="clear">
|
||||
<div class="cell2">
|
||||
<button type="button" class="pill pill-nuit" data-action="effet" data-nom="veilleuse" aria-label="Veilleuse, s'éteint dans 30 minutes">
|
||||
<svg viewBox="0 0 32 32" aria-hidden="true"><path d="M17 3a13 13 0 1 0 13 17A10.5 10.5 0 0 1 17 3z" fill="#ffd38a"/></svg>Veilleuse <small>30 min</small>
|
||||
</button>
|
||||
</div>
|
||||
<div class="cell2">
|
||||
<button type="button" class="pill" data-action="off" aria-label="Éteindre">
|
||||
<svg viewBox="0 0 32 32" aria-hidden="true"><path d="M10 7.5a11 11 0 1 0 12 0" fill="none" stroke="#fff" stroke-width="3.5" stroke-linecap="round"/><path d="M16 3v12" stroke="#fff" stroke-width="3.5" stroke-linecap="round"/></svg>Éteindre
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Couleurs -->
|
||||
<div class="bloc">
|
||||
<div class="clear">
|
||||
<div class="cell5"><button type="button" class="sw" data-action="couleur" data-teinte="0" style="background:#ff2a2a" aria-label="Rouge"></button></div>
|
||||
<div class="cell5"><button type="button" class="sw" data-action="couleur" data-teinte="25" style="background:#ff8a1f" aria-label="Orange"></button></div>
|
||||
<div class="cell5"><button type="button" class="sw" data-action="couleur" data-teinte="50" style="background:#ffd91f" aria-label="Jaune"></button></div>
|
||||
<div class="cell5"><button type="button" class="sw" data-action="couleur" data-teinte="120" style="background:#2fe05a" aria-label="Vert"></button></div>
|
||||
<div class="cell5"><button type="button" class="sw" data-action="couleur" data-teinte="170" style="background:#1fe0c4" aria-label="Turquoise"></button></div>
|
||||
</div>
|
||||
<div class="clear rang2">
|
||||
<div class="cell5"><button type="button" class="sw" data-action="couleur" data-teinte="200" style="background:#29b8ff" aria-label="Bleu ciel"></button></div>
|
||||
<div class="cell5"><button type="button" class="sw" data-action="couleur" data-teinte="235" style="background:#3d5cff" aria-label="Bleu"></button></div>
|
||||
<div class="cell5"><button type="button" class="sw" data-action="couleur" data-teinte="275" style="background:#a14dff" aria-label="Violet"></button></div>
|
||||
<div class="cell5"><button type="button" class="sw" data-action="couleur" data-teinte="320" style="background:#ff4fc0" aria-label="Rose"></button></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Plus ou moins fort -->
|
||||
<div class="bloc clear">
|
||||
<div class="cell4"><button type="button" class="lum l1" data-action="luminosite" data-valeur="10" aria-label="Très doux"><svg viewBox="0 0 32 32" aria-hidden="true"><use xlink:href="#soleil" href="#soleil"/></svg></button></div>
|
||||
<div class="cell4"><button type="button" class="lum l2" data-action="luminosite" data-valeur="40" aria-label="Doux"><svg viewBox="0 0 32 32" aria-hidden="true"><use xlink:href="#soleil" href="#soleil"/></svg></button></div>
|
||||
<div class="cell4"><button type="button" class="lum l3" data-action="luminosite" data-valeur="70" aria-label="Fort"><svg viewBox="0 0 32 32" aria-hidden="true"><use xlink:href="#soleil" href="#soleil"/></svg></button></div>
|
||||
<div class="cell4"><button type="button" class="lum l4" data-action="luminosite" data-valeur="100" aria-label="Très fort"><svg viewBox="0 0 32 32" aria-hidden="true"><use xlink:href="#soleil" href="#soleil"/></svg></button></div>
|
||||
</div>
|
||||
|
||||
<!-- Animations calmes -->
|
||||
<div class="bloc clear">
|
||||
<div class="cell4">
|
||||
<button type="button" class="fx fx-arc" data-action="effet" data-nom="arc_en_ciel" aria-label="Arc-en-ciel">
|
||||
<svg viewBox="0 0 64 44" aria-hidden="true" fill="none" stroke-width="5">
|
||||
<path d="M6 40a26 26 0 0 1 52 0" stroke="#ff3b3b"/>
|
||||
<path d="M11 40a21 21 0 0 1 42 0" stroke="#ff9a1f"/>
|
||||
<path d="M16 40a16 16 0 0 1 32 0" stroke="#ffe11f"/>
|
||||
<path d="M21 40a11 11 0 0 1 22 0" stroke="#3be05a"/>
|
||||
<path d="M26 40a6 6 0 0 1 12 0" stroke="#7a4dff"/>
|
||||
</svg>
|
||||
<b>Arc-en-ciel</b>
|
||||
</button>
|
||||
</div>
|
||||
<div class="cell4">
|
||||
<button type="button" class="fx fx-resp" data-action="effet" data-nom="respiration" aria-label="Respiration">
|
||||
<span class="breath"></span>
|
||||
<b>Respire</b>
|
||||
</button>
|
||||
</div>
|
||||
<div class="cell4">
|
||||
<button type="button" class="fx fx-sea" data-action="effet" data-nom="ocean" aria-label="Océan">
|
||||
<svg viewBox="0 0 64 44" aria-hidden="true" fill="none" stroke="#fff" stroke-width="4" stroke-linecap="round">
|
||||
<path d="M4 12q7-8 14 0t14 0t14 0t14 0"/>
|
||||
<path d="M4 24q7-8 14 0t14 0t14 0t14 0" opacity=".8"/>
|
||||
<path d="M4 36q7-8 14 0t14 0t14 0t14 0" opacity=".6"/>
|
||||
</svg>
|
||||
<b>Océan</b>
|
||||
</button>
|
||||
</div>
|
||||
<div class="cell4">
|
||||
<button type="button" class="fx fx-sun" data-action="effet" data-nom="coucher_de_soleil" aria-label="Coucher de soleil">
|
||||
<svg viewBox="0 0 64 44" aria-hidden="true">
|
||||
<path d="M12 34a20 20 0 0 1 40 0z" fill="#ffe08a"/>
|
||||
<rect x="4" y="34" width="56" height="3" rx="1.5" fill="#fff"/>
|
||||
<rect x="16" y="40" width="32" height="2.5" rx="1.25" fill="#ffe08a" opacity=".7"/>
|
||||
</svg>
|
||||
<b>Coucher de soleil</b>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Animations boum -->
|
||||
<div class="bloc clear">
|
||||
<div class="cell3">
|
||||
<button type="button" class="fx fx-neon fx-disco" data-action="effet" data-nom="disco" aria-label="Disco">
|
||||
<svg viewBox="0 0 64 44" aria-hidden="true">
|
||||
<path d="M32 0v6" stroke="#9aa3d6" stroke-width="2"/>
|
||||
<circle cx="32" cy="24" r="17" fill="#c9d1ff"/>
|
||||
<g fill="none" stroke="#6f78b8" stroke-width="1.6">
|
||||
<ellipse cx="32" cy="24" rx="17" ry="6"/>
|
||||
<path d="M15 24h34M32 7v34"/>
|
||||
<ellipse cx="32" cy="24" rx="7" ry="17"/>
|
||||
</g>
|
||||
<path d="M8 8l2 4 4 2-4 2-2 4-2-4-4-2 4-2z" fill="#ff4fc0"/>
|
||||
<path d="M56 26l1.5 3 3 1.5-3 1.5-1.5 3-1.5-3-3-1.5 3-1.5z" fill="#29b8ff"/>
|
||||
<path d="M54 4l1.2 2.4 2.4 1.2-2.4 1.2L54 11.2l-1.2-2.4-2.4-1.2 2.4-1.2z" fill="#ffd91f"/>
|
||||
</svg>
|
||||
<b>Disco</b>
|
||||
</button>
|
||||
</div>
|
||||
<div class="cell3">
|
||||
<button type="button" class="fx fx-neon fx-boum" data-action="effet" data-nom="boum" aria-label="Boum">
|
||||
<svg viewBox="0 0 64 44" aria-hidden="true">
|
||||
<rect x="20" y="2" width="24" height="40" rx="5" fill="#2b335f" stroke="#29b8ff" stroke-width="2"/>
|
||||
<circle cx="32" cy="12" r="4.5" fill="#29b8ff"/>
|
||||
<circle cx="32" cy="29" r="9" fill="#29b8ff"/>
|
||||
<circle cx="32" cy="29" r="3.5" fill="#150a2e"/>
|
||||
<g fill="none" stroke-width="3" stroke-linecap="round">
|
||||
<path d="M12 14a14 14 0 0 0 0 16" stroke="#ff4fc0"/>
|
||||
<path d="M6 9a22 22 0 0 0 0 26" stroke="#ffd91f"/>
|
||||
<path d="M52 14a14 14 0 0 1 0 16" stroke="#ff4fc0"/>
|
||||
<path d="M58 9a22 22 0 0 1 0 26" stroke="#ffd91f"/>
|
||||
</g>
|
||||
</svg>
|
||||
<b>Boum</b>
|
||||
</button>
|
||||
</div>
|
||||
<div class="cell3">
|
||||
<button type="button" class="fx fx-neon fx-tourb" data-action="effet" data-nom="tourbillon" aria-label="Tourbillon">
|
||||
<svg viewBox="0 0 64 44" aria-hidden="true">
|
||||
<defs>
|
||||
<linearGradient id="arcenciel" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#ff3b3b"/>
|
||||
<stop offset=".25" stop-color="#ffe11f"/>
|
||||
<stop offset=".5" stop-color="#3be05a"/>
|
||||
<stop offset=".75" stop-color="#29b8ff"/>
|
||||
<stop offset="1" stop-color="#a14dff"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M32 22a3 3 0 0 1 6 0a6 6 0 0 1-12 0a9 9 0 0 1 18 0a12 12 0 0 1-24 0a15 15 0 0 1 30 0a18 18 0 0 1-18 18"
|
||||
fill="none" stroke="url(#arcenciel)" stroke-width="3.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
<b>Tourbillon</b>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast">Oups ! La lumière ne répond pas.</div>
|
||||
|
||||
<!-- Icône soleil réutilisée par les boutons de luminosité -->
|
||||
<svg width="0" height="0" style="position:absolute" aria-hidden="true">
|
||||
<defs>
|
||||
<g id="soleil" fill="#ffd38a" stroke="#ffd38a" stroke-width="2.6" stroke-linecap="round">
|
||||
<circle cx="16" cy="16" r="6.5" stroke="none"/>
|
||||
<path d="M16 2.5v3.5M16 26v3.5M2.5 16h3.5M26 16h3.5M6.5 6.5l2.5 2.5M23 23l2.5 2.5M6.5 25.5L9 23M23 9l2.5-2.5"/>
|
||||
</g>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<script>
|
||||
/* ES5 uniquement : doit tourner sur un vieux navigateur Android. */
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var WEBHOOK = '/api/webhook/__WEBHOOK_ID__';
|
||||
var ECART_MIN = 700; // ms entre deux envois, pour ménager le réseau Zigbee
|
||||
|
||||
var dernierEnvoi = 0;
|
||||
var enCours = false;
|
||||
var enAttente = null; // seule la dernière demande compte
|
||||
var minuterie = null;
|
||||
var toast = document.getElementById('toast');
|
||||
var toastMinuterie = null;
|
||||
|
||||
function maintenant() { return new Date().getTime(); }
|
||||
|
||||
function poster(corps, fini) {
|
||||
var json = JSON.stringify(corps);
|
||||
if (window.fetch) {
|
||||
window.fetch(WEBHOOK, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: json
|
||||
}).then(function (r) { fini(r.ok); }, function () { fini(false); });
|
||||
return;
|
||||
}
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', WEBHOOK, true);
|
||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) { fini(xhr.status >= 200 && xhr.status < 300); }
|
||||
};
|
||||
xhr.send(json);
|
||||
}
|
||||
|
||||
function rejouerAnimation(el, classe) {
|
||||
el.className = el.className.replace(/\s*\b(ok|err)\b/g, '');
|
||||
void el.offsetWidth; // force le navigateur à relancer l'animation
|
||||
el.className += ' ' + classe;
|
||||
}
|
||||
|
||||
function montrerErreur() {
|
||||
toast.className = 'toast show';
|
||||
if (toastMinuterie) { clearTimeout(toastMinuterie); }
|
||||
toastMinuterie = setTimeout(function () { toast.className = 'toast'; }, 2500);
|
||||
}
|
||||
|
||||
function pomper() {
|
||||
if (enCours || !enAttente) { return; }
|
||||
var attente = dernierEnvoi + ECART_MIN - maintenant();
|
||||
if (attente > 0) {
|
||||
if (!minuterie) {
|
||||
minuterie = setTimeout(function () { minuterie = null; pomper(); }, attente);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var tache = enAttente;
|
||||
enAttente = null;
|
||||
enCours = true;
|
||||
dernierEnvoi = maintenant();
|
||||
poster(tache.corps, function (ok) {
|
||||
enCours = false;
|
||||
rejouerAnimation(tache.el, ok ? 'ok' : 'err');
|
||||
if (!ok) { montrerErreur(); }
|
||||
pomper();
|
||||
});
|
||||
}
|
||||
|
||||
function envoyer(corps, el) {
|
||||
enAttente = { corps: corps, el: el };
|
||||
pomper();
|
||||
}
|
||||
|
||||
function boutonDe(el) {
|
||||
while (el && el !== document) {
|
||||
if (el.getAttribute && el.getAttribute('data-action')) { return el; }
|
||||
el = el.parentNode;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Retour visuel immédiat au toucher (avant même le "click")
|
||||
document.addEventListener('touchstart', function (e) {
|
||||
var b = boutonDe(e.target);
|
||||
if (b) { b.className += ' press'; }
|
||||
}, false);
|
||||
function relacher() {
|
||||
var btns = document.querySelectorAll('.press');
|
||||
for (var i = 0; i < btns.length; i++) {
|
||||
btns[i].className = btns[i].className.replace(/\s*\bpress\b/g, '');
|
||||
}
|
||||
}
|
||||
document.addEventListener('touchend', relacher, false);
|
||||
document.addEventListener('touchcancel', relacher, false);
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
var b = boutonDe(e.target);
|
||||
if (!b) { return; }
|
||||
var action = b.getAttribute('data-action');
|
||||
var corps = { action: action };
|
||||
if (action === 'couleur') { corps.teinte = parseInt(b.getAttribute('data-teinte'), 10); }
|
||||
if (action === 'luminosite') { corps.valeur = parseInt(b.getAttribute('data-valeur'), 10); }
|
||||
if (action === 'effet') { corps.nom = b.getAttribute('data-nom'); }
|
||||
envoyer(corps, b);
|
||||
}, false);
|
||||
}());
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "La lumière de __PRENOM__",
|
||||
"short_name": "Ma lumière",
|
||||
"start_url": "index.html",
|
||||
"scope": "./",
|
||||
"display": "standalone",
|
||||
"orientation": "portrait",
|
||||
"background_color": "#141a3a",
|
||||
"theme_color": "#141a3a",
|
||||
"icons": [
|
||||
{ "src": "icon-192.png", "sizes": "192x192", "type": "image/png" },
|
||||
{ "src": "icon-512.png", "sizes": "512x512", "type": "image/png" }
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user