[
  {
    "path": ".editorconfig",
    "content": "# EditorConfig is awesome: https://EditorConfig.org\n\n# top-most EditorConfig file\nroot = true\n\n# Unix-style newlines with a newline ending every file\n[*]\nend_of_line = lf\ninsert_final_newline = true\ncharset = utf-8\nindent_style = space\nindent_size = 2\n"
  },
  {
    "path": ".gitignore",
    "content": "__xx/*\r\n"
  },
  {
    "path": ".hintrc",
    "content": "{\n  \"extends\": [\n    \"development\"\n  ],\n  \"hints\": {\n    \"axe/forms\": \"off\",\n    \"meta-viewport\": \"off\",\n    \"axe/language\": \"off\",\n    \"no-inline-styles\": \"off\"\n  }\n}"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License\n\nCopyright 2018 Corsair Memory, Inc\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "\n# Philips Hue Plugin for Elgato Stream Deck \n# UPDATE INFORMATION\n\n## There's a new version of the Philips Hue plugin available on the [Elgato Marketplace](https://marketplace.elgato.com/product/philips-hue-27f49792-2de3-455f-8892-fd382716f548).\n\nYou'll find that version [here](https://marketplace.elgato.com/product/philips-hue-27f49792-2de3-455f-8892-fd382716f548)\n\nNew discussions/support will move to [Discord](https://discord.gg/elgato)\n\nThe new version of the plugin should work with all recent Philips Hue Bridges (sold like 6-7 years ago).\n\n**If you have the original plugin installed, please, first install the update to the old plugin** [Philips-Hue original plugin 1.6.8](https://github.com/user-attachments/files/15746596/com.elgato.philips-hue.streamDeckPlugin.zip), or remove it - otherwise your sidebar gets mixed up...\n\nYou can install both versions (old and new) in parallel without problems, but it has no benefits. The new version is far more capable than the legacy version.\n\n<img width=\"952\" alt=\"CleanShot_2024-06-08_08 35 59\" src=\"https://github.com/elgatosf/streamdeck-philipshue/assets/80752/621322d9-5e19-42fb-aec4-7fce9a11aac6\">\n\n\n----\n\n(The notes below are kept for reference)\n\n## Philips Hue Plugin for Elgato Stream Deck (legacy version)\nThis sample plugin allows controlling `Philips Hue` lights in your network. It's a demonstration of the [Stream Deck SDK](https://developer.elgato.com/documentation/stream-deck/).\nSince version 1.6.0, it also supports the Stream Deck +. The brightness-action contains an example of how to change the display of the dial-control's touch-panel.\n\n## Version 1.6.7 is also available in the Stream Deck Store!\n\n## Features\n- Code written in JavaScript\n- Cross-platform (macOS, Windows)\n- Localized\n- Basic support for Stream Deck +\n\n![](screenshot.png)\n\n\n# Installation\nIn the [Release](./Release) folder, you can find the file `com.elgato.philips-hue.streamDeckPlugin`. If you double-click this file on your machine, Stream Deck will install the plugin.\n\n\n# Source code\nThe [Sources](./Sources) folder contains the source code of the plugin.\n\n# Changes\n## 1.6.4\n- fixed/improved support for temperature actions\n- PI now lets you only select lights for a temperature action if they support color temperature\n\n## 1.6.3\n- updated CSS to the latest versions of our SDK-libs\n- added an option to the PI to allow larger steps if you rotate dials (1,2,3,4,5,10).\n\n## 1.6.0\n- fixed broken localizations\n- changed versioning to semver\n- added basic support for Stream Deck +\n\n# How it works (since 1.6.0)\n![](touchpanel.png)\n\n You can now drag a brightness-/ or temperature-action to a SD+ dial-control. It supports these actions:\n - Turn the dial to change the brightness/temperature\n - Press the dial to:\n - - set the brightness/temperature to the configured value - if the light is on\n - - turn the light on - if the light is off\n - Long-Press the dial to toggle the light on/off\n - Tap the touch-panel to toggle the light on/off\n  \n DialStacks are not properly supported yet.\n\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/de.json",
    "content": "{\n  \"Name\": \"Philips Hue\",\n  \"Category\": \"Philips Hue\",\n  \"Description\": \"Steuere deine Philips Hue-Lampen.\",\n  \"com.elgato.philips-hue.power\": {\n    \"Name\": \"Ein/Aus\",\n    \"Tooltip\": \"Schalte das Licht ein oder aus.\",\n    \"States\": [\n      {\n        \"Name\": \"Ein\"\n      },\n      {\n        \"Name\": \"Aus\"\n      }\n    ]\n  },\n  \"com.elgato.philips-hue.color\": {\n    \"Name\": \"Farbe\",\n    \"Tooltip\": \"Stelle die Farbe des Lichts ein.\"\n  },\n  \"com.elgato.philips-hue.cycle\": {\n    \"Name\": \"Farben durchschalten\",\n    \"Tooltip\": \"Wechsle zwischen mehreren Lichtfarben.\"\n  },\n  \"com.elgato.philips-hue.brightness\": {\n    \"Name\": \"Helligkeit\",\n    \"Tooltip\": \"Stelle die Helligkeit des Lichts ein.\",\n    \"Encoder\": {\n      \"TriggerDescription\": {\n        \"Rotate\": \"Helligkeit anpassen\",\n        \"Touch\": \"Licht ein/aus\"\n      }\n    }\n  },\n  \"com.elgato.philips-hue.brightness-rel\": {\n    \"Name\": \"Relative Helligkeit\",\n    \"Tooltip\": \"Stelle die Helligkeit des Lichts relativ zur aktuellen Helligkeit ein.\"\n  },\n  \"com.elgato.philips-hue.scene\": {\n    \"Name\": \"Szene\",\n    \"Tooltip\": \"Stelle eine Szene ein.\"\n  },\n  \"Localization\": {\n    \"PI\": {\n      \"Bridge\": \"Bridge\",\n      \"NoBridges\": \"Keine Bridges\",\n      \"AddBridge\": \"Neue hinzufügen\",\n      \"Lights\": \"Lampen\",\n      \"Group\": \"Gruppe\",\n      \"LightsTitle\": \"Lampen\",\n      \"GroupsTitle\": \"Gruppen\",\n      \"NoLights\": \"Keine Lampen\",\n      \"NoGroups\": \"Keine Gruppen\",\n      \"Color\": \"Farbe\",\n      \"Colors\": \"Farben\",\n      \"Temperature\": \"Temperatur\",\n      \"Brightness\": \"Helligkeit\",\n      \"Steps\": \"Schritte\",\n      \"Scene\": \"Szene\",\n      \"NoScenes\": \"Keine Szenen\"\n    },\n    \"Setup\": {\n      \"Intro\": {\n        \"Title\": \"Bridge hinzufügen\",\n        \"Description\": \"Mit einer Philips Hue-Bridge koppeln, um deine Lichter zu steuern.\",\n        \"Start\": \"Bridges suchen\",\n        \"Manual\": \"Bridge manuell hinzufügen\",\n        \"Close\": \"Nicht jetzt\"\n      },\n      \"Discovery\": {\n        \"Title\": \"Bridges werden gesucht …\",\n        \"TitleNone\": \"Keine Bridges gefunden\",\n        \"TitleOne\": \"Eine Bridge gefunden\",\n        \"TitleMultiple\": \"{{ number }} Bridges gefunden\",\n        \"DescriptionFound\": \"Jetzt koppeln?\",\n        \"DescriptionNone\": \"Vergewissere dich, dass die Bridge eingeschaltet und mit dem Netzwerk verbunden ist.\",\n        \"Pair\": \"Koppeln\",\n        \"Close\": \"Schließen\",\n        \"Retry\": \"Erneut versuchen\"\n      },\n      \"Manual\": {\n        \"Title\": \"Bridge manuell hinzufügen\",\n        \"Description\": \"Füge deine Philips Hue-Bridge deinem Netzwerk manuell über die IP-Adresse hinzu, falls Auto-Discovery nicht unterstützt wird.\",\n        \"IPAddress\": \"IP-Adresse der Bridge\",\n        \"Check\": \"Eingabe überprüfen\",\n        \"Close\": \"Schließen\",\n        \"Error\": {\n          \"Empty\": \"Bitte trage eine IP-Adresse in das Eingabefeld ein.\",\n          \"Invalid\": \"Die eingetragene IP-Adresse hat ein ungültiges Format. Bitte überprüfe deine Eingabe.\",\n          \"Unreachable\": \"Die Bridge ist unter der IP-Adresse nicht erreichbar. Bitte überprüfe die IP-Adresse.\"\n        }\n      },\n      \"Pairing\": {\n        \"Title\": \"Wird gekoppelt …\",\n        \"Description\": \"Drücke auf der Bridge jetzt die Taste zum Verbinden.\",\n        \"Close\": \"Schließen\",\n        \"Retry\": \"Erneut versuchen\"\n      },\n      \"Save\": {\n        \"Title\": \"Bridge hinzugefügt\",\n        \"Description\": \"Die Bridge wurde erfolgreich hinzugefügt.\",\n        \"Save\": \"Fertig\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/en.json",
    "content": "{\n  \"Name\": \"Philips Hue\",\n  \"Category\": \"Philips Hue\",\n  \"Description\": \"Control your Philips Hue lights.\",\n  \"com.elgato.philips-hue.power\": {\n    \"Name\": \"On / Off\",\n    \"Tooltip\": \"Turn lights on or off.\",\n    \"States\": [{\n      \"Name\": \"On\"\n    },\n    {\n      \"Name\": \"Off\"\n    }]\n  },\n  \"com.elgato.philips-hue.color\": {\n    \"Name\": \"Color\",\n    \"Tooltip\": \"Set the color of your light.\"\n  },\n  \"com.elgato.philips-hue.cycle\": {\n    \"Name\": \"Color Cycle\",\n    \"Tooltip\": \"Cycle between colors of your light.\"\n  },\n  \"com.elgato.philips-hue.brightness\": {\n    \"Name\": \"Brightness\",\n    \"Tooltip\": \"Set the brightness of your light.\",\n    \"Encoder\": {\n      \"TriggerDescription\": {\n          \"Rotate\": \"Adjust Brightness\",\n          \"Touch\": \"Toggle Lights on/off\"\n      }\n    }\n  },\n  \"com.elgato.philips-hue.brightness-rel\": {\n    \"Name\": \"Brightness Relative\",\n    \"Tooltip\": \"Adjust the brightness of the light relative to the current brightness.\"\n  },\n  \"com.elgato.philips-hue.scene\": {\n    \"Name\": \"Scene\",\n    \"Tooltip\": \"Set a scene.\"\n  },\n  \"Localization\": {\n    \"PI\": {\n      \"Bridge\": \"Bridge\",\n      \"NoBridges\": \"No Bridges\",\n      \"AddBridge\": \"Add New\",\n      \"Lights\": \"Lights\",\n      \"Group\": \"Group\",\n      \"LightsTitle\": \"Lights\",\n      \"GroupsTitle\": \"Groups\",\n      \"NoLights\": \"No Lights\",\n      \"NoGroups\": \"No Groups\",\n      \"Color\": \"Color\",\n      \"Colors\": \"Colors\",\n      \"Temperature\": \"Temperature\",\n      \"Brightness\": \"Brightness\",\n      \"Steps\": \"Steps\",\n      \"Scene\": \"Scene\",\n      \"NoScenes\": \"No Scenes\"\n    },\n    \"Setup\": {\n      \"Intro\": {\n        \"Title\" : \"Add Bridge\",\n        \"Description\": \"Pair with a Philips Hue bridge to control your lights.\",\n        \"Start\": \"Discover Bridges\",\n        \"Manual\": \"Add Bridge manually\",\n        \"Close\": \"Not Now\"\n      },\n      \"Discovery\": {\n        \"Title\" : \"Discovering Bridges …\",\n        \"TitleNone\" : \"No Bridges Found\",\n        \"TitleOne\" : \"One Bridge Found\",\n        \"TitleMultiple\" : \"{{ number }} Bridges Found\",\n        \"DescriptionFound\": \"Start pairing now?\",\n        \"DescriptionNone\": \"Make sure your bridge is switched on and connected to the network.\",\n        \"Pair\": \"Pair\",\n        \"Close\": \"Close\",\n        \"Retry\": \"Try Again\"\n      },\n      \"Manual\": {\n        \"Title\": \"Add bridge manually\",\n        \"Description\": \"Add your Philips Hue-Bridge manually (via IP-Address) to your network, in cases where Auto-Discovery is not supported.\",\n        \"IPAddress\": \"IP address of the bridge\",\n        \"Check\": \"Validate input\",\n        \"Close\": \"Close\",\n        \"Error\": {\n          \"Empty\": \"Please add the ip address of your bridge.\",\n          \"Invalid\": \"The entered IP address has an invalid format. Please check your input.\",\n          \"Unreachable\": \"Bridge is not reachable. Please check the ip address.\"\n        }\n      },\n      \"Pairing\": {\n        \"Title\" : \"Pairing...\",\n        \"Description\": \"Please press the link button on the bridge now.\",\n        \"Close\": \"Close\",\n        \"Retry\": \"Try Again\"\n      },\n      \"Save\": {\n        \"Title\" : \"Bridge Added\",\n        \"Description\": \"The bridge has been paired sucessfully.\",\n        \"Save\": \"Done\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/es.json",
    "content": "{\n  \"Name\": \"Philips Hue\",\n  \"Category\": \"Philips Hue\",\n  \"Description\": \"Controla tus luces Philips Hue.\",\n  \"com.elgato.philips-hue.power\": {\n    \"Name\": \"On / Off\",\n    \"Tooltip\": \"Encender o apagar las luces.\",\n    \"States\": [\n      {\n        \"Name\": \"On\"\n      },\n      {\n        \"Name\": \"Off\"\n      }\n    ]\n  },\n  \"com.elgato.philips-hue.color\": {\n    \"Name\": \"Color\",\n    \"Tooltip\": \"Ajusta el color de tu luz.\"\n  },\n  \"com.elgato.philips-hue.cycle\": {\n    \"Name\": \"Transición de color\",\n    \"Tooltip\": \"Hacer transición entre los colores de tu luz.\"\n  },\n  \"com.elgato.philips-hue.brightness\": {\n    \"Name\": \"Brillo\",\n    \"Tooltip\": \"Ajusta el brillo de tu luz.\",\n    \"Encoder\": {\n      \"TriggerDescription\": {\n        \"Rotate\": \"Ajustar brillo\",\n        \"Touch\": \"Encender/apagar luces\"\n      }\n    }\n  },\n  \"com.elgato.philips-hue.brightness-rel\": {\n    \"Name\": \"Brillo relativo\",\n    \"Tooltip\": \"Ajusta el brillo de la luz con respecto al brillo actual.\"\n  },\n  \"com.elgato.philips-hue.scene\": {\n    \"Name\": \"Escena\",\n    \"Tooltip\": \"Activa una escena.\"\n  },\n  \"Localization\": {\n    \"PI\": {\n      \"Bridge\": \"Bridge\",\n      \"NoBridges\": \"No hay Bridges\",\n      \"AddBridge\": \"Añadir\",\n      \"Lights\": \"Luces\",\n      \"Group\": \"Grupo\",\n      \"LightsTitle\": \"Luces\",\n      \"GroupsTitle\": \"Grupos\",\n      \"NoLights\": \"No hay luces\",\n      \"NoGroups\": \"No hay grupos\",\n      \"Color\": \"Color\",\n      \"Colors\": \"Colores\",\n      \"Temperature\": \"Temperatura\",\n      \"Brightness\": \"Brillo\",\n      \"Steps\": \"Pasos\",\n      \"Scene\": \"Escena\",\n      \"NoScenes\": \"No hay escenas\"\n    },\n    \"Setup\": {\n      \"Intro\": {\n        \"Title\": \"Añadir Bridge\",\n        \"Description\": \"Enlaza con un Bridge Philips Hue para controlar tus luces.\",\n        \"Start\": \"Detectar Bridge\",\n        \"Manual\": \"Añadir Bridge a mano\",\n        \"Close\": \"Ahora no\"\n      },\n      \"Discovery\": {\n        \"Title\": \"Detectando Bridges …\",\n        \"TitleNone\": \"No se han detectado Bridges\",\n        \"TitleOne\": \"Se ha detectado un Bridge\",\n        \"TitleMultiple\": \"{{ number }} Bridges detectados\",\n        \"DescriptionFound\": \"¿Empezar a enlazar ahora?\",\n        \"DescriptionNone\": \"Comprueba que el Bridge está encendido y conectado a la red.\",\n        \"Pair\": \"Enlazar\",\n        \"Close\": \"Cerrar\",\n        \"Retry\": \"Volver a intentar\"\n      },\n      \"Manual\": {\n        \"Title\": \"Añadir Bridge a mano\",\n        \"Description\": \"Añade tu Philips Hue Bridge manualmente (mediante la dirección IP) a tu red, en los casos en los que la detección automática no se puede usar.\",\n        \"IPAddress\": \"Dirección IP del Bridge\",\n        \"Check\": \"Validar entrada\",\n        \"Close\": \"Cerrar\",\n        \"Error\": {\n          \"Empty\": \"Añade la dirección IP de tu Bridge.\",\n          \"Invalid\": \"El formato de la dirección IP no es válido. Comprueba lo que has introducido.\",\n          \"Unreachable\": \"Nose puede conectar con el Bridge. Comprueba la dirección IP.\"\n        }\n      },\n      \"Pairing\": {\n        \"Title\": \"Enlazando...\",\n        \"Description\": \"Pulsa ahora el botón de enlace en el Bridge.\",\n        \"Close\": \"Cerrar\",\n        \"Retry\": \"Volver a intentar\"\n      },\n      \"Save\": {\n        \"Title\": \"Bridge añadido\",\n        \"Description\": \"El Bridge se ha enlazado correctamente.\",\n        \"Save\": \"OK\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/fr.json",
    "content": "{\n  \"Name\": \"Philips Hue\",\n  \"Category\": \"Philips Hue\",\n  \"Description\": \"Contrôlez vos systèmes d’éclairage Philips Hue.\",\n  \"com.elgato.philips-hue.power\": {\n    \"Name\": \"Activer/Désactiver\",\n    \"Tooltip\": \"Allumez ou éteignez les lumières.\",\n    \"States\": [\n      {\n        \"Name\": \"Activer\"\n      },\n      {\n        \"Name\": \"Désactiver\"\n      }\n    ]\n  },\n  \"com.elgato.philips-hue.color\": {\n    \"Name\": \"Couleur\",\n    \"Tooltip\": \"Réglez la couleur de la lumière.\"\n  },\n  \"com.elgato.philips-hue.cycle\": {\n    \"Name\": \"Cycle de couleurs\",\n    \"Tooltip\": \"Activez les différentes couleurs de votre lumière de façon cyclique.\"\n  },\n  \"com.elgato.philips-hue.brightness\": {\n    \"Name\": \"Luminosité\",\n    \"Tooltip\": \"Réglez la luminosité de la lumière.\",\n    \"Encoder\": {\n      \"TriggerDescription\": {\n        \"Rotate\": \"Ajuster la luminosité\",\n        \"Touch\": \"Allumer ou éteindre les lumières\"\n      }\n    }\n  },\n  \"com.elgato.philips-hue.brightness-rel\": {\n    \"Name\": \"Luminosité relative\",\n    \"Tooltip\": \"Permet de régler la luminosité de l’éclairage par rapport à la luminosité actuelle.\"\n  },\n  \"com.elgato.philips-hue.scene\": {\n    \"Name\": \"Scène\",\n    \"Tooltip\": \"Définissez une scène.\"\n  },\n  \"Localization\": {\n    \"PI\": {\n      \"Bridge\": \"Pont\",\n      \"NoBridges\": \"Aucun pont\",\n      \"AddBridge\": \"Ajouter\",\n      \"Lights\": \"Lumières\",\n      \"Group\": \"Groupe\",\n      \"LightsTitle\": \"Lumières\",\n      \"GroupsTitle\": \"Groupes\",\n      \"NoLights\": \"Aucune lumière\",\n      \"NoGroups\": \"Aucun groupe\",\n      \"Color\": \"Couleur\",\n      \"Colors\": \"Couleurs\",\n      \"Temperature\": \"Température\",\n      \"Brightness\": \"Luminosité\",\n      \"Steps\": \"Pas\",\n      \"Scene\": \"Scène\",\n      \"NoScenes\": \"Aucune scène\"\n    },\n    \"Setup\": {\n      \"Intro\": {\n        \"Title\": \"Ajouter un pont\",\n        \"Description\": \"Jumelez l’application à un pont Philips Hue pour contrôler vos lumières.\",\n        \"Start\": \"Découvrir des ponts\",\n        \"Manual\": \"Ajouter un pont manuellement\",\n        \"Close\": \"Pas maintenant\"\n      },\n      \"Discovery\": {\n        \"Title\": \"Découverte des ponts…\",\n        \"TitleNone\": \"Aucun pont trouvé\",\n        \"TitleOne\": \"1 pont trouvé\",\n        \"TitleMultiple\": \"{{ number }} ponts trouvés\",\n        \"DescriptionFound\": \"Lancer le jumelage ?\",\n        \"DescriptionNone\": \"Vérifiez que votre pont est allumé et connecté au réseau.\",\n        \"Pair\": \"Jumeler\",\n        \"Close\": \"Fermer\",\n        \"Retry\": \"Réessayer\"\n      },\n      \"Manual\": {\n        \"Title\": \"Ajouter un pont manuellement\",\n        \"Description\": \"Ajoutez votre pont Philips Hue manuellement (à partir de son adresse IP) à votre réseau, au cas où la découverte automatique serait impossible.\",\n        \"IPAddress\": \"Adresse IP du pont\",\n        \"Check\": \"Valider la saisie\",\n        \"Close\": \"Fermer\",\n        \"Error\": {\n          \"Empty\": \"Veuillez ajouter l’adresse IP de votre pont.\",\n          \"Invalid\": \"Le format de l’adresse IP saisie n’est pas valide. Veuillez vérifier votre saisie.\",\n          \"Unreachable\": \"Le pont est injoignable. Veuillez vérifier l’adresse IP.\"\n        }\n      },\n      \"Pairing\": {\n        \"Title\": \"Jumelage…\",\n        \"Description\": \"Veuillez appuyer sur le bouton de jumelage du pont.\",\n        \"Close\": \"Fermer\",\n        \"Retry\": \"Réessayer\"\n      },\n      \"Save\": {\n        \"Title\": \"Pont ajouté\",\n        \"Description\": \"Le pont a bien été jumelé à l’application.\",\n        \"Save\": \"Terminé\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/ja.json",
    "content": "{\n  \"Name\": \"Philips Hue\",\n  \"Category\": \"Philips Hue\",\n  \"Description\": \"Philips Hueの照明をコントロールします。\",\n  \"com.elgato.philips-hue.power\": {\n    \"Name\": \"オン/オフ\",\n    \"Tooltip\": \"照明をオン、またはオフにします。\",\n    \"States\": [\n      {\n        \"Name\": \"オン\"\n      },\n      {\n        \"Name\": \"オフ\"\n      }\n    ]\n  },\n  \"com.elgato.philips-hue.color\": {\n    \"Name\": \"カラー\",\n    \"Tooltip\": \"照明のカラーを設定します。\"\n  },\n  \"com.elgato.philips-hue.cycle\": {\n    \"Name\": \"カラーサイクル\",\n    \"Tooltip\": \"照明のカラーを循環して表示します。\"\n  },\n  \"com.elgato.philips-hue.brightness\": {\n    \"Name\": \"明るさ\",\n    \"Tooltip\": \"照明の明るさを調整します。\",\n    \"Encoder\": {\n      \"TriggerDescription\": {\n        \"Rotate\": \"明るさを調整\",\n        \"Touch\": \"照明のオン/オフを切り替え\"\n      }\n    }\n  },\n  \"com.elgato.philips-hue.brightness-rel\": {\n    \"Name\": \"明るさ (相対的)\",\n    \"Tooltip\": \"現在の明るさを基準にして照明の明るさを調整します。\"\n  },\n  \"com.elgato.philips-hue.scene\": {\n    \"Name\": \"シーン\",\n    \"Tooltip\": \"シーンを設定します。\"\n  },\n  \"Localization\": {\n    \"PI\": {\n      \"Bridge\": \"ブリッジ\",\n      \"NoBridges\": \"ブリッジがありません\",\n      \"AddBridge\": \"新規を追加\",\n      \"Lights\": \"照明\",\n      \"Group\": \"グループ\",\n      \"LightsTitle\": \"照明\",\n      \"GroupsTitle\": \"グループ\",\n      \"NoLights\": \"照明がありません\",\n      \"NoGroups\": \"グループがありません\",\n      \"Color\": \"カラー\",\n      \"Colors\": \"カラー\",\n      \"Temperature\": \"温度\",\n      \"Brightness\": \"明るさ\",\n      \"Steps\": \"ステップ\",\n      \"Scene\": \"シーン\",\n      \"NoScenes\": \"シーンがありません\"\n    },\n    \"Setup\": {\n      \"Intro\": {\n        \"Title\": \"ブリッジを追加\",\n        \"Description\": \"照明をコントロールするには、Philips Hueのブリッジを使ってペアリングしてください。\",\n        \"Start\": \"ブリッジを検出\",\n        \"Manual\": \"ブリッジを手動で追加\",\n        \"Close\": \"今はしない\"\n      },\n      \"Discovery\": {\n        \"Title\": \"ブリッジを検出中…\",\n        \"TitleNone\": \"ブリッジが見つかりません\",\n        \"TitleOne\": \"１件のブリッジを検出しました\",\n        \"TitleMultiple\": \"{{ number }}件のブリッジを検出しました\",\n        \"DescriptionFound\": \"今すぐペアリングしますか？\",\n        \"DescriptionNone\": \"ブリッジがオンになっていて、ネットワークに接続されていることを確認してください。\",\n        \"Pair\": \"ペアリング\",\n        \"Close\": \"閉じる\",\n        \"Retry\": \"やり直す\"\n      },\n      \"Manual\": {\n        \"Title\": \"ブリッジを手動で追加\",\n        \"Description\": \"自動検出がサポートされていない場合、Philips Hueブリッジをお使いのネットワークに (IPアドレスを使って) 手動で追加してください。\",\n        \"IPAddress\": \"ブリッジのIPアドレス\",\n        \"Check\": \"入力を検証\",\n        \"Close\": \"閉じる\",\n        \"Error\": {\n          \"Empty\": \"ブリッジのIPアドレスを追加してください。\",\n          \"Invalid\": \"入力されたIPアドレスは無効なフォーマットです。入力を確認してください。\",\n          \"Unreachable\": \"ブリッジに接続できません。IPアドレスを確認してください。\"\n        }\n      },\n      \"Pairing\": {\n        \"Title\": \"ペアリング中…\",\n        \"Description\": \"今すぐブリッジの接続ボタンを押してください。\",\n        \"Close\": \"閉じる\",\n        \"Retry\": \"やり直す\"\n      },\n      \"Save\": {\n        \"Title\": \"ブリッジが追加されました\",\n        \"Description\": \"ブリッジのペアリングを完了しました。\",\n        \"Save\": \"完了\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/ko.json",
    "content": "{\n  \"Name\": \"Philips Hue\",\n  \"Category\": \"Philips Hue\",\n  \"Description\": \"Philips Hue 조명을 조종하세요.\",\n  \"com.elgato.philips-hue.power\": {\n    \"Name\": \"켬 / 끔\",\n    \"Tooltip\": \"조명을 끄거나 켭니다.\",\n    \"States\": [\n      {\n        \"Name\": \"켬\"\n      },\n      {\n        \"Name\": \"끔\"\n      }\n    ]\n  },\n  \"com.elgato.philips-hue.color\": {\n    \"Name\": \"색상\",\n    \"Tooltip\": \"조명 색상을 설정하세요.\"\n  },\n  \"com.elgato.philips-hue.cycle\": {\n    \"Name\": \"색상 사이클\",\n    \"Tooltip\": \"조명 색상 사이를 순환합니다.\"\n  },\n  \"com.elgato.philips-hue.brightness\": {\n    \"Name\": \"밝기\",\n    \"Tooltip\": \"조명의 밝기를 조절합니다.\",\n    \"Encoder\": {\n      \"TriggerDescription\": {\n        \"Rotate\": \"밝기 조절\",\n        \"Touch\": \"조명 켬/끔\"\n      }\n    }\n  },\n  \"com.elgato.philips-hue.brightness-rel\": {\n    \"Name\": \"상대적 밝기\",\n    \"Tooltip\": \"현재 밝기에 비례하여 밝기를 조절합니다.\"\n  },\n  \"com.elgato.philips-hue.scene\": {\n    \"Name\": \"장면\",\n    \"Tooltip\": \"장면을 설정합니다.\"\n  },\n  \"Localization\": {\n    \"PI\": {\n      \"Bridge\": \"브릿지\",\n      \"NoBridges\": \"브릿지 없음\",\n      \"AddBridge\": \"신규 추가\",\n      \"Lights\": \"조명\",\n      \"Group\": \"그룹\",\n      \"LightsTitle\": \"조명\",\n      \"GroupsTitle\": \"그룹\",\n      \"NoLights\": \"조명 없음\",\n      \"NoGroups\": \"그룹 없음\",\n      \"Color\": \"색상\",\n      \"Colors\": \"색상\",\n      \"Temperature\": \"온도\",\n      \"Brightness\": \"밝기\",\n      \"Steps\": \"단계\",\n      \"Scene\": \"장면\",\n      \"NoScenes\": \"장면 없음\"\n    },\n    \"Setup\": {\n      \"Intro\": {\n        \"Title\": \"브릿지 추가\",\n        \"Description\": \"Philips Hue 브릿지와 연결하여 조명을 조절하세요.\",\n        \"Start\": \"브릿지 탐색\",\n        \"Manual\": \"수동으로 브릿지 추가\",\n        \"Close\": \"나중에\"\n      },\n      \"Discovery\": {\n        \"Title\": \"브릿지 탐색 중...\",\n        \"TitleNone\": \"브릿지를 찾을 수 없음\",\n        \"TitleOne\": \"하나의 브릿지 발견\",\n        \"TitleMultiple\": \"{{ number }}개의 브릿지 발견\",\n        \"DescriptionFound\": \"지금 연결하시겠습니까?\",\n        \"DescriptionNone\": \"브릿지가 켜져 있고 네트워크에 연결되어 있는지 확인하세요.\",\n        \"Pair\": \"연결\",\n        \"Close\": \"닫기\",\n        \"Retry\": \"다시 시도하기\"\n      },\n      \"Manual\": {\n        \"Title\": \"수동으로 브릿지 추가\",\n        \"Description\": \"자동 탐색이 지원되지 않는 경우, (IP 주소를 통해) Philips Hue 브릿지를 수동으로 네트워크에 추가하십시오.\",\n        \"IPAddress\": \"브릿지의 IP 주소\",\n        \"Check\": \"입력 유효성 검사\",\n        \"Close\": \"닫기\",\n        \"Error\": {\n          \"Empty\": \"브릿지의 IP 주소를 추가하십시오.\",\n          \"Invalid\": \"입력한 IP 주소가 유효한 형식이 아닙니다. 입력을 확인하십시오.\",\n          \"Unreachable\": \"브릿지에 도달할 수 없습니다. IP 주소를 확인하십시오.\"\n        }\n      },\n      \"Pairing\": {\n        \"Title\": \"연결 중...\",\n        \"Description\": \"지금 브릿지의 링크 버튼을 누르세요.\",\n        \"Close\": \"닫기\",\n        \"Retry\": \"다시 시도하기\"\n      },\n      \"Save\": {\n        \"Title\": \"브릿지 추가됨\",\n        \"Description\": \"브릿지가 성공적으로 연결되었습니다.\",\n        \"Save\": \"완료\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/manifest.json",
    "content": "{\n  \"Actions\": [\n    {\n      \"Icon\": \"plugin/images/actions/power\",\n      \"Name\": \"On / Off\",\n      \"States\": [\n        {\n          \"Image\": \"plugin/images/icons/on\",\n          \"Name\": \"On\"\n        },\n        {\n          \"Image\": \"plugin/images/icons/off\",\n          \"Name\": \"Off\"\n        }\n      ],\n      \"Tooltip\": \"Turn lights on or off.\",\n      \"UUID\": \"com.elgato.philips-hue.power\"\n    },\n    {\n      \"Icon\": \"plugin/images/actions/color\",\n      \"Name\": \"Color\",\n      \"States\": [\n        {\n          \"Image\": \"plugin/images/icons/color\"\n        }\n      ],\n      \"Tooltip\": \"Set the color of your light.\",\n      \"UUID\": \"com.elgato.philips-hue.color\"\n    },\n    {\n      \"Icon\": \"plugin/images/actions/cycle\",\n      \"Name\": \"Color Cycle\",\n      \"States\": [\n        {\n          \"Image\": \"plugin/images/icons/cycle\"\n        }\n      ],\n      \"Tooltip\": \"Cycle between colors of your light.\",\n      \"UUID\": \"com.elgato.philips-hue.cycle\"\n    },\n    {\n      \"Icon\": \"plugin/images/actions/brightness\",\n      \"Name\": \"Brightness\",\n      \"States\": [\n        {\n          \"Image\": \"plugin/images/icons/brightness\"\n        }\n      ],\n      \"Controllers\": [\n        \"Keypad\",\n        \"Encoder\"\n      ],\n      \"Encoder\": {\n        \"layout\": \"$B1\",\n        \"TriggerDescription\": {\n          \"Rotate\": \"Adjust Brightness\",\n          \"Touch\": \"Toggle Light on/off\"\n        }\n      },\n      \"Tooltip\": \"Set the brightness of your light.\",\n      \"UUID\": \"com.elgato.philips-hue.brightness\"\n    },\n    {\n      \"Icon\": \"plugin/images/actions/temperature\",\n      \"Name\": \"Temperature\",\n      \"States\": [\n        {\n          \"Image\": \"plugin/images/icons/temperature\"\n        }\n      ],\n      \"Controllers\": [\n        \"Keypad\",\n        \"Encoder\"\n      ],\n      \"Encoder\": {\n        \"layout\": \"$B1\",\n        \"TriggerDescription\": {\n          \"Rotate\": \"Adjust Temperature\",\n          \"Touch\": \"Toggle Light on/off\"\n        }\n      },\n      \"Tooltip\": \"Set the temperature of your light.\",\n      \"UUID\": \"com.elgato.philips-hue.temperature\"\n    },\n    {\n      \"Icon\": \"plugin/images/actions/brightness\",\n      \"Name\": \"Brightness Relative\",\n      \"States\": [\n        {\n          \"Image\": \"plugin/images/icons/brightness\"\n        }\n      ],\n      \"Tooltip\": \"Set the brightness of your light.\",\n      \"UUID\": \"com.elgato.philips-hue.brightness-rel\"\n    },\n    {\n      \"Icon\": \"plugin/images/actions/scene\",\n      \"Name\": \"Scene\",\n      \"States\": [\n        {\n          \"Image\": \"plugin/images/icons/scene\"\n        }\n      ],\n      \"Tooltip\": \"Set a scene.\",\n      \"UUID\": \"com.elgato.philips-hue.scene\"\n    }\n  ],\n  \"SDKVersion\": 2,\n  \"Author\": \"Elgato\",\n  \"CodePath\": \"plugin/index.html\",\n  \"Description\": \"Control your Philips Hue lights.\",\n  \"Name\": \"Philips Hue\",\n  \"Icon\": \"plugin/images/icons/plugin\",\n  \"Category\": \"Philips Hue\",\n  \"CategoryIcon\": \"plugin/images/actions/category\",\n  \"PropertyInspectorPath\": \"pi/index.html\",\n  \"URL\": \"https://www.elgato.com/gaming/stream-deck\",\n  \"Version\": \"1.6.5\",\n  \"DefaultWindowSize\": [\n    500,\n    650\n  ],\n  \"OS\": [\n    {\n      \"Platform\": \"mac\",\n      \"MinimumVersion\": \"10.11\"\n    },\n    {\n      \"Platform\": \"windows\",\n      \"MinimumVersion\": \"10\"\n    }\n  ],\n  \"Software\": {\n    \"MinimumVersion\": \"5.0\"\n  }\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/css/colorPI.css",
    "content": "/**\n@file      colorPI.css\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\n.temperature::-webkit-slider-runnable-track {\n  background-image: linear-gradient(to right, #faa04e, #86c6e8) !important;\n}\n\n.temperature::-webkit-slider-thumb {\n  background-color: #86c6e8 !important;\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/css/cyclePI.css",
    "content": "/**\n@file      cyclePI.css\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\n#cycle-buttons .sdpi-item-value {\n  font-size: 0;\n}\n\n#cycle-buttons button {\n  width: 36px;\n  font-weight: 900;\n  display: inline-block;\n  margin: 0 6px 0 2px;\n  padding: 0;\n}\n\n#color-input-container div.sdpi-item-value {\n  font-size: 0;\n}\n\n#color-input-container input[type='color'] {\n  margin: 0 4px 0 0;\n  padding: 0;\n  width: 40px;\n}\n\n#color-input-container span:nth-child(5n) input {\n  margin-right: 0;\n}\n\n#color-input-container span:nth-child(5n):after {\n  content: ' ';\n  display: block;\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/css/pi.css",
    "content": "/**\n@file      pi.css\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\n#pi {\n  display: none;\n}\n\n#placeholder {\n  margin-top: 3px;\n}\n\nselect option:disabled {\n  font-size: 1pt;\n  background-color: #505050;\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/css/sdpi.css",
    "content": ":root {\n  --sdpi-bgcolor: #2D2D2D;\n  --sdpi-background: #3D3D3D;\n  --sdpi-color: #d8d8d8;\n  --sdpi-bordercolor: #3a3a3a;\n  --sdpi-buttonbordercolor: #969696;\n  --sdpi-borderradius: 0px;\n  --sdpi-width: 224px;\n  --sdpi-fontweight: 600;\n  --sdpi-letterspacing: -0.25pt;\n}\n\nhtml {\n  --sdpi-bgcolor: #2D2D2D;\n  --sdpi-background: #3D3D3D;\n  --sdpi-color: #d8d8d8;\n  --sdpi-bordercolor: #3a3a3a;\n  --sdpi-buttonbordercolor: #969696;\n  --sdpi-borderradius: 0px;\n  --sdpi-width: 224px;\n  --sdpi-fontweight: 600;\n  --sdpi-letterspacing: -0.25pt;\n  height: 100%;\n  width: 100%;\n  overflow: hidden;\n  touch-action: none;\n}\n\nhtml,\nbody {\n  font-family: system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n  font-size: 9pt;\n  background-color: var(--sdpi-bgcolor);\n  color: #9a9a9a;\n}\n\nbody {\n  height: 100%;\n  padding: 0;\n  overflow-x: hidden;\n  overflow-y: auto;\n  margin: 0;\n  -webkit-overflow-scrolling: touch;\n  -webkit-text-size-adjust: 100%;\n  -webkit-font-smoothing: antialiased;\n}\n\nmark {\n  background-color: var(--sdpi-bgcolor);\n  color: var(--sdpi-color);\n}\n\nhr,\nhr2 {\n  -webkit-margin-before: 1em;\n  -webkit-margin-after: 1em;\n  border-style: none;\n  background: var(--sdpi-background);\n  height: 1px;\n}\n\nhr2,\n.sdpi-heading {\n  display: flex;\n  flex-basis: 100%;\n  align-items: center;\n  color: inherit;\n  font-size: 9pt;\n  margin: 8px 0px;\n}\n\n.sdpi-heading::before,\n.sdpi-heading::after {\n  content: \"\";\n  flex-grow: 1;\n  background: var(--sdpi-background);\n  height: 1px;\n  font-size: 0px;\n  line-height: 0px;\n  margin: 0px 16px;\n}\n\nhr2 {\n  height: 2px;\n}\n\nhr,\nhr2 {\n  margin-left: 16px;\n  margin-right: 16px;\n}\n\n.sdpi-item-value,\noption,\ninput,\nselect,\nbutton {\n  font-size: 10pt;\n  font-weight: var(--sdpi-fontweight);\n  letter-spacing: var(--sdpi-letterspacing);\n}\n\n.sdpi-item-value > :last-of-type,\n.sdpi-item-value:last-child {\n  margin-bottom: 4px;\n}\n\n.win .sdpi-item-value,\n.win option,\n.win input,\n.win select,\n.win button {\n  font-size: 11px;\n  font-style: normal;\n  letter-spacing: inherit;\n  font-weight: 100;\n}\n\n.win button {\n  font-size: 12px;\n}\n\n::-webkit-progress-value,\nmeter::-webkit-meter-optimum-value {\n  border-radius: 2px;\n  /* background: linear-gradient(#ccf, #99f 20%, #77f 45%, #77f 55%, #cdf); */\n}\n\n::-webkit-progress-bar,\nmeter::-webkit-meter-bar {\n  border-radius: 3px;\n  background: var(--sdpi-background);\n}\n\n::-webkit-progress-bar:active,\nmeter::-webkit-meter-bar:active {\n  border-radius: 3px;\n  background: #222222;\n}\n\n::-webkit-progress-value:active,\nmeter::-webkit-meter-optimum-value:active {\n  background: #99f;\n}\n\nprogress,\nprogress.sdpi-item-value {\n  min-height: 5px !important;\n  height: 5px;\n  background-color: #303030;\n}\n\nprogress {\n  margin-top: 8px !important;\n  margin-bottom: 8px !important;\n}\n\n.full progress,\nprogress.full {\n  margin-top: 3px !important;\n}\n\n::-webkit-progress-inner-element {\n  background-color: transparent;\n}\n\n\n.sdpi-item[type=\"progress\"] {\n  margin-top: 4px !important;\n  margin-bottom: 12px;\n  min-height: 15px;\n}\n\n.sdpi-item-child.full:last-child {\n  margin-bottom: 4px;\n}\n\n.tabs {\n  /**\n * Setting display to flex makes this container lay\n * out its children using flexbox, the exact same\n * as in the above \"Stepper input\" example.\n */\n  display: flex;\n\n  border-bottom: 1px solid #D7DBDD;\n}\n\n.tab {\n  cursor: pointer;\n  padding: 5px 30px;\n  color: #16a2d7;\n  font-size: 9pt;\n  border-bottom: 2px solid transparent;\n}\n\n.tab.is-tab-selected {\n  border-bottom-color: #4ebbe4;\n}\n\nselect {\n  -webkit-appearance: none;\n  -moz-appearance: none;\n  -o-appearance: none;\n  appearance: none;\n  background: url(../assets/caret.svg) no-repeat 97% center;\n}\n\nlabel.sdpi-file-label,\ninput[type=\"button\"],\ninput[type=\"submit\"],\ninput[type=\"reset\"],\ninput[type=\"file\"],\ninput[type=file]::-webkit-file-upload-button,\nbutton,\nselect {\n  color: var(--sdpi-color);\n  border: 1pt solid #303030;\n  font-size: 8pt;\n  background-color: var(--sdpi-background);\n  border-radius: var(--sdpi-borderradius);\n}\n\nlabel.sdpi-file-label,\ninput[type=\"button\"],\ninput[type=\"submit\"],\ninput[type=\"reset\"],\ninput[type=\"file\"],\ninput[type=file]::-webkit-file-upload-button,\nbutton {\n  border: 1pt solid var(--sdpi-buttonbordercolor);\n  border-radius: var(--sdpi-borderradius);\n  border-color: var(--sdpi-buttonbordercolor);\n  min-height: 23px !important;\n  height: 23px !important;\n  margin-right: 8px;\n}\n\ninput[type=number]::-webkit-inner-spin-button,\ninput[type=number]::-webkit-outer-spin-button {\n  -webkit-appearance: none;\n  margin: 0;\n}\n\ninput[type=\"file\"] {\n  border-radius: var(--sdpi-borderradius);\n  max-width: 220px;\n}\n\noption {\n  height: 1.5em;\n  padding: 4px;\n}\n\n/* SDPI */\n\n.sdpi-wrapper {\n  overflow-x: hidden;\n  height: 100%;\n}\n\n.sdpi-item {\n  display: flex;\n  flex-direction: row;\n  min-height: 32px;\n  align-items: center;\n  margin-top: 2px;\n  max-width: 344px;\n  -webkit-user-drag: none;\n}\n\n.sdpi-item:first-child {\n  margin-top: -1px;\n}\n\n.sdpi-item:first-of-type {\n  margin-top: 2px;\n}\n\n.sdpi-item[type=\"radio\"]:first-of-type,\n.sdpi-item[type=\"checkbox\"]:first-of-type {\n  margin-top: -4px;\n}\n\n.sdpi-item:last-child {\n  margin-bottom: 0px;\n}\n\n.sdpi-item > *:not(.sdpi-item-label):not(meter):not(details):not(canvas) {\n  min-height: 26px;\n}\n\n.sdpi-item > *:not(.sdpi-item-label.empty):not(meter) {\n  min-height: 26px;\n}\n\n.sdpi-item > input {\n  padding: 0px 4px;\n}\n\n.sdpi-item-group {\n  padding: 0 !important;\n}\n\nmeter.sdpi-item-value {\n  margin-left: 6px;\n}\n\n.sdpi-item[type=\"group\"] {\n  display: block;\n  margin-top: 12px;\n  margin-bottom: 12px;\n  /* border: 1px solid white; */\n  flex-direction: unset;\n  text-align: left;\n}\n\n.sdpi-item[type=\"group\"] > .sdpi-item-label,\n.sdpi-item[type=\"group\"].sdpi-item-label {\n  width: 96%;\n  text-align: left;\n  font-weight: 700;\n  margin-bottom: 4px;\n  padding-left: 4px;\n}\n\ndl,\nul,\nol {\n  -webkit-margin-before: 0px;\n  -webkit-margin-after: 4px;\n  -webkit-padding-start: 1em;\n  max-height: 90px;\n  overflow-y: scroll;\n  cursor: pointer;\n  user-select: none;\n}\n\ntable.sdpi-item-value,\ndl.sdpi-item-value,\nul.sdpi-item-value,\nol.sdpi-item-value {\n  -webkit-margin-before: 4px;\n  -webkit-margin-after: 8px;\n  -webkit-padding-start: 1em;\n  width: var(--sdpi-width);\n  text-align: center;\n}\n\ntable > caption {\n  margin: 2px;\n}\n\n.list,\n.sdpi-item[type=\"list\"] {\n  align-items: baseline;\n}\n\n.sdpi-item-label {\n  text-align: right;\n  flex: none;\n  width: 94px;\n  padding-right: 5px;\n  font-weight: 600;\n  -webkit-user-select: none;\n  line-height: 24px;\n  margin-left: -1px;\n}\n\n.win .sdpi-item-label,\n.sdpi-item-label > small {\n  font-weight: normal;\n}\n\n.sdpi-item-label:after {\n  content: \": \";\n}\n\n.sdpi-item-label.empty:after {\n  content: \"\";\n}\n\n.sdpi-test,\n.sdpi-item-value {\n  flex: 1 0 0;\n  /* flex-grow: 1;\nflex-shrink: 0; */\n  margin-right: 14px;\n  margin-left: 4px;\n  justify-content: space-evenly;\n}\n\ncanvas.sdpi-item-value {\n  max-width: 144px;\n  max-height: 144px;\n  width: 144px;\n  height: 144px;\n  margin: 0 auto;\n  cursor: pointer;\n}\n\ninput.sdpi-item-value {\n  margin-left: 5px;\n}\n\n.sdpi-item-value button,\nbutton.sdpi-item-value {\n  margin-left: 6px;\n  margin-right: 14px;\n}\n\n.sdpi-item-value.range {\n  margin-left: 0px;\n}\n\ntable,\ndl.sdpi-item-value,\nul.sdpi-item-value,\nol.sdpi-item-value,\n.sdpi-item-value > dl,\n.sdpi-item-value > ul,\n.sdpi-item-value > ol {\n  list-style-type: none;\n  list-style-position: outside;\n  margin-left: -4px;\n  margin-right: -4px;\n  padding: 4px;\n  border: 1px solid var(--sdpi-bordercolor);\n}\n\ndl.sdpi-item-value,\nul.sdpi-item-value,\nol.sdpi-item-value,\n.sdpi-item-value > ol {\n  list-style-type: none;\n  list-style-position: inside;\n  margin-left: 5px;\n  margin-right: 12px;\n  padding: 4px !important;\n  display: flex;\n  flex-direction: column;\n}\n\n.two-items li {\n  display: flex;\n}\n\n.two-items li > *:first-child {\n  flex: 0 0 50%;\n  text-align: left;\n}\n\n.two-items.thirtyseventy li > *:first-child {\n  flex: 0 0 30%;\n}\n\nol.sdpi-item-value,\n.sdpi-item-value > ol[listtype=\"none\"] {\n  list-style-type: none;\n}\n\nol.sdpi-item-value[type=\"decimal\"],\n.sdpi-item-value > ol[type=\"decimal\"] {\n  list-style-type: decimal;\n}\n\nol.sdpi-item-value[type=\"decimal-leading-zero\"],\n.sdpi-item-value > ol[type=\"decimal-leading-zero\"] {\n  list-style-type: decimal-leading-zero;\n}\n\nol.sdpi-item-value[type=\"lower-alpha\"],\n.sdpi-item-value > ol[type=\"lower-alpha\"] {\n  list-style-type: lower-alpha;\n}\n\nol.sdpi-item-value[type=\"upper-alpha\"],\n.sdpi-item-value > ol[type=\"upper-alpha\"] {\n  list-style-type: upper-alpha;\n}\n\nol.sdpi-item-value[type=\"upper-roman\"],\n.sdpi-item-value > ol[type=\"upper-roman\"] {\n  list-style-type: upper-roman;\n}\n\nol.sdpi-item-value[type=\"lower-roman\"],\n.sdpi-item-value > ol[type=\"lower-roman\"] {\n  list-style-type: upper-roman;\n}\n\ntr:nth-child(even),\n.sdpi-item-value > ul > li:nth-child(even),\n.sdpi-item-value > ol > li:nth-child(even),\nli:nth-child(even) {\n  background-color: rgba(0, 0, 0, .2)\n}\n\ntd:hover,\n.sdpi-item-value > ul > li:hover:nth-child(even),\n.sdpi-item-value > ol > li:hover:nth-child(even),\nli:hover:nth-child(even),\nli:hover {\n  background-color: rgba(255, 255, 255, .1);\n}\n\ntd.selected,\ntd.selected:hover,\nli.selected:hover,\nli.selected {\n  color: white;\n  background-color: #77f;\n}\n\ntr {\n  border: 1px solid var(--sdpi-bordercolor);\n}\n\ntd {\n  border-right: 1px solid var(--sdpi-bordercolor);\n  -webkit-user-select: none;\n}\n\ntr:last-child,\ntd:last-child {\n  border: none;\n}\n\n.sdpi-item-value.select,\n.sdpi-item-value > select {\n  margin-right: 13px;\n  margin-left: 4px;\n  padding: 0px 4px;\n}\n\n.sdpi-item-child,\n.sdpi-item-group > .sdpi-item > input[type=\"color\"] {\n  margin-top: 0.4em;\n  margin-right: 4px;\n  margin-left: 4px;\n}\n\n.full,\n.full *,\n.sdpi-item-value.full,\n.sdpi-item-child > full > *,\n.sdpi-item-child.full,\n.sdpi-item-child.full > *,\n.full > .sdpi-item-child,\n.full > .sdpi-item-child > * {\n  display: flex;\n  flex: 1 1 0;\n  margin-bottom: 4px;\n  margin-left: 0px;\n  width: 100%;\n\n  justify-content: space-evenly;\n}\n\n.sdpi-item-group > .sdpi-item > input[type=\"color\"] {\n  margin-top: 0px;\n}\n\n::-webkit-calendar-picker-indicator:focus,\ninput[type=file]::-webkit-file-upload-button:focus,\nbutton:focus,\ntextarea:focus,\ninput:focus,\nselect:focus,\noption:focus,\ndetails:focus,\nsummary:focus,\n.custom-select select {\n  outline: none;\n}\n\nsummary {\n  cursor: default;\n  -webkit-user-select: none;\n}\n\n.pointer,\nsummary .pointer {\n  cursor: pointer;\n}\n\ndetails * {\n  font-size: 12px;\n  font-weight: normal;\n}\n\ndetails.message {\n  padding: 4px 18px 4px 12px;\n}\n\ndetails.message summary {\n  font-size: 10pt;\n  font-weight: 600;\n  min-height: 18px;\n}\n\ndetails.message:first-child {\n  margin-top: 4px;\n  margin-left: 0;\n  padding-left: 102px !important;\n}\n\ndetails.message > summary:first-of-type {\n  line-height: 20px;\n}\n\ndetails.message h1 {\n  text-align: left;\n}\n\ndetails:not(.pointer) > summary {\n  list-style: none;\n}\n\ndetails > summary::-webkit-details-marker\n.message > summary::-webkit-details-marker {\n  display: none;\n}\n\n.info20,\n.question,\n.caution,\n.info {\n  background-repeat: no-repeat;\n  background-position: 72px center;\n}\n\n.info20 {\n  background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath fill='%23999' d='M10,20 C4.4771525,20 0,15.5228475 0,10 C0,4.4771525 4.4771525,0 10,0 C15.5228475,0 20,4.4771525 20,10 C20,15.5228475 15.5228475,20 10,20 Z M10,8 C8.8954305,8 8,8.84275812 8,9.88235294 L8,16.1176471 C8,17.1572419 8.8954305,18 10,18 C11.1045695,18 12,17.1572419 12,16.1176471 L12,9.88235294 C12,8.84275812 11.1045695,8 10,8 Z M10,3 C8.8954305,3 8,3.88165465 8,4.96923077 L8,5.03076923 C8,6.11834535 8.8954305,7 10,7 C11.1045695,7 12,6.11834535 12,5.03076923 L12,4.96923077 C12,3.88165465 11.1045695,3 10,3 Z'/%3E%3C/svg%3E%0A\");\n}\n\n.info {\n  background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath fill='%23999' d='M10,18 C5.581722,18 2,14.418278 2,10 C2,5.581722 5.581722,2 10,2 C14.418278,2 18,5.581722 18,10 C18,14.418278 14.418278,18 10,18 Z M10,8 C9.44771525,8 9,8.42137906 9,8.94117647 L9,14.0588235 C9,14.5786209 9.44771525,15 10,15 C10.5522847,15 11,14.5786209 11,14.0588235 L11,8.94117647 C11,8.42137906 10.5522847,8 10,8 Z M10,5 C9.44771525,5 9,5.44082732 9,5.98461538 L9,6.01538462 C9,6.55917268 9.44771525,7 10,7 C10.5522847,7 11,6.55917268 11,6.01538462 L11,5.98461538 C11,5.44082732 10.5522847,5 10,5 Z'/%3E%3C/svg%3E%0A\");\n}\n\n.info2 {\n  background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='15' height='15' viewBox='0 0 15 15'%3E%3Cpath fill='%23999' d='M7.5,15 C3.35786438,15 0,11.6421356 0,7.5 C0,3.35786438 3.35786438,0 7.5,0 C11.6421356,0 15,3.35786438 15,7.5 C15,11.6421356 11.6421356,15 7.5,15 Z M7.5,2 C6.67157287,2 6,2.66124098 6,3.47692307 L6,3.52307693 C6,4.33875902 6.67157287,5 7.5,5 C8.32842705,5 9,4.33875902 9,3.52307693 L9,3.47692307 C9,2.66124098 8.32842705,2 7.5,2 Z M5,6 L5,7.02155172 L6,7 L6,12 L5,12.0076778 L5,13 L10,13 L10,12 L9,12.0076778 L9,6 L5,6 Z'/%3E%3C/svg%3E%0A\");\n}\n\n.sdpi-more-info {\n  background-image: linear-gradient(to right, #00000000 0%, #00000040 80%), url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpolygon fill='%23999' points='4 7 8 7 8 5 12 8 8 11 8 9 4 9'/%3E%3C/svg%3E%0A\");\n}\n\n.caution {\n  background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath fill='%23999' fill-rule='evenodd' d='M9.03952676,0.746646542 C9.57068894,-0.245797319 10.4285735,-0.25196227 10.9630352,0.746646542 L19.7705903,17.2030214 C20.3017525,18.1954653 19.8777595,19 18.8371387,19 L1.16542323,19 C0.118729947,19 -0.302490098,18.2016302 0.231971607,17.2030214 L9.03952676,0.746646542 Z M10,2.25584053 L1.9601405,17.3478261 L18.04099,17.3478261 L10,2.25584053 Z M10,5.9375 C10.531043,5.9375 10.9615385,6.37373537 10.9615385,6.91185897 L10.9615385,11.6923077 C10.9615385,12.2304313 10.531043,12.6666667 10,12.6666667 C9.46895697,12.6666667 9.03846154,12.2304313 9.03846154,11.6923077 L9.03846154,6.91185897 C9.03846154,6.37373537 9.46895697,5.9375 10,5.9375 Z M10,13.4583333 C10.6372516,13.4583333 11.1538462,13.9818158 11.1538462,14.6275641 L11.1538462,14.6641026 C11.1538462,15.3098509 10.6372516,15.8333333 10,15.8333333 C9.36274837,15.8333333 8.84615385,15.3098509 8.84615385,14.6641026 L8.84615385,14.6275641 C8.84615385,13.9818158 9.36274837,13.4583333 10,13.4583333 Z'/%3E%3C/svg%3E%0A\");\n}\n\n.question {\n  background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath fill='%23999' d='M10,18 C5.581722,18 2,14.418278 2,10 C2,5.581722 5.581722,2 10,2 C14.418278,2 18,5.581722 18,10 C18,14.418278 14.418278,18 10,18 Z M6.77783203,7.65332031 C6.77783203,7.84798274 6.85929281,8.02888914 7.0222168,8.19604492 C7.18514079,8.36320071 7.38508996,8.44677734 7.62207031,8.44677734 C8.02409055,8.44677734 8.29703704,8.20768468 8.44091797,7.72949219 C8.59326248,7.27245865 8.77945854,6.92651485 8.99951172,6.69165039 C9.2195649,6.45678594 9.56233491,6.33935547 10.027832,6.33935547 C10.4256205,6.33935547 10.7006836,6.37695313 11.0021973,6.68847656 C11.652832,7.53271484 10.942627,8.472229 10.3750916,9.1321106 C9.80755615,9.79199219 8.29492188,11.9897461 10.027832,12.1347656 C10.4498423,12.1700818 10.7027991,11.9147157 10.7832031,11.4746094 C11.0021973,9.59857178 13.1254883,8.82415771 13.1254883,7.53271484 C13.1254883,7.07568131 12.9974785,6.65250846 12.7414551,6.26318359 C12.4854317,5.87385873 12.1225609,5.56600048 11.652832,5.33959961 C11.1831031,5.11319874 10.6414419,5 10.027832,5 C9.36767248,5 8.79004154,5.13541531 8.29492187,5.40625 C7.79980221,5.67708469 7.42317837,6.01879677 7.16503906,6.43139648 C6.90689975,6.8439962 6.77783203,7.25130007 6.77783203,7.65332031 Z M10.0099668,15 C10.2713191,15 10.5016601,14.9108147 10.7009967,14.7324415 C10.9003332,14.5540682 11,14.3088087 11,13.9966555 C11,13.7157177 10.9047629,13.4793767 10.7142857,13.2876254 C10.5238086,13.0958742 10.2890379,13 10.0099668,13 C9.72646591,13 9.48726565,13.0958742 9.2923588,13.2876254 C9.09745196,13.4793767 9,13.7157177 9,13.9966555 C9,14.313268 9.10077419,14.5596424 9.30232558,14.735786 C9.50387698,14.9119295 9.73975502,15 10.0099668,15 Z'/%3E%3C/svg%3E%0A\");\n}\n\n\n.sdpi-more-info {\n  position: fixed;\n  left: 0px;\n  right: 0px;\n  bottom: 0px;\n  min-height: 16px;\n  padding-right: 16px;\n  text-align: right;\n  -webkit-touch-callout: none;\n  cursor: pointer;\n  user-select: none;\n  background-position: right center;\n  background-repeat: no-repeat;\n  border-radius: var(--sdpi-borderradius);\n  text-decoration: none;\n  color: var(--sdpi-color);\n}\n\n.sdpi-more-info-button {\n  display: flex;\n  align-self: right;\n  margin-left: auto;\n  position: fixed;\n  right: 17px;\n  bottom: 0px;\n  user-select: none;\n}\n\n\n.sdpi-bottom-bar {\n  display: flex;\n  align-self: right;\n  margin-left: auto;\n  position: fixed;\n  right: 17px;\n  bottom: 0px;\n  user-select: none;\n}\n\n.sdpi-bottom-bar.right {\n  right: 0px;\n}\n\n.sdpi-bottom-bar button {\n  min-height: 20px !important;\n  height: 20px !important;\n}\n\ndetails a {\n  background-position: right !important;\n  min-height: 24px;\n  display: inline-block;\n  line-height: 24px;\n  padding-right: 28px;\n}\n\n\ninput:not([type=\"range\"]),\ntextarea {\n  -webkit-appearance: none;\n  background: var(--sdpi-background);\n  color: var(--sdpi-color);\n  font-weight: normal;\n  font-size: 9pt;\n  border: none;\n  margin-top: 2px;\n  margin-bottom: 2px;\n  min-width: 219px;\n}\n\ntextarea + label {\n  display: flex;\n  justify-content: flex-end\n}\n\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n  display: none;\n}\n\ninput[type=\"radio\"] + label,\ninput[type=\"checkbox\"] + label {\n  font-size: 9pt;\n  color: var(--sdpi-color);\n  font-weight: normal;\n  margin-right: 8px;\n  -webkit-user-select: none;\n}\n\ninput[type=\"radio\"] + label:after,\ninput[type=\"checkbox\"] + label:after {\n  content: \" \" !important;\n}\n\n.sdpi-item[type=\"radio\"] > .sdpi-item-value,\n.sdpi-item[type=\"checkbox\"] > .sdpi-item-value {\n  padding-top: 2px;\n}\n\n.sdpi-item[type=\"checkbox\"] > .sdpi-item-value > * {\n  margin-top: 4px;\n}\n\n.sdpi-item[type=\"checkbox\"] .sdpi-item-child,\n.sdpi-item[type=\"radio\"] .sdpi-item-child {\n  display: inline-block;\n}\n\n.sdpi-item[type=\"range\"] .sdpi-item-value,\n.sdpi-item[type=\"meter\"] .sdpi-item-child,\n.sdpi-item[type=\"progress\"] .sdpi-item-child {\n  display: flex;\n}\n\n.sdpi-item[type=\"range\"] .sdpi-item-value {\n  min-height: 26px;\n}\n\n.sdpi-item[type=\"range\"] .sdpi-item-value span,\n.sdpi-item[type=\"meter\"] .sdpi-item-child span,\n.sdpi-item[type=\"progress\"] .sdpi-item-child span {\n  margin-top: -2px;\n  min-width: 8px;\n  text-align: right;\n  user-select: none;\n  cursor: pointer;\n  -webkit-user-select: none;\n  user-select: none;\n}\n\n.sdpi-item[type=\"range\"] .sdpi-item-value span {\n  margin-top: 7px;\n  text-align: right;\n}\n\nspan + input[type=\"range\"] {\n  display: flex;\n  max-width: 168px;\n\n}\n\n.sdpi-item[type=\"range\"] .sdpi-item-value span:first-child,\n.sdpi-item[type=\"meter\"] .sdpi-item-child span:first-child,\n.sdpi-item[type=\"progress\"] .sdpi-item-child span:first-child {\n  margin-right: 4px;\n}\n\n.sdpi-item[type=\"range\"] .sdpi-item-value span:last-child,\n.sdpi-item[type=\"meter\"] .sdpi-item-child span:last-child,\n.sdpi-item[type=\"progress\"] .sdpi-item-child span:last-child {\n  margin-left: 4px;\n}\n\n.reverse {\n  transform: rotate(180deg);\n}\n\n.sdpi-item[type=\"meter\"] .sdpi-item-child meter + span:last-child {\n  margin-left: -10px;\n}\n\n.sdpi-item[type=\"progress\"] .sdpi-item-child meter + span:last-child {\n  margin-left: -14px;\n}\n\n.sdpi-item[type=\"radio\"] > .sdpi-item-value > * {\n  margin-top: 2px;\n}\n\ndetails {\n  padding: 8px 18px 8px 12px;\n  min-width: 86px;\n}\n\ndetails > h4 {\n  border-bottom: 1px solid var(--sdpi-bordercolor);\n}\n\nlegend {\n  display: none;\n}\n\n.sdpi-item-value > textarea {\n  padding: 0px;\n  width: 219px;\n  margin-left: 1px;\n  margin-top: 3px;\n  padding: 4px;\n}\n\ninput[type=\"radio\"] + label span,\ninput[type=\"checkbox\"] + label span {\n  display: inline-block;\n  width: 16px;\n  height: 16px;\n  margin: 2px 4px 2px 0;\n  border-radius: 3px;\n  vertical-align: middle;\n  background: var(--sdpi-background);\n  cursor: pointer;\n  border: 1px solid rgb(0, 0, 0, .2);\n}\n\ninput[type=\"radio\"] + label span {\n  border-radius: 100%;\n}\n\ninput[type=\"radio\"]:checked + label span,\ninput[type=\"checkbox\"]:checked + label span {\n  background-color: #77f;\n  background-image: url(../assets/check.svg);\n  background-repeat: no-repeat;\n  background-position: center center;\n  border: 1px solid rgb(0, 0, 0, .4);\n}\n\ninput[type=\"radio\"]:active:checked + label span,\ninput[type=\"radio\"]:active + label span,\ninput[type=\"checkbox\"]:active:checked + label span,\ninput[type=\"checkbox\"]:active + label span {\n  background-color: #303030;\n}\n\ninput[type=\"radio\"]:checked + label span {\n  background-image: url(../assets/rcheck.svg);\n}\n\ninput[type=\"range\"] {\n  width: var(--sdpi-width);\n  height: 30px;\n  overflow: hidden;\n  cursor: pointer;\n  background: transparent !important;\n}\n\n.sdpi-item > input[type=\"range\"] {\n  margin-left: 2px;\n  max-width: var(--sdpi-width);\n  width: var(--sdpi-width);\n  padding: 0px;\n  margin-top: 2px;\n}\n\n/*\ninput[type=\"range\"],\ninput[type=\"range\"]::-webkit-slider-runnable-track,\ninput[type=\"range\"]::-webkit-slider-thumb {\n-webkit-appearance: none;\n}\n*/\n\ninput[type=\"range\"]::-webkit-slider-runnable-track {\n  height: 5px;\n  background: #979797;\n  border-radius: 3px;\n  padding: 0px !important;\n  border: 1px solid var(--sdpi-background);\n}\n\ninput[type=\"range\"]::-webkit-slider-thumb {\n  position: relative;\n  -webkit-appearance: none;\n  background-color: var(--sdpi-color);\n  width: 12px;\n  height: 12px;\n  border-radius: 20px;\n  margin-top: -5px;\n  border: none;\n}\n\ninput[type=\"range\" i] {\n  margin: 0;\n}\n\ninput[type=\"range\"]::-webkit-slider-thumb::before {\n  position: absolute;\n  content: \"\";\n  height: 5px; /* equal to height of runnable track or 1 less */\n  width: 500px; /* make this bigger than the widest range input element */\n  left: -502px; /* this should be -2px - width */\n  top: 8px; /* don't change this */\n  background: #77f;\n}\n\ninput[type=\"color\"] {\n  min-width: 32px;\n  min-height: 32px;\n  width: 32px;\n  height: 32px;\n  padding: 0;\n  background-color: var(--sdpi-bgcolor);\n  flex: none;\n}\n\n::-webkit-color-swatch {\n  min-width: 24px;\n}\n\ntextarea {\n  height: 3em;\n  word-break: break-word;\n  line-height: 1.5em;\n}\n\n.textarea {\n  padding: 0px !important;\n}\n\ntextarea {\n  width: 219px; /*98%;*/\n  height: 96%;\n  min-height: 6em;\n  resize: none;\n  border-radius: var(--sdpi-borderradius);\n}\n\n/* CAROUSEL */\n\n.sdpi-item[type=\"carousel\"] {}\n\n.sdpi-item.card-carousel-wrapper,\n.sdpi-item > .card-carousel-wrapper {\n  padding: 0;\n}\n\n\n.card-carousel-wrapper {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin: 12px auto;\n  color: #666a73;\n}\n\n.card-carousel {\n  display: flex;\n  justify-content: center;\n  width: 278px;\n}\n\n.card-carousel--overflow-container {\n  overflow: hidden;\n}\n\n.card-carousel--nav__left,\n.card-carousel--nav__right {\n  /* display: inline-block; */\n  width: 12px;\n  height: 12px;\n  border-top: 2px solid #42b883;\n  border-right: 2px solid #42b883;\n  cursor: pointer;\n  margin: 0 4px;\n  transition: transform 150ms linear;\n}\n\n.card-carousel--nav__left[disabled],\n.card-carousel--nav__right[disabled] {\n  opacity: 0.2;\n  border-color: black;\n}\n\n.card-carousel--nav__left {\n  transform: rotate(-135deg);\n}\n\n.card-carousel--nav__left:active {\n  transform: rotate(-135deg) scale(0.85);\n}\n\n.card-carousel--nav__right {\n  transform: rotate(45deg);\n}\n\n.card-carousel--nav__right:active {\n  transform: rotate(45deg) scale(0.85);\n}\n\n.card-carousel-cards {\n  display: flex;\n  transition: transform 150ms ease-out;\n  transform: translatex(0px);\n}\n\n.card-carousel-cards .card-carousel--card {\n  margin: 0 5px;\n  cursor: pointer;\n  /* box-shadow: 0 4px 15px 0 rgba(40, 44, 53, 0.06), 0 2px 2px 0 rgba(40, 44, 53, 0.08); */\n  background-color: #fff;\n  border-radius: 4px;\n  z-index: 3;\n}\n\n.xxcard-carousel-cards .card-carousel--card:first-child {\n  margin-left: 0;\n}\n\n.xxcard-carousel-cards .card-carousel--card:last-child {\n  margin-right: 0;\n}\n\n.card-carousel-cards .card-carousel--card img {\n  vertical-align: bottom;\n  border-top-left-radius: 4px;\n  border-top-right-radius: 4px;\n  transition: opacity 150ms linear;\n  width: 60px;\n}\n\n.card-carousel-cards .card-carousel--card img:hover {\n  opacity: 0.5;\n}\n\n.card-carousel-cards .card-carousel--card--footer {\n  border-top: 0;\n  max-width: 80px;\n  overflow: hidden;\n  display: flex;\n  height: 100%;\n  flex-direction: column;\n}\n\n.card-carousel-cards .card-carousel--card--footer p {\n  padding: 3px 0;\n  margin: 0;\n  margin-bottom: 2px;\n  font-size: 15px;\n  font-weight: 500;\n  color: #2c3e50;\n}\n\n.card-carousel-cards .card-carousel--card--footer p:nth-of-type(2) {\n  font-size: 12px;\n  font-weight: 300;\n  padding: 6px;\n  color: #666a73;\n}\n\n\nh1 {\n  font-size: 1.3em;\n  font-weight: 500;\n  text-align: center;\n  margin-bottom: 12px;\n}\n\n::-webkit-datetime-edit {\n  font-family: system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n  background: url(../assets/elg_calendar_inv.svg) no-repeat left center;\n  padding-right: 1em;\n  padding-left: 25px;\n  background-position: 4px 0px;\n}\n\n::-webkit-datetime-edit-fields-wrapper {}\n\n::-webkit-datetime-edit-text {\n  padding: 0 0.3em;\n}\n\n::-webkit-datetime-edit-month-field {}\n\n::-webkit-datetime-edit-day-field {}\n\n::-webkit-datetime-edit-year-field {}\n\n::-webkit-inner-spin-button {\n\n  /* display: none; */\n}\n\n::-webkit-calendar-picker-indicator {\n  background: transparent;\n  font-size: 17px;\n}\n\n::-webkit-calendar-picker-indicator:focus {\n  background-color: rgba(0, 0, 0, 0.2);\n}\n\ninput[type=\"date\"] {\n  -webkit-align-items: center;\n  display: -webkit-inline-flex;\n  font-family: monospace;\n  overflow: hidden;\n  padding: 0;\n  -webkit-padding-start: 1px;\n}\n\ninput::-webkit-datetime-edit {\n  -webkit-flex: 1;\n  -webkit-user-modify: read-only !important;\n  display: inline-block;\n  min-width: 0;\n  overflow: hidden;\n}\n\n/*\ninput::-webkit-datetime-edit-fields-wrapper {\n-webkit-user-modify: read-only !important;\ndisplay: inline-block;\npadding: 1px 0;\nwhite-space: pre;\n\n}\n*/\n\n/*\ninput[type=\"date\"] {\nbackground-color: red;\noutline: none;\n}\n\ninput[type=\"date\"]::-webkit-clear-button {\nfont-size: 18px;\nheight: 30px;\nposition: relative;\n}\n\ninput[type=\"date\"]::-webkit-inner-spin-button {\nheight: 28px;\n}\n\ninput[type=\"date\"]::-webkit-calendar-picker-indicator {\nfont-size: 15px;\n} */\n\ninput[type=\"file\"] {\n  opacity: 0;\n  display: none;\n}\n\n.sdpi-item > input[type=\"file\"] {\n  opacity: 1;\n  display: flex;\n}\n\ninput[type=\"file\"] + span {\n  display: flex;\n  flex: 0 1 auto;\n  background-color: #0000ff50;\n}\n\nlabel.sdpi-file-label {\n  cursor: pointer;\n  user-select: none;\n  display: inline-block;\n  min-height: 21px !important;\n  height: 21px !important;\n  line-height: 20px;\n  padding: 0px 4px;\n  margin: auto;\n  margin-right: 0px;\n  float: right;\n}\n\n.sdpi-file-label > label:active,\n.sdpi-file-label.file:active,\nlabel.sdpi-file-label:active,\nlabel.sdpi-file-info:active,\ninput[type=\"file\"]::-webkit-file-upload-button:active,\nbutton:active {\n  background-color: var(--sdpi-color);\n  color: #303030;\n}\n\ninput:required:invalid,\ninput:focus:invalid {\n  background: var(--sdpi-background) url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5IiBoZWlnaHQ9IjkiIHZpZXdCb3g9IjAgMCA5IDkiPgogICAgPHBhdGggZmlsbD0iI0Q4RDhEOCIgZD0iTTQuNSwwIEM2Ljk4NTI4MTM3LC00LjU2NTM4NzgyZS0xNiA5LDIuMDE0NzE4NjMgOSw0LjUgQzksNi45ODUyODEzNyA2Ljk4NTI4MTM3LDkgNC41LDkgQzIuMDE0NzE4NjMsOSAzLjA0MzU5MTg4ZS0xNiw2Ljk4NTI4MTM3IDAsNC41IEMtMy4wNDM1OTE4OGUtMTYsMi4wMTQ3MTg2MyAyLjAxNDcxODYzLDQuNTY1Mzg3ODJlLTE2IDQuNSwwIFogTTQsMSBMNCw2IEw1LDYgTDUsMSBMNCwxIFogTTQuNSw4IEM0Ljc3NjE0MjM3LDggNSw3Ljc3NjE0MjM3IDUsNy41IEM1LDcuMjIzODU3NjMgNC43NzYxNDIzNyw3IDQuNSw3IEM0LjIyMzg1NzYzLDcgNCw3LjIyMzg1NzYzIDQsNy41IEM0LDcuNzc2MTQyMzcgNC4yMjM4NTc2Myw4IDQuNSw4IFoiLz4KICA8L3N2Zz4) no-repeat 98% center;\n}\n\ninput:required:valid {\n  background: var(--sdpi-background) url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5IiBoZWlnaHQ9IjkiIHZpZXdCb3g9IjAgMCA5IDkiPjxwb2x5Z29uIGZpbGw9IiNEOEQ4RDgiIHBvaW50cz0iNS4yIDEgNi4yIDEgNi4yIDcgMy4yIDcgMy4yIDYgNS4yIDYiIHRyYW5zZm9ybT0icm90YXRlKDQwIDQuNjc3IDQpIi8+PC9zdmc+) no-repeat 98% center;\n}\n\n.tooltip,\n:tooltip,\n:title {\n  color: yellow;\n}\n\n.sdpi-item-group.file {\n  width: 232px;\n  display: flex;\n  align-items: center;\n}\n\n.sdpi-file-info {\n  overflow-wrap: break-word;\n  word-wrap: break-word;\n  hyphens: auto;\n\n  min-width: 132px;\n  max-width: 144px;\n  max-height: 32px;\n  margin-top: 0px;\n  margin-left: 5px;\n  display: inline-block;\n  overflow: hidden;\n  padding: 6px 4px;\n  background-color: var(--sdpi-background);\n}\n\n\n::-webkit-scrollbar {\n  width: 8px;\n}\n\n::-webkit-scrollbar-track {\n  -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);\n  box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);\n  margin: 4px;\n  border-radius: 8px;\n}\n\n::-webkit-scrollbar-thumb {\n  background-color: #999999;\n  outline: 1px solid slategrey;\n  border-radius: 8px;\n}\n\na {\n  color: #7397d2;\n}\n\n.testcontainer {\n  display: flex;\n  background-color: #0000ff20;\n  max-width: 400px;\n  height: 200px;\n  align-content: space-evenly;\n}\n\ninput[type=range] {\n  -webkit-appearance: none;\n  /* background-color: green; */\n  height: 6px;\n  margin-top: 12px;\n  z-index: 0;\n  overflow: visible;\n}\n\n/*\ninput[type=\"range\"]::-webkit-slider-thumb {\n-webkit-appearance: none;\nbackground-color: var(--sdpi-color);\nwidth: 12px;\nheight: 12px;\nborder-radius: 20px;\nmargin-top: -6px;\nborder: none;\n} */\n\n:-webkit-slider-thumb {\n  -webkit-appearance: none;\n  background-color: var(--sdpi-color);\n  width: 16px;\n  height: 16px;\n  border-radius: 20px;\n  margin-top: -6px;\n  border: 1px solid #999999;\n}\n\n.sdpi-item[type=\"range\"] .sdpi-item-group {\n  display: flex;\n  flex-direction: column;\n}\n\n.xxsdpi-item[type=\"range\"] .sdpi-item-group input {\n  max-width: 204px;\n}\n\n.sdpi-item[type=\"range\"] .sdpi-item-group span {\n  margin-left: 0px !important;\n}\n\n.sdpi-item[type=\"range\"] .sdpi-item-group > .sdpi-item-child {\n  display: flex;\n  flex-direction: row;\n}\n\n.rangeLabel {\n  position: absolute;\n  font-weight: normal;\n  margin-top: 24px;\n}\n\n:disabled {\n  color: #993333;\n}\n\nselect,\nselect option {\n  color: var(--sdpi-color);\n}\n\nselect.disabled,\nselect option:disabled {\n  color: #fd9494;\n  font-style: italic;\n}\n\n.runningAppsContainer {\n  display: none;\n}\n\n.one-line {\n  min-height: 1.5em;\n}\n\n.two-lines {\n  min-height: 3em;\n}\n\n.three-lines {\n  min-height: 4.5em;\n}\n\n.four-lines {\n  min-height: 6em;\n}\n\n.min80 > .sdpi-item-child {\n  min-width: 80px;\n}\n\n.min100 > .sdpi-item-child {\n  min-width: 100px;\n}\n\n.min120 > .sdpi-item-child {\n  min-width: 120px;\n}\n\n.min140 > .sdpi-item-child {\n  min-width: 140px;\n}\n\n.min160 > .sdpi-item-child {\n  min-width: 160px;\n}\n\n.min200 > .sdpi-item-child {\n  min-width: 200px;\n}\n\n.max40 {\n  flex-basis: 40%;\n  flex-grow: 0;\n}\n\n.max30 {\n  flex-basis: 30%;\n  flex-grow: 0;\n}\n\n.max20 {\n  flex-basis: 20%;\n  flex-grow: 0;\n}\n\n.up20 {\n  margin-top: -20px;\n}\n\n.alignCenter {\n  align-items: center;\n}\n\n.alignTop {\n  align-items: flex-start;\n}\n\n.alignBaseline {\n  align-items: baseline;\n}\n\n.noMargins,\n.noMargins *,\n.noInnerMargins * {\n  margin: 0;\n  padding: 0;\n}\n\n.hidden {\n  display: none !important;\n}\n\n.icon-help,\n.icon-help-line,\n.icon-help-fill,\n.icon-help-inv,\n.icon-brighter,\n.icon-darker,\n.icon-warmer,\n.icon-cooler {\n  min-width: 20px;\n  width: 20px;\n  background-repeat: no-repeat;\n  opacity: 1;\n}\n\n.icon-help:active,\n.icon-help-line:active,\n.icon-help-fill:active,\n.icon-help-inv:active,\n.icon-brighter:active,\n.icon-darker:active,\n.icon-warmer:active,\n.icon-cooler:active {\n  opacity: 0.5;\n}\n\n.icon-brighter,\n.icon-darker,\n.icon-warmer,\n.icon-cooler {\n  margin-top: 5px !important;\n}\n\n.icon-help,\n.icon-help-line,\n.icon-help-fill,\n.icon-help-inv {\n  cursor: pointer;\n  margin: 0px;\n  margin-left: 4px;\n}\n\n.icon-brighter {\n  background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23999' fill-rule='evenodd'%3E%3Ccircle cx='10' cy='10' r='4'/%3E%3Cpath d='M14.8532861,7.77530426 C14.7173255,7.4682615 14.5540843,7.17599221 14.3666368,6.90157083 L16.6782032,5.5669873 L17.1782032,6.4330127 L14.8532861,7.77530426 Z M10.5,4.5414007 C10.2777625,4.51407201 10.051423,4.5 9.82179677,4.5 C9.71377555,4.5 9.60648167,4.50311409 9.5,4.50925739 L9.5,2 L10.5,2 L10.5,4.5414007 Z M5.38028092,6.75545367 C5.18389364,7.02383457 5.01124349,7.31068015 4.86542112,7.61289977 L2.82179677,6.4330127 L3.32179677,5.5669873 L5.38028092,6.75545367 Z M4.86542112,12.3871002 C5.01124349,12.6893198 5.18389364,12.9761654 5.38028092,13.2445463 L3.32179677,14.4330127 L2.82179677,13.5669873 L4.86542112,12.3871002 Z M9.5,15.4907426 C9.60648167,15.4968859 9.71377555,15.5 9.82179677,15.5 C10.051423,15.5 10.2777625,15.485928 10.5,15.4585993 L10.5,18 L9.5,18 L9.5,15.4907426 Z M14.3666368,13.0984292 C14.5540843,12.8240078 14.7173255,12.5317385 14.8532861,12.2246957 L17.1782032,13.5669873 L16.6782032,14.4330127 L14.3666368,13.0984292 Z'/%3E%3C/g%3E%3C/svg%3E\");\n}\n\n.icon-darker {\n  background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23999' fill-rule='evenodd'%3E%3Cpath d='M10 14C7.790861 14 6 12.209139 6 10 6 7.790861 7.790861 6 10 6 12.209139 6 14 7.790861 14 10 14 12.209139 12.209139 14 10 14zM10 13C11.6568542 13 13 11.6568542 13 10 13 8.34314575 11.6568542 7 10 7 8.34314575 7 7 8.34314575 7 10 7 11.6568542 8.34314575 13 10 13zM14.8532861 7.77530426C14.7173255 7.4682615 14.5540843 7.17599221 14.3666368 6.90157083L16.6782032 5.5669873 17.1782032 6.4330127 14.8532861 7.77530426zM10.5 4.5414007C10.2777625 4.51407201 10.051423 4.5 9.82179677 4.5 9.71377555 4.5 9.60648167 4.50311409 9.5 4.50925739L9.5 2 10.5 2 10.5 4.5414007zM5.38028092 6.75545367C5.18389364 7.02383457 5.01124349 7.31068015 4.86542112 7.61289977L2.82179677 6.4330127 3.32179677 5.5669873 5.38028092 6.75545367zM4.86542112 12.3871002C5.01124349 12.6893198 5.18389364 12.9761654 5.38028092 13.2445463L3.32179677 14.4330127 2.82179677 13.5669873 4.86542112 12.3871002zM9.5 15.4907426C9.60648167 15.4968859 9.71377555 15.5 9.82179677 15.5 10.051423 15.5 10.2777625 15.485928 10.5 15.4585993L10.5 18 9.5 18 9.5 15.4907426zM14.3666368 13.0984292C14.5540843 12.8240078 14.7173255 12.5317385 14.8532861 12.2246957L17.1782032 13.5669873 16.6782032 14.4330127 14.3666368 13.0984292z'/%3E%3C/g%3E%3C/svg%3E\");\n}\n\n.icon-warmer {\n  background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23999' fill-rule='evenodd'%3E%3Cpath d='M12.3247275 11.4890349C12.0406216 11.0007637 11.6761954 10.5649925 11.2495475 10.1998198 11.0890394 9.83238991 11 9.42659309 11 9 11 7.34314575 12.3431458 6 14 6 15.6568542 6 17 7.34314575 17 9 17 10.6568542 15.6568542 12 14 12 13.3795687 12 12.8031265 11.8116603 12.3247275 11.4890349zM17.6232392 11.6692284C17.8205899 11.4017892 17.9890383 11.1117186 18.123974 10.8036272L20.3121778 12.0669873 19.8121778 12.9330127 17.6232392 11.6692284zM18.123974 7.19637279C17.9890383 6.88828142 17.8205899 6.5982108 17.6232392 6.33077158L19.8121778 5.0669873 20.3121778 5.9330127 18.123974 7.19637279zM14.5 4.52746439C14.3358331 4.50931666 14.1690045 4.5 14 4.5 13.8309955 4.5 13.6641669 4.50931666 13.5 4.52746439L13.5 2 14.5 2 14.5 4.52746439zM13.5 13.4725356C13.6641669 13.4906833 13.8309955 13.5 14 13.5 14.1690045 13.5 14.3358331 13.4906833 14.5 13.4725356L14.5 16 13.5 16 13.5 13.4725356zM14 11C15.1045695 11 16 10.1045695 16 9 16 7.8954305 15.1045695 7 14 7 12.8954305 7 12 7.8954305 12 9 12 10.1045695 12.8954305 11 14 11zM9.5 11C10.6651924 11.4118364 11.5 12.5 11.5 14 11.5 16 10 17.5 8 17.5 6 17.5 4.5 16 4.5 14 4.5 12.6937812 5 11.5 6.5 11L6.5 7 9.5 7 9.5 11z'/%3E%3Cpath d='M12,14 C12,16.209139 10.209139,18 8,18 C5.790861,18 4,16.209139 4,14 C4,12.5194353 4.80439726,11.2267476 6,10.5351288 L6,4 C6,2.8954305 6.8954305,2 8,2 C9.1045695,2 10,2.8954305 10,4 L10,10.5351288 C11.1956027,11.2267476 12,12.5194353 12,14 Z M11,14 C11,12.6937812 10.1651924,11.5825421 9,11.1707057 L9,4 C9,3.44771525 8.55228475,3 8,3 C7.44771525,3 7,3.44771525 7,4 L7,11.1707057 C5.83480763,11.5825421 5,12.6937812 5,14 C5,15.6568542 6.34314575,17 8,17 C9.65685425,17 11,15.6568542 11,14 Z'/%3E%3C/g%3E%3C/svg%3E\");\n}\n\n.icon-cooler {\n  background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23999' fill-rule='evenodd'%3E%3Cpath d='M10.4004569 11.6239517C10.0554735 10.9863849 9.57597206 10.4322632 9 9.99963381L9 9.7450467 9.53471338 9.7450467 10.8155381 8.46422201C10.7766941 8.39376637 10.7419749 8.32071759 10.7117062 8.2454012L9 8.2454012 9 6.96057868 10.6417702 6.96057868C10.6677696 6.86753378 10.7003289 6.77722682 10.7389179 6.69018783L9.44918707 5.40045694 9 5.40045694 9 4.34532219 9.32816127 4.34532219 9.34532219 2.91912025 10.4004569 2.91912025 10.4004569 4.53471338 11.6098599 5.74411634C11.7208059 5.68343597 11.8381332 5.63296451 11.9605787 5.59396526L11.9605787 3.8884898 10.8181818 2.74609294 11.5642748 2 12.5727518 3.00847706 13.5812289 2 14.3273218 2.74609294 13.2454012 3.82801356 13.2454012 5.61756719C13.3449693 5.65339299 13.4408747 5.69689391 13.5324038 5.74735625L14.7450467 4.53471338 14.7450467 2.91912025 15.8001815 2.91912025 15.8001815 4.34532219 17.2263834 4.34532219 17.2263834 5.40045694 15.6963166 5.40045694 14.4002441 6.69652946C14.437611 6.78161093 14.4692249 6.86979146 14.4945934 6.96057868L16.2570138 6.96057868 17.3994107 5.81818182 18.1455036 6.56427476 17.1370266 7.57275182 18.1455036 8.58122888 17.3994107 9.32732182 16.3174901 8.2454012 14.4246574 8.2454012C14.3952328 8.31861737 14.3616024 8.38969062 14.3240655 8.45832192L15.6107903 9.7450467 17.2263834 9.7450467 17.2263834 10.8001815 15.8001815 10.8001815 15.8001815 12.2263834 14.7450467 12.2263834 14.7450467 10.6963166 13.377994 9.32926387C13.3345872 9.34850842 13.2903677 9.36625331 13.2454012 9.38243281L13.2454012 11.3174901 14.3273218 12.3994107 13.5812289 13.1455036 12.5848864 12.1491612 11.5642748 13.1455036 10.8181818 12.3994107 11.9605787 11.2570138 11.9605787 9.40603474C11.8936938 9.38473169 11.828336 9.36000556 11.7647113 9.33206224L10.4004569 10.6963166 10.4004569 11.6239517zM12.75 8.5C13.3022847 8.5 13.75 8.05228475 13.75 7.5 13.75 6.94771525 13.3022847 6.5 12.75 6.5 12.1977153 6.5 11.75 6.94771525 11.75 7.5 11.75 8.05228475 12.1977153 8.5 12.75 8.5zM9.5 14C8.5 16.3333333 7.33333333 17.5 6 17.5 4.66666667 17.5 3.5 16.3333333 2.5 14L9.5 14z'/%3E%3Cpath d='M10,14 C10,16.209139 8.209139,18 6,18 C3.790861,18 2,16.209139 2,14 C2,12.5194353 2.80439726,11.2267476 4,10.5351288 L4,4 C4,2.8954305 4.8954305,2 6,2 C7.1045695,2 8,2.8954305 8,4 L8,10.5351288 C9.19560274,11.2267476 10,12.5194353 10,14 Z M9,14 C9,12.6937812 8.16519237,11.5825421 7,11.1707057 L7,4 C7,3.44771525 6.55228475,3 6,3 C5.44771525,3 5,3.44771525 5,4 L5,11.1707057 C3.83480763,11.5825421 3,12.6937812 3,14 C3,15.6568542 4.34314575,17 6,17 C7.65685425,17 9,15.6568542 9,14 Z'/%3E%3C/g%3E%3C/svg%3E\");\n}\n\n.icon-help {\n  background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cpath fill='%23999' d='M11.292 12.516l.022 1.782H9.07v-1.804c0-1.98 1.276-2.574 2.662-3.278h-.022c.814-.44 1.65-.88 1.694-2.2.044-1.386-1.122-2.728-3.234-2.728-1.518 0-2.662.902-3.366 2.354L5 5.608C5.946 3.584 7.662 2 10.17 2c3.564 0 5.632 2.442 5.588 5.06-.066 2.618-1.716 3.41-3.102 4.158-.704.374-1.364.682-1.364 1.298zm-1.122 2.442c.858 0 1.452.594 1.452 1.452 0 .682-.594 1.408-1.452 1.408-.77 0-1.386-.726-1.386-1.408 0-.858.616-1.452 1.386-1.452z'/%3E%3C/svg%3E\");\n}\n\n.icon-help-line {\n  background-image: url(\"data:image/svg+xml,%3Csvg width='20' height='20' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%23999' fill-rule='evenodd'%3E%3Cpath d='M10 20C4.477 20 0 15.523 0 10S4.477 0 10 0s10 4.477 10 10-4.477 10-10 10zm0-1a9 9 0 1 0 0-18 9 9 0 0 0 0 18z'/%3E%3Cpath d='M10.848 12.307l.02 1.578H8.784v-1.597c0-1.753 1.186-2.278 2.474-2.901h-.02c.756-.39 1.533-.78 1.574-1.948.041-1.226-1.043-2.414-3.006-2.414-1.41 0-2.474.798-3.128 2.083L5 6.193C5.88 4.402 7.474 3 9.805 3 13.118 3 15.04 5.161 15 7.478c-.061 2.318-1.595 3.019-2.883 3.68-.654.332-1.268.604-1.268 1.15zM9.805 14.47c.798 0 1.35.525 1.35 1.285 0 .603-.552 1.246-1.35 1.246-.715 0-1.288-.643-1.288-1.246 0-.76.573-1.285 1.288-1.285z' fill-rule='nonzero'/%3E%3C/g%3E%3C/svg%3E\");\n}\n\n.icon-help-fill {\n  background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Ccircle cx='10' cy='10' r='10' fill='%23999'/%3E%3Cpath fill='%23FFF' fill-rule='nonzero' d='M8.368 7.189H5C5 3.5 7.668 2 10.292 2 13.966 2 16 4.076 16 7.012c0 3.754-3.849 3.136-3.849 5.211v1.656H8.455v-1.832c0-2.164 1.4-2.893 2.778-3.6.437-.242 1.006-.574 1.006-1.236 0-2.208-3.871-2.142-3.871-.022zM10.25 18a1.75 1.75 0 1 1 0-3.5 1.75 1.75 0 0 1 0 3.5z'/%3E%3C/g%3E%3C/svg%3E\");\n}\n\n.icon-help-inv {\n  background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cpath fill='%23999' fill-rule='evenodd' d='M10 20C4.477 20 0 15.523 0 10S4.477 0 10 0s10 4.477 10 10-4.477 10-10 10zM8.368 7.189c0-2.12 3.87-2.186 3.87.022 0 .662-.568.994-1.005 1.236-1.378.707-2.778 1.436-2.778 3.6v1.832h3.696v-1.656c0-2.075 3.849-1.457 3.849-5.21C16 4.075 13.966 2 10.292 2 7.668 2 5 3.501 5 7.189h3.368zM10.25 18a1.75 1.75 0 1 0 0-3.5 1.75 1.75 0 0 0 0 3.5z'/%3E%3C/svg%3E\");\n}\n\n.kelvin::after {\n  content: \"K\";\n}\n\n.mired::after {\n  content: \" Mired\";\n}\n\n.percent::after {\n  content: \"%\";\n}\n\n.sdpi-item-value + .icon-cooler,\n.sdpi-item-value + .icon-warmer {\n  margin-left: 0px !important;\n  margin-top: 15px !important;\n}\n\n/**\nCONTROL-CENTER STYLES\n*/\ninput[type=\"range\"].colorbrightness::-webkit-slider-runnable-track,\ninput[type=\"range\"].colortemperature::-webkit-slider-runnable-track {\n  height: 8px;\n  background: #979797;\n  border-radius: 4px;\n  background-image: linear-gradient(to right, #94d0ec, #ffb165);\n}\n\ninput[type=\"range\"].colorbrightness::-webkit-slider-runnable-track {\n  background-color: #efefef;\n  background-image: linear-gradient(to right, black, rgba(0, 0, 0, 0));\n}\n\n\ninput[type=\"range\"].colorbrightness::-webkit-slider-thumb,\ninput[type=\"range\"].colortemperature::-webkit-slider-thumb {\n  width: 16px;\n  height: 16px;\n  border-radius: 20px;\n  margin-top: -5px;\n  background-color: #86c6e8;\n  box-shadow: 0px 0px 1px #000000;\n  border: 1px solid #d8d8d8;\n}\n\n.sdpi-info-label {\n  display: inline-block;\n  user-select: none;\n  position: absolute;\n  height: 15px;\n  width: auto;\n  text-align: center;\n  border-radius: 4px;\n  min-width: 44px;\n  max-width: 80px;\n  background: white;\n  font-size: 11px;\n  color: black;\n  z-index: 1000;\n  box-shadow: 0px 0px 12px rgba(0, 0, 0, .8);\n  padding: 2px;\n\n}\n\n.sdpi-info-label.hidden {\n  opacity: 0;\n  transition: opacity 0.25s linear;\n}\n\n.sdpi-info-label.shown {\n  position: absolute;\n  opacity: 1;\n  transition: opacity 0.25s ease-out;\n}\n\n/* adding some styles here that override sdpi things so we can use this as notes for sdpi updates*/\nselect {\n  min-width: 0px;\n\n  /* this is a clunky fix for using background image as select arrow with long text options */\n  -webkit-appearance: media-slider;\n  text-overflow: ellipsis;\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>com.elgato.philips-hue.pi</title>\n    <meta charset=\"UTF-8\" />\n    <!-- Import style sheets -->\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"css/sdpi.css\" />\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"css/colorPI.css\" />\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"css/cyclePI.css\" />\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"css/pi.css\" />\n    <!-- Import scripts -->\n    <script src=\"../plugin/js/utils.js\"></script>\n    <script src=\"../plugin/js/philips/meethue.js\"></script>\n    <script src=\"js/tooltips.js\"></script>\n    <script src=\"js/pi.js\"></script>\n    <script src=\"js/powerPI.js\"></script>\n    <script src=\"js/colorPI.js\"></script>\n    <script src=\"js/cyclePI.js\"></script>\n    <script src=\"js/temperaturePI.js\"></script>\n    <script src=\"js/brightnessPI.js\"></script>\n    <script src=\"js/brightnessRelPI.js\"></script>\n    <script src=\"js/scenePI.js\"></script>\n    <script src=\"js/main.js\"></script>\n  </head>\n  <body>\n    <!-- Property Inspector -->\n    <div class=\"sdpi-wrapper\" id=\"pi\">\n      <!-- Bridge select -->\n      <div class=\"sdpi-item\">\n        <div class=\"sdpi-item-label\" id=\"bridge-label\"></div>\n        <select class=\"sdpi-item-value select\" id=\"bridge-select\">\n          <option id=\"no-bridges\" value=\"no-bridges\"></option>\n          <option disabled></option>\n          <option id=\"add-bridge\" value=\"add\"></option>\n        </select>\n      </div>\n      <!-- Light/Group select -->\n      <div class=\"sdpi-item\">\n        <div class=\"sdpi-item-label\" id=\"lights-label\"></div>\n        <select class=\"sdpi-item-value select\" id=\"light-select\">\n          <optgroup id=\"groups\">\n            <option id=\"no-groups\" value=\"no-groups\"></option>\n          </optgroup>\n          <optgroup id=\"lights\">\n            <option id=\"no-lights\" value=\"no-lights\"></option>\n          </optgroup>\n        </select>\n      </div>\n      <!-- Placeholder for more UI elements -->\n      <div id=\"placeholder\"></div>\n    </div>\n    <!-- Floating tooltips element -->\n    <div class=\"sdpi-info-label hidden\" style=\"top: -1000px\" data-value=\"\"></div>\n  </body>\n</html>\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/js/brightnessPI.js",
    "content": "/**\n@file      brightnessPI.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\nfunction BrightnessPI(inContext, inLanguage, inStreamDeckVersion, inPluginVersion, isEncoder) {\n    // Init BrightnessPI\n    let instance = this;\n\n    // Inherit from PI\n    PI.call(this, inContext, inLanguage, inStreamDeckVersion, inPluginVersion);\n\n    // Before overwriting parent method, save a copy of it\n    let piLocalize = this.localize;\n\n    // Localize the UI\n    this.localize = () => {\n        // Call PIs localize method\n        piLocalize.call(instance);\n\n        // Localize the brightness label\n        document.getElementById('brightness-label').innerHTML = instance.localization['Brightness'];\n        if(isEncoder) {\n          document.getElementById('scaleticks-label').innerHTML = instance.localization['Scale Ticks'] || 'Scale Ticks';\n        }\n\n    };\n\n    const values = [1,2,3,4,5,10,20];\n    const selectedIndex = values.indexOf(Number(settings.scaleTicks));\n\n    // Add brightness slider\n    document.getElementById('placeholder').innerHTML = `\n      <div type=\"range\" class=\"sdpi-item\">\n        <div class=\"sdpi-item-label\" id=\"brightness-label\"></div>\n        <div class=\"sdpi-item-value\">\n            <input class=\"floating-tooltip\" data-suffix=\"%\" type=\"range\" id=\"brightness-input\" min=\"1\" max=\"100\" value=\"${settings.brightness}\">\n        </div>\n      </div>\n      ${this.getEncoderOptions(settings.scaleTicks, isEncoder)}\n      `;\n\n      console.log(\"value:\", selectedIndex, settings.scaleTicks, typeof settings.scaleTicks, document.getElementById('placeholder').innerHTML);\n\n\n    // Initialize the tooltips\n    initToolTips();\n\n    // Add event listener\n    document.getElementById('brightness-input').addEventListener('change', brightnessChanged);\n    if(isEncoder) {\n      document.getElementById('scaleticks-input').addEventListener('change', scaleticksChanged);\n    }\n\n    // Brightness changed\n    function brightnessChanged(inEvent) {\n        // Save the new brightness settings\n        settings.brightness = inEvent.target.value;\n        instance.saveSettings();\n\n        // Inform the plugin that a new brightness is set\n        instance.sendToPlugin({\n            piEvent: 'valueChanged',\n        });\n    }\n\n    function scaleticksChanged(inEvent) {\n      settings.scaleTicks = inEvent.target.value;\n      instance.saveSettings();\n    }\n\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/js/brightnessRelPI.js",
    "content": "/**\n@file      brightnessRelPI.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\nfunction BrightnessRelPI(inContext, inLanguage, inStreamDeckVersion, inPluginVersion) {\n    // Init BrightnessPI\n    let instance = this;\n\n    // Inherit from PI\n    PI.call(this, inContext, inLanguage, inStreamDeckVersion, inPluginVersion);\n\n    // Before overwriting parent method, save a copy of it\n    let piLocalize = this.localize;\n\n    // Localize the UI\n    this.localize = function() {\n        // Call PIs localize method\n        piLocalize.call(instance);\n\n        // Localize the brightness label\n        document.getElementById('brightness-rel-label').innerHTML = instance.localization['Steps'];\n    };\n\n    // Add steps slider\n    document.getElementById('placeholder').innerHTML = `\n      <div type=\"range\" class=\"sdpi-item\">\n        <div class=\"sdpi-item-label\" id=\"brightness-rel-label\"></div>\n        <div class=\"sdpi-item-value\">\n          <input class=\"floating-tooltip\" data-suffix=\"%\" type=\"range\" id=\"brightness-rel-input\" min=\"-50\" max=\"50\" value=\"${settings.brightnessRel}\">\n        </div>\n      </div>\n    `;\n\n    // Initialize the tooltips\n    initToolTips();\n\n    // Add event listener\n    document.getElementById('brightness-rel-input').addEventListener('change', brightnessRelChanged);\n\n    // Brightness changed\n    function brightnessRelChanged(inEvent) {\n        // Save the new brightness settings\n        settings.brightnessRel = inEvent.target.value;\n        instance.saveSettings();\n\n        // Inform the plugin that a new brightness is set\n        instance.sendToPlugin({ 'piEvent': 'valueChanged' });\n    }\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/js/colorPI.js",
    "content": "/**\n@file      colorPI.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\nfunction ColorPI(inContext, inLanguage, inStreamDeckVersion, inPluginVersion) {\n    // Init ColorPI\n    let instance = this;\n\n    // Inherit from PI\n    PI.call(this, inContext, inLanguage, inStreamDeckVersion, inPluginVersion);\n\n    // Add event listener\n    document.getElementById('light-select').addEventListener('change', lightChanged);\n\n    // Color changed\n    function colorChanged(inEvent) {\n        // Get the selected color\n        let color = inEvent.target.value;\n\n        // If the color is hex\n        if (color.charAt(0) === '#') {\n            // Convert the color to HSV\n            let hsv = Bridge.hex2hsv(color);\n\n            // Check if the color is valid\n            if (hsv.v !== 1) {\n                // Remove brightness component\n                hsv.v = 1;\n\n                // Set the color to the corrected color\n                color = Bridge.hsv2hex(hsv);\n            }\n        }\n\n        // Save the new color\n        settings.color = color;\n        instance.saveSettings();\n\n        // Inform the plugin that a new color is set\n        instance.sendToPlugin({\n            piEvent: 'valueChanged',\n        });\n    }\n\n    // Light changed\n    function lightChanged() {\n        // Get the light value manually\n        // Because it is not set if this function was triggered via a CustomEvent\n        let lightID = document.getElementById('light-select').value;\n\n        // Don't show any color picker if no light or group is set\n        if (lightID === 'no-lights' || lightID === 'no-groups') {\n            return;\n        }\n\n        // Check if any bridge is configured\n        if (!('bridge' in settings)) {\n            return;\n        }\n\n        // Check if the configured bridge is in the cache\n        if (!(settings.bridge in cache)) {\n            return;\n        }\n\n        // Find the configured bridge\n        let bridgeCache = cache[settings.bridge];\n\n        // Check if the selected light or group is in the cache\n        if (!(lightID in bridgeCache.lights || lightID in bridgeCache.groups)) {\n            return;\n        }\n\n        // Get light or group cache\n        let lightCache;\n\n        if (lightID.indexOf('l') !== -1) {\n            lightCache = bridgeCache.lights[lightID];\n        }\n        else {\n            lightCache = bridgeCache.groups[lightID];\n        }\n\n        // Add full color picker or only temperature slider\n        let colorPicker;\n\n        if (lightCache.xy !== null) {\n            colorPicker = `\n              <div type=\"color\" class=\"sdpi-item\">\n                <div class=\"sdpi-item-label\" id=\"color-label\">${instance.localization['Color']}</div>\n                <input type=\"color\" class=\"sdpi-item-value\" id=\"color-input\" value=\"${settings.color}\">\n              </div>\n            `;\n        }\n        else {\n            colorPicker = `\n              <div type=\"range\" class=\"sdpi-item\">\n                <div class=\"sdpi-item-label\" id=\"temperature-label\">${instance.localization['Temperature']}</div>\n                <div class=\"sdpi-item-value\">\n                  <input class=\"temperature floating-tooltip\" data-suffix=\"K\" type=\"range\" id=\"color-input\" min=\"2000\" max=\"6500\" value=\"${settings.color}\">\n                </div>\n              </div>\n            `;\n        }\n\n        // Add color picker\n        document.getElementById('placeholder').innerHTML = colorPicker;\n\n        // Initialize the tooltips\n        initToolTips();\n\n        // Add event listener\n        document.getElementById('color-input').addEventListener('change', colorChanged);\n    }\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/js/cyclePI.js",
    "content": "/**\n@file      cyclePI.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\nfunction CyclePI(inContext, inLanguage, inStreamDeckVersion, inPluginVersion) {\n    // Init CyclePI\n    let instance = this;\n\n    // Maximum amount of Colors\n    let maxColors = 10;\n\n    // Current amount of Colors\n    let curColors = settings?.colors?.length || 0;\n\n    // Default color for new pickers\n    let defaultColor = \"#ffffff\";\n\n    // Default temperature for new pickers\n    let defaultTemperature = 2000;\n\n    // Inherit from PI\n    PI.call(this, inContext, inLanguage, inStreamDeckVersion, inPluginVersion);\n\n    // Add event listener\n    document.getElementById('light-select').addEventListener('change', lightChanged);\n\n    // Color changed\n    function colorChanged(inEvent) {\n        // Get the selected index and color\n        let index = inEvent.target.dataset.id;\n        let color = inEvent.target.value;\n\n        // If the color is hex\n        if (color.charAt(0) === '#') {\n            // Convert the color to HSV\n            let hsv = Bridge.hex2hsv(color);\n\n            // Check if the color is valid\n            if (hsv.v !== 1) {\n                // Remove brightness component\n                hsv.v = 1;\n\n                // Set the color to the corrected color\n                color = Bridge.hsv2hex(hsv);\n            }\n        }\n\n        // Save the new color\n        settings.colors[index] = color;\n        instance.saveSettings();\n\n        // Inform the plugin that a new color is set\n        instance.sendToPlugin({\n            piEvent: 'valueChanged',\n        });\n    }\n\n    // Light changed\n    function lightChanged() {\n        // Get the light value manually\n        // Because it is not set if this function was triggered via a CustomEvent\n        let lightID = document.getElementById('light-select').value;\n\n        // Don't show any color picker if no light or group is set\n        if (lightID === 'no-lights' || lightID === 'no-groups') {\n            return;\n        }\n\n        // Check if any bridge is configured\n        if (!('bridge' in settings)) {\n            return;\n        }\n\n        // Check if the configured bridge is in the cache\n        if (!(settings.bridge in cache)) {\n            return;\n        }\n\n        // Find the configured bridge\n        let bridgeCache = cache[settings.bridge];\n\n        // Check if the selected light or group is in the cache\n        if (!(lightID in bridgeCache.lights || lightID in bridgeCache.groups)) {\n            return;\n        }\n\n        // Get light or group cache\n        let lightCache;\n\n        if (lightID.indexOf('l') !== -1) {\n            lightCache = bridgeCache.lights[lightID];\n        }\n        else {\n            lightCache = bridgeCache.groups[lightID];\n        }\n\n        // Get html of color picker or temperature slider\n        let getColorPicker = i => {\n            let colorIndex = i - 1;\n\n            if (lightCache.xy != null) {\n                if (i === 0) {\n                    return `\n                      <div type=\"color\" class=\"sdpi-item\" id=\"color-input-container\">\n                        <div class=\"sdpi-item-label\" class=\"color-label\">${instance.localization['Colors']}</div>\n                        <div class=\"sdpi-item-value\"></div>\n                      </div>\n                    `;\n                }\n                else {\n                    return `\n                      <span id=\"color-input-container-${colorIndex}\"><input type=\"color\" class=\"sdpi-item-value\" id=\"color-input-${colorIndex}\" name=\"color-input\" data-id=\"${colorIndex}\" value=\"${(settings.colors[colorIndex] || defaultColor)}\"></span>\n                    `;\n                }\n            }\n            else if (i > 0) {\n                return `\n                  <div type=\"range\" class=\"sdpi-item\" id=\"color-input-container-${colorIndex}\">\n                    <div class=\"sdpi-item-label\" id=\"temperature-label\">${instance.localization['Temperature']} ${i}</div>\n                    <div class=\"sdpi-item-value\">\n                      <input class=\"temperature floating-tooltip\" data-suffix=\"K\" type=\"range\" id=\"color-input-${colorIndex}\" name=\"color-input\" data-id=\"${colorIndex}\" min=\"2000\" max=\"6500\" value=\"${(settings.colors[colorIndex] || defaultTemperature)}\">\n                    </div>\n                  </div>\n                `;\n            }\n\n            return '';\n        };\n\n        let placeholder = document.getElementById('placeholder');\n\n        // Add a new color picker to document\n        let addColorPicker = i => {\n            let picker = document.createElement('div');\n            const cphtml = getColorPicker(i).trim();\n            picker.innerHTML = cphtml;\n\n\n            if (lightCache.xy != null) {\n                document.querySelector('#color-input-container .sdpi-item-value').append(picker.firstChild);\n            }\n            else {\n                placeholder.insertBefore(picker.firstChild, document.getElementById('cycle-buttons'));\n            }\n\n            document.getElementById('color-input-' + (i - 1)).addEventListener('change', colorChanged);\n        };\n\n        // Add first color pickers container and buttons\n        placeholder.innerHTML = getColorPicker(0) + `\n          <div id=\"cycle-buttons\" class=\"sdpi-item\">\n            <div class=\"sdpi-item-label empty\"></div>\n            <div class=\"sdpi-item-value\">\n              <button id=\"add-color\">+</button>\n              <button id=\"remove-color\">-</button>\n            </div>\n          </div>\n        `;\n\n        // Initial create color pickers from settings\n        for (let n = 1; n <= settings.colors.length; n++) {\n            addColorPicker(n);\n        }\n\n        // Get buttons for later usage\n        let addButton = document.getElementById('add-color');\n        let removeButton = document.getElementById('remove-color');\n        let checkButtonStates = () => {\n            // Hide add button when reached max color pickers\n            addButton.style.display = curColors >= maxColors ? 'none' : 'inline-block';\n\n            // Hide remove button when only two color pickers left\n            removeButton.style.display = curColors <= 2 ? 'none' : 'inline-block';\n        };\n\n        // Event listener for add color\n        addButton.addEventListener('click', () => {\n            addColorPicker((++curColors));\n\n            // Add new picker value to settings\n            let colorIndex = curColors - 1;\n\n            if (!settings.colors[colorIndex]) {\n                if (lightCache.xy != null) {\n                    settings.colors[colorIndex] = defaultColor;\n                }\n                else {\n                    settings.colors[colorIndex] = defaultTemperature;\n                }\n\n                instance.saveSettings();\n            }\n\n            checkButtonStates();\n        });\n\n        // Event listener for remove last color\n        removeButton.addEventListener('click', () => {\n            document.getElementById('color-input-container-' + (--curColors)).remove();\n\n            // Remove color from settings\n            settings.colors = settings.colors.splice(0, settings.colors.length - 1);\n            instance.saveSettings();\n\n            checkButtonStates();\n        });\n\n        // Initial button states\n        checkButtonStates();\n\n        // Initialize the tooltips\n        initToolTips();\n    }\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/js/main.js",
    "content": "/**\n@file      main.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\n// Global web socket\nvar websocket = null;\n\n// Global plugin settings\nvar globalSettings = {};\n\n// Global settings\nvar settings = {};\n\n// Global cache\nvar cache = {};\n\n// Setup the websocket and handle communication\nfunction connectElgatoStreamDeckSocket(inPort, inUUID, inRegisterEvent, inInfo, inActionInfo) {\n    // Parse parameter from string to object\n    let actionInfo = JSON.parse(inActionInfo);\n    let info = JSON.parse(inInfo);\n    let isEncoder = actionInfo?.payload?.controller == 'Encoder';\n    let streamDeckVersion = info['application']['version'];\n    let pluginVersion = info['plugin']['version'];\n\n    // Save global settings\n    settings = actionInfo['payload']['settings'];\n\n    // Retrieve language\n    let language = info['application']['language'];\n\n    // Retrieve action identifier\n    let action = actionInfo['action'];\n\n    // Open the web socket to Stream Deck\n    // Use 127.0.0.1 because Windows needs 300ms to resolve localhost\n    websocket = new WebSocket(`ws://127.0.0.1:${inPort}`);\n\n    // WebSocket is connected, send message\n    websocket.onopen = () => {\n        // Register property inspector to Stream Deck\n        registerPluginOrPI(inRegisterEvent, inUUID);\n\n        // Request the global settings of the plugin\n        requestGlobalSettings(inUUID);\n    };\n\n    // Create actions\n    let pi;\n\n    if (action === 'com.elgato.philips-hue.power') {\n        pi = new PowerPI(inUUID, language, streamDeckVersion, pluginVersion);\n    }\n    else if (action === 'com.elgato.philips-hue.color') {\n        pi = new ColorPI(inUUID, language, streamDeckVersion, pluginVersion);\n    }\n    else if (action === 'com.elgato.philips-hue.cycle') {\n        pi = new CyclePI(inUUID, language, streamDeckVersion, pluginVersion);\n    }\n    else if (action === 'com.elgato.philips-hue.brightness') {\n        pi = new BrightnessPI(inUUID, language, streamDeckVersion, pluginVersion, isEncoder);\n    }\n    else if (action === 'com.elgato.philips-hue.temperature') {\n      pi = new TemperaturePI(inUUID, language, streamDeckVersion, pluginVersion, isEncoder);\n  }\n    else if (action === 'com.elgato.philips-hue.scene') {\n        pi = new ScenePI(inUUID, language, streamDeckVersion, pluginVersion);\n    }\n    else if (action === 'com.elgato.philips-hue.brightness-rel') {\n        pi = new BrightnessRelPI(inUUID, language, streamDeckVersion, pluginVersion);\n    }\n\n    websocket.onmessage = msg => {\n        // Received message from Stream Deck\n        let jsonObj = JSON.parse(msg.data);\n        let event = jsonObj['event'];\n        let jsonPayload = jsonObj['payload'];\n\n        if (event === 'didReceiveGlobalSettings') {\n            // Set global plugin settings\n            globalSettings = jsonPayload['settings'];\n        }\n        else if (event === 'didReceiveSettings') {\n            // Save global settings after default was set\n            settings = jsonPayload['settings'];\n        }\n        else if (event === 'sendToPropertyInspector') {\n            // Save global cache\n            cache = jsonPayload;\n\n            // Load bridges and lights\n            pi.loadBridges();\n        }\n    };\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/js/pi.js",
    "content": "/**\n@file      pi.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\nfunction PI(inContext, inLanguage, inStreamDeckVersion, inPluginVersion) {\n    // Init PI\n    let instance = this;\n\n    const values = [1,2,3,4,5,10,20];\n    this.getEncoderOptions = (settingsValue, forEncoder) => {\n      const selectedIndex = values.indexOf(Number(settingsValue));\n      return forEncoder === true ? `<div type=\"select\" class=\"sdpi-item\">\n    <div class=\"sdpi-item-label\" id=\"scaleticks-label\"></div>\n    <select id=\"scaleticks-input\" class=\"sdpi-item-value select\" selectedIndex=\"${selectedIndex}\">\n      ${values.map((value, index) => `<option value=\"${value}\" ${index === selectedIndex ? 'selected' : ''}>${value}x</option>`).join('')}\n  </select>\n    </select>\n  </div>`: ''\n    };\n\n    // Public localizations for the UI\n    this.localization = {};\n\n    // Add event listener\n    document.getElementById('bridge-select').addEventListener('change', bridgeChanged);\n    document.getElementById('light-select').addEventListener('change', lightsChanged);\n    document.addEventListener('saveBridge', setupCallback);\n\n    // Load the localizations\n    getLocalization(inLanguage, (inStatus, inLocalization) => {\n        if (inStatus) {\n            // Save public localization\n            instance.localization = inLocalization['PI'];\n\n            // Localize the PI\n            instance.localize();\n        }\n        else {\n            log(inLocalization);\n        }\n    });\n\n    // Localize the UI\n    this.localize = () => {\n        // Check if localizations were loaded\n        if (instance.localization == null) {\n            return;\n        }\n\n        // Localize the bridge select\n        document.getElementById('bridge-label').innerHTML = instance.localization['Bridge'];\n        document.getElementById('no-bridges').innerHTML = instance.localization['NoBridges'];\n        document.getElementById('add-bridge').innerHTML = instance.localization['AddBridge'];\n\n        // Localize the light and group select\n        document.getElementById('lights-label').innerHTML = instance.localization['Lights'];\n        document.getElementById('lights').label = instance.localization['LightsTitle'];\n        document.getElementById('no-lights').innerHTML = instance.localization['NoLights'];\n        document.getElementById('no-groups').innerHTML = instance.localization['NoGroups'];\n\n        // Groups label is removed for scenes PI\n        if (document.getElementById('groups') != null) {\n            document.getElementById('groups').label = instance.localization['GroupsTitle'];\n        }\n    };\n\n    // Show all paired bridges\n    this.loadBridges = () => {\n        // Remove previously shown bridges\n        let bridges = document.getElementsByClassName('bridges');\n        while (bridges.length > 0) {\n            bridges[0].parentNode.removeChild(bridges[0]);\n        }\n\n        // Check if any bridge is paired\n        if (Object.keys(cache).length > 0) {\n            // Hide the 'No Bridges' option\n            document.getElementById('no-bridges').style.display = 'none';\n\n            // Sort the bridges alphabetically\n            let bridgeIDsSorted = Object.keys(cache).sort((a, b) => {\n                return cache[a].name.localeCompare(cache[b].name);\n            });\n\n            // Add the bridges\n            bridgeIDsSorted.forEach(inBridgeID => {\n                // Add the group\n                let option = `\n                  <option value='${inBridgeID}' class='bridges'>${cache[inBridgeID].name}</option>\n                `;\n                document.getElementById('no-bridges').insertAdjacentHTML('beforebegin', option);\n            });\n\n            // Check if the bridge is already configured\n            if (settings.bridge !== undefined) {\n                // Select the currently configured bridge\n                document.getElementById('bridge-select').value = settings.bridge;\n            }\n\n            // Load the lights\n            loadLights();\n        }\n        else {\n            // Show the 'No Bridges' option\n            document.getElementById('no-bridges').style.display = 'block';\n        }\n\n        // Show PI\n        document.getElementById('pi').style.display = 'block';\n    }\n\n    // Show all lights\n    function loadLights() {\n        // Check if any bridge is configured\n        if (!('bridge' in settings)) {\n            return;\n        }\n\n        // Check if the configured bridge is in the cache\n        if (!(settings.bridge in cache)) {\n            return;\n        }\n\n        // Find the configured bridge\n        let bridgeCache = cache[settings.bridge];\n\n        // Remove previously shown lights\n        let lights = document.getElementsByClassName('lights');\n        while (lights.length > 0) {\n            lights[0].parentNode.removeChild(lights[0]);\n        }\n\n        let requireTemperature = instance instanceof ColorPI || instance instanceof TemperaturePI;\n\n        // Check if the bridge has at least one light\n        if (Object.keys(bridgeCache.lights).length > 0) {\n            // Hide the 'No Light' option\n            document.getElementById('no-lights').style.display = 'none';\n\n            // Sort the lights alphabetically\n            let lightIDsSorted = Object.keys(bridgeCache.lights).sort((a, b) => {\n                return bridgeCache.lights[a].name.localeCompare(bridgeCache.lights[b].name);\n            });\n\n            // Add the lights\n            lightIDsSorted.forEach(inLightID => {\n                let light = bridgeCache.lights[inLightID];\n\n                // Check if this is a color action and the lights supports colors\n                if (!(requireTemperature && light.temperature == null && light.xy == null)) {\n                    // Add the light\n                    let option = `\n                      <option value='l-${light.id}' class='lights'>${light.name}</option>\n                    `;\n                    document.getElementById('no-lights').insertAdjacentHTML('beforebegin', option);\n                }\n            });\n        }\n        else {\n            // Show the 'No Light' option\n            document.getElementById('no-lights').style.display = 'block';\n        }\n\n        // Remove previously shown groups\n        let groups = document.getElementsByClassName('groups');\n        while (groups.length > 0) {\n            groups[0].parentNode.removeChild(groups[0]);\n        }\n\n        // Check if the bridge has at least one group\n        if (Object.keys(bridgeCache.groups).length > 0) {\n            // Hide the 'No Group' option\n            document.getElementById('no-groups').style.display = 'none';\n\n            // Sort the groups alphabetically\n            let groupIDsSorted = Object.keys(bridgeCache.groups).sort((a, b) => {\n                return bridgeCache.groups[a].name.localeCompare(bridgeCache.groups[b].name);\n            });\n\n            // Add the groups\n            groupIDsSorted.forEach(inGroupID => {\n                let group = bridgeCache.groups[inGroupID];\n\n                // Check if this is a color action and the lights supports colors\n                if (!(instance instanceof ColorPI && group.temperature == null && group.xy == null)) {\n                    // Add the group\n                    let option = `\n                      <option value='g-${group.id}' class='groups'>${group.name}</option>\n                    `;\n                    document.getElementById('no-groups').insertAdjacentHTML('beforebegin', option);\n                }\n            });\n        }\n        else {\n            // Show the 'No Group' option\n            document.getElementById('no-groups').style.display = 'block';\n        }\n\n        // Check if a light is already setup\n        if (settings.light !== undefined) {\n            // Check if the configured light or group is part of the bridge cache\n            if (!(settings.light in bridgeCache.lights || settings.light in bridgeCache.groups)) {\n                return;\n            }\n\n            // Select the currently configured light or group\n            document.getElementById('light-select').value = settings.light;\n\n            // Dispatch light change event manually\n            // So that the colorPI can set the correct color picker at initialization\n            document.getElementById('light-select').dispatchEvent(new CustomEvent('change', {'detail': {'manual': true}} ));\n        }\n\n        // If this is a scene PI\n        if (instance instanceof ScenePI) {\n            //Load the scenes\n            instance.loadScenes();\n        }\n    }\n\n    // Function called on successful bridge pairing\n    function setupCallback(inEvent) {\n        // Set bridge to the newly added bridge\n        settings.bridge = inEvent.detail.id;\n        instance.saveSettings();\n\n        // Check if global settings need to be initialized\n        if (globalSettings.bridges === undefined) {\n            globalSettings.bridges = {};\n        }\n\n        // Add new bridge to the global settings\n        globalSettings.bridges[inEvent.detail.id] = {\n            ip: inEvent.detail.ip,\n            id: inEvent.detail.id,\n            username: inEvent.detail.username,\n        };\n        saveGlobalSettings(inContext);\n    }\n\n    // Bridge select changed\n    function bridgeChanged(inEvent) {\n        if (inEvent.target.value === 'add') {\n            // Open setup window\n            window.open(`../setup/index.html?language=${inLanguage}&streamDeckVersion=${inStreamDeckVersion}&pluginVersion=${inPluginVersion}`);\n\n            // Select the first in case user cancels the setup\n            document.getElementById('bridge-select').selectedIndex = 0;\n        }\n        else if (inEvent.target.value === 'no-bridges') {\n            // If no bridge was selected, do nothing\n        }\n        else {\n            settings.bridge = inEvent.target.value;\n            instance.saveSettings();\n            instance.loadBridges();\n        }\n    }\n\n    // Light select changed\n    function lightsChanged(inEvent) {\n        if (inEvent.target.value === 'no-lights' || inEvent.target.value === 'no-groups') {\n            // If no light or group was selected, do nothing\n        }\n        else if (inEvent.detail !== undefined) {\n            // If the light was changed via code\n            if (inEvent.detail.manual === true) {\n                // do nothing\n            }\n        }\n        else {\n            settings.light = inEvent.target.value;\n            instance.saveSettings();\n\n            // If this is a scene PI\n            if (instance instanceof ScenePI) {\n                //Load the scenes\n                instance.loadScenes();\n            }\n\n            instance.sendToPlugin({\n              piEvent: 'lightsChanged',\n          });\n        }\n    }\n\n    // Private function to return the action identifier\n    function getAction() {\n        let action\n\n        // Find out type of action\n        if (instance instanceof PowerPI) {\n            action = 'com.elgato.philips-hue.power';\n        }\n        else if (instance instanceof ColorPI) {\n            action = 'com.elgato.philips-hue.color';\n        }\n        else if (instance instanceof CyclePI) {\n            action = 'com.elgato.philips-hue.cycle';\n        }\n        else if (instance instanceof BrightnessPI) {\n            action = 'com.elgato.philips-hue.brightness';\n        }\n        else if (instance instanceof TemperaturePI) {\n          action = 'com.elgato.philips-hue.temperature';\n      }\n        else if (instance instanceof BrightnessRelPI) {\n            action = 'com.elgato.philips-hue.brightness-rel';\n        }\n        else if (instance instanceof ScenePI) {\n            action = 'com.elgato.philips-hue.scene';\n        }\n\n        return action;\n    }\n\n    // Public function to save the settings\n    this.saveSettings = () => {\n        saveSettings(getAction(), inContext, settings);\n    }\n\n    // Public function to send data to the plugin\n    this.sendToPlugin = inData => {\n        sendToPlugin(getAction(), inContext, inData);\n    }\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/js/powerPI.js",
    "content": "/**\n@file      powerPI.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\nfunction PowerPI(inContext, inLanguage, inStreamDeckVersion, inPluginVersion) {\n    // Inherit from PI\n    PI.call(this, inContext, inLanguage, inStreamDeckVersion, inPluginVersion);\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/js/scenePI.js",
    "content": "/**\n@file      scenePI.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\nfunction ScenePI(inContext, inLanguage, inStreamDeckVersion, inPluginVersion) {\n    // Init ScenePI\n    let instance = this;\n\n    // Inherit from PI\n    PI.call(this, inContext, inLanguage, inStreamDeckVersion, inPluginVersion);\n\n    // Hide lights from light select\n    document.getElementById('lights').style.display = 'none';\n\n    // Remove groups label from lights select\n    let groups = document.getElementById('groups');\n    let groupsChildren = document.getElementById('groups').children;\n    let lightSelect = document.getElementById('light-select');\n\n    lightSelect.removeChild(groups);\n    lightSelect.appendChild(groupsChildren[0]);\n\n    // Before overwriting parent method, save a copy of it\n    let piLocalize = this.localize;\n\n    // Localize the UI\n    this.localize = () => {\n        // Call PIs localize method\n        piLocalize.call(instance);\n\n        // Localize the scene select\n        document.getElementById('lights-label').innerHTML = instance.localization['Group'];\n        document.getElementById('scene-label').innerHTML = instance.localization['Scene'];\n        document.getElementById('no-scenes').innerHTML = instance.localization['NoScenes'];\n    };\n\n    // Add scene select\n    document.getElementById('placeholder').innerHTML = `\n        <div class='sdpi-item'>\n          <div class='sdpi-item-label' id='scene-label'></div>\n          <select class='sdpi-item-value select' id='scene-select'>\n            <option id='no-scenes' value='no-scene'></option>\n          </select>\n        </div>\n    `;\n\n    // Add event listener\n    document.getElementById('scene-select').addEventListener('change', sceneChanged);\n\n    // Scenes changed\n    function sceneChanged(inEvent) {\n        if (inEvent.target.value === 'no-scenes') {\n            // do nothing\n        }\n        else {\n            // Save the new scene settings\n            settings.scene = inEvent.target.value;\n            instance.saveSettings();\n\n            // Inform the plugin that a new scene is set\n            instance.sendToPlugin({\n                piEvent: 'valueChanged',\n            });\n        }\n    }\n\n    // Show all scenes\n    this.loadScenes = () => {\n        // Check if any bridge is configured\n        if (!('bridge' in settings)) {\n            return;\n        }\n\n        // Check if the configured bridge is in the cache\n        if (!(settings.bridge in cache)) {\n            return;\n        }\n\n        // Find the configured bridge\n        let bridgeCache = cache[settings.bridge];\n\n        // Check if any light is configured\n        if (!('light' in settings)) {\n            return;\n        }\n\n        // Check if the light was set to a group\n        if (!(settings.light.indexOf('g-') !== -1)) {\n            return;\n        }\n\n        // Check if the configured group is in the cache\n        if (!(settings.light in bridgeCache.groups)) {\n            return;\n        }\n\n        // Find the configured group\n        let groupCache = bridgeCache.groups[settings.light];\n\n        // Remove previously shown scenes\n        let scenes = document.getElementsByClassName('scenes');\n        while (scenes.length > 0) {\n            scenes[0].parentNode.removeChild(scenes[0]);\n        }\n\n        // Check if the group has at least one scene\n        if (Object.keys(groupCache.scenes).length > 0) {\n            // Hide the 'No Scenes' option\n            document.getElementById('no-scenes').style.display = 'none';\n\n            // Sort the scenes alphabetically\n            let sceneIDsSorted = Object.keys(groupCache.scenes).sort((a, b) => {\n                return groupCache.scenes[a].name.localeCompare(groupCache.scenes[b].name);\n            });\n\n            // Add the scenes\n            sceneIDsSorted.forEach((inSceneID) => {\n                // Add the scene\n                let scene = groupCache.scenes[inSceneID];\n                let option = `<option value=\"${scene.id}\" class=\"scenes\">${scene.name}</option>`;\n                document.getElementById('no-scenes').insertAdjacentHTML('beforebegin', option);\n            });\n        }\n        else {\n            // Show the 'No Scenes' option\n            document.getElementById('no-scenes').style.display = 'block';\n        }\n\n        // Check if scene is already setup\n        if (settings.scene !== undefined) {\n            // Check if the configured scene is in this group\n            if (!(settings.scene in groupCache.scenes)) {\n                return;\n            }\n\n            // Select the currently configured scene\n            document.getElementById('scene-select').value = settings.scene;\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/js/temperaturePI.js",
    "content": "/**\n@file      temperaturePI.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\nfunction TemperaturePI(inContext, inLanguage, inStreamDeckVersion, inPluginVersion, isEncoder) {\n    // Init TemperaturePI\n    let instance = this;\n\n    // Inherit from PI\n    PI.call(this, inContext, inLanguage, inStreamDeckVersion, inPluginVersion);\n\n    // Before overwriting parent method, save a copy of it\n    let piLocalize = this.localize;\n    \n    // Localize the UI\n    this.localize = () => {\n        // Call PIs localize method\n        piLocalize.call(instance);\n\n        // Localize the brightness label\n        document.getElementById('temperature-label').innerHTML = instance.localization['Temperature'];\n        if(isEncoder) {\n          document.getElementById('scaleticks-label').innerHTML = instance.localization['Scale Ticks'] || 'Scale Ticks';\n        }\n    };\n\n    // Add brightness slider\n    document.getElementById('placeholder').innerHTML = `\n      <div type=\"range\" class=\"sdpi-item\">\n        <div class=\"sdpi-item-label\" id=\"temperature-label\"></div>\n        <div class=\"sdpi-item-value\">\n            <span class=\"clickable\" value=0>0%</span>\n            <input class=\"floating-tooltip\" data-suffix=\"%\" type=\"range\" id=\"temperature-input\" min=\"1\" max=\"100\" value=\"${settings.temperature}\">\n            <span class=\"clickable\" value=\"100\">100%</span>\n        </div>\n      </div>\n      ${this.getEncoderOptions(settings.scaleTicks, isEncoder)}\n    `;\n\n    // Initialize the tooltips\n    initToolTips();\n\n    // Add event listener\n    document.getElementById('temperature-input').addEventListener('input', temperatureChanged);\n    if(isEncoder) {\n      document.getElementById('scaleticks-input').addEventListener('change', scaleticksChanged);\n    }\n\n    // Brightness changed\n    function temperatureChanged(inEvent) {\n        // Save the new brightness settings\n        settings.temperature = inEvent.target.value;\n        instance.saveSettings();\n\n        // Inform the plugin that a new brightness is set\n        instance.sendToPlugin({\n            piEvent: 'valueChanged',\n        });\n    }\n\n    function scaleticksChanged(inEvent) {\n      settings.scaleTicks = inEvent.target.value;\n      instance.saveSettings();\n    }\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/js/tooltips.js",
    "content": "//==============================================================================\n/**\n@file       tooltips.js\n@brief      Philips Hue Plugin\n@copyright  (c) 2019, Corsair Memory, Inc.\n            This source code is licensed under the MIT-style license found in the LICENSE file.\n**/\n//==============================================================================\n\nfunction rangeToPercent(value, min, max) {\n    return ((value - min) / (max - min));\n}\n\nfunction initToolTips() {\n    const tooltip = document.querySelector('.sdpi-info-label');\n    const arrElements = document.querySelectorAll('.floating-tooltip');\n\n    arrElements.forEach((e,i) => {\n        initToolTip(e, tooltip)\n    })\n}\n\nfunction initToolTip(element, tooltip) {\n    const tw = tooltip.getBoundingClientRect().width;\n    const suffix = element.getAttribute('data-suffix') || '';\n\n    const updateTooltip = () => {\n        const elementRect = element.getBoundingClientRect();\n        const w = elementRect.width - tw / 2;\n        const percent = rangeToPercent(\n            element.value,\n            element.min,\n            element.max,\n        );\n\n        tooltip.textContent = suffix !== '' ? `${element.value} ${suffix}` : String(element.value);\n        tooltip.style.left = `${elementRect.left + Math.round(w * percent) - tw / 4}px`;\n        tooltip.style.top = `${elementRect.top - 32}px`;\n    };\n\n    if (element) {\n        element.addEventListener('mouseenter', () => {\n            tooltip.classList.remove('hidden');\n            tooltip.classList.add('shown');\n            updateTooltip();\n        }, false);\n\n        element.addEventListener('mouseout', () => {\n            tooltip.classList.remove('shown');\n            tooltip.classList.add('hidden');\n            updateTooltip();\n        }, false);\n\n\t\telement.addEventListener('input', updateTooltip, false);\n    }\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/index.html",
    "content": "<!DOCTYPE HTML>\n<html>\n  <head>\n    <title>com.elgato.philips-hue</title>\n    <meta charset=\"UTF-8\">\n    <!-- Import scripts -->\n    <script src=\"js/timers.js\"></script>\n    <script src=\"js/utils.js\"></script>\n    <script src=\"js/philips/cache.js\"></script>\n    <script src=\"js/main.js\"></script>\n    <script src=\"js/action.js\"></script>\n    <script src=\"js/propertyAction.js\"></script>\n    <script src=\"js/colorAction.js\"></script>\n    <script src=\"js/cycleAction.js\"></script>\n    <script src=\"js/powerAction.js\"></script>\n    <script src=\"js/temperatureAction.js\"></script>\n    <script src=\"js/brightnessAction.js\"></script>\n    <script src=\"js/brightnessRelAction.js\"></script>\n    <script src=\"js/sceneAction.js\"></script>\n    <script src=\"js/philips/meethue.js\"></script>\n  </head>\n</html>\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/js/action.js",
    "content": "/**\n@file      action.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\n// Prototype which represents an action\nfunction Action(inContext, inSettings, jsn) {\n    // Init Action\n    let instance = this;\n    let debounceDelay = 50;\n\n    // Private variable containing the context of the action\n    let context = inContext;\n    this.isEncoder = jsn?.payload?.controller == 'Encoder';\n    this.isInMultiAction = jsn?.payload?.isInMultiAction;\n\n    this.savedValue = -1;\n    this.savedPower = null;\n    // Private variable containing the settings of the action\n    let settings = inSettings;\n    \n    let updateActionsEvent = new CustomEvent('updateActions', {detail: {sender: this}} );\n\n    // Set the default values\n    setDefaults();\n\n    // Public function returning the context\n    this.getContext = () => {\n        return context;\n    };\n\n    // Public function returning the settings\n    this.getSettings = () => {\n        return settings;\n    };\n\n    // Public function for settings the settings\n    this.setSettings = inSettings => {\n        settings = inSettings;\n    };\n\n    // Public function called when new cache is available\n    this.newCacheAvailable = inCallback => {\n        // Set default settings\n        setDefaults(inCallback);\n    };\n\n    this.updateAllActions = () => {\n      document.dispatchEvent(updateActionsEvent);\n    };\n\n    this.updateActionIfCacheAvailable = (ctx) => {\n        // update the action and its display\n      const cacheSize = Object.keys(cache.data).length;\n      if(cacheSize === 0) {\n        // after a willAppear event, the cache is not yet available\n        wait(1000).then(() => {\n          this.updateAction();\n        });\n      } else {\n        this.updateAction();\n      }\n    }\n\n    this.setFeedback = (context, value, opacity) => {\n      console.assert(websocket, 'no connection to websocket');\n      if(websocket && this.isEncoder) {\n        // send the values to the encoder  (SD+)\n        setFeedback(context, {\n          value: {\n            value,\n            opacity\n          },\n          indicator: {\n            value,\n            opacity\n          }\n        });\n      }\n    };\n\n    this.updateDisplay = (lightOrGroup, property) => {\n      if(!lightOrGroup) {\n        if(!this.getCurrentLightOrGroup) return;\n        const curLightOrGroup = this.getCurrentLightOrGroup();\n        if(curLightOrGroup) {\n          lightOrGroup = curLightOrGroup.objCache;\n          this.savedValue = -1; // force update\n        }\n        console.assert(lightOrGroup, 'no light or group', curLightOrGroup);\n        if(!lightOrGroup) return;\n      };\n      if(this.isInMultiAction || !this.isEncoder) return;\n      const powerHue = property == 'power' ? !lightOrGroup?.power : lightOrGroup?.power;\n      let actionValue = lightOrGroup?.[this.property];\n  \n      // check if the values have changed\n      if(actionValue === this.savedValue && powerHue === this.savedPower) {\n        return;\n      }\n      // cache the values\n      this.savedValue = actionValue;\n      this.savedPower = powerHue;\n  \n      // values in hue are 0-254, convert to 0-100 // !this is not true for temperature\n      let value;\n      if(this.property == 'temperature') {\n        const ct = lightOrGroup.originalValue?.capabilities?.control?.ct;\n        console.assert(ct, 'no ct in capabilities', lightOrGroup);\n        if(!ct) return;\n        value = parseInt(Utils.percent(lightOrGroup.temperature, ct.min, ct.max));\n      } else {\n        value = parseInt(actionValue / 2.54);\n      }\n      // if the light is off, set the opacity to 0.5\n      const opacity = powerHue ? 1 :0.5;\n      this.setFeedback(inContext, value, opacity);\n    };\n\n    this.togglePower = (inContext) => {\n      const target = this.getCurrentLightOrGroup();\n      if(!target) return;\n      const targetState = !target.objCache.power;\n      target.obj.setPower(targetState, (success, error) => {\n        if (success) {\n            target.objCache.power = targetState;\n            // cache.refresh();\n            this.updateAllActions();\n        }\n        else {\n            log(error);\n            showAlert(inContext);\n        }\n      });\n      return target;\n    };\n\n    this.getVerifiedSettings = function(inContext, requiredPropertySetting = null) {\n\n      // Check if any bridge is configured\n      if(!('bridge' in settings)) {\n        log('No bridge configured');\n        showAlert(inContext);\n        return false;\n      }\n  \n      // Check if the configured bridge is in the cache\n      if(!(settings.bridge in cache.data)) {\n        log(`Bridge ${settings.bridge} not found in cache`);\n        showAlert(inContext);\n        return false;\n      }\n  \n      // Check if any light is configured\n      if(!('light' in settings)) {\n        log('No light or group configured');\n        showAlert(inContext);\n        return false;\n      }\n\n      if(requiredPropertySetting) {\n        if(!(requiredPropertySetting in settings)) {\n          log(`No ${requiredPropertySetting} configured`);\n          showAlert(inContext);\n          return;\n        }\n      }\n   \n      // Find the configured bridge\n      let bridgeCache = cache.data[settings.bridge];\n      if(bridgeCache === false) {\n        console.warn('getVerifiedSettings: no bridge in cache');\n        return false;\n      };\n  \n      // Check if the configured light or group is in the cache\n      if(!(settings.light in bridgeCache.lights || settings.light in bridgeCache.groups)) {\n        log(`Light or group ${settings.light} not found in cache`, settings, bridgeCache);\n        showAlert(inContext);\n        return false;\n      }\n  \n      return settings;\n    };\n\n    // Private function to set the defaults\n    function setDefaults(inCallback) {\n        // If at least one bridge is paired\n        if (!(Object.keys(cache.data).length > 0)) {\n            // If a callback function was given\n            if (inCallback !== undefined) {\n                // Execute the callback function\n                inCallback();\n            }\n            return;\n        }\n\n        // Find out type of action\n        let action;\n        if (instance instanceof PowerAction) {\n            action = 'com.elgato.philips-hue.power';\n        }\n        else if (instance instanceof ColorAction) {\n            action = 'com.elgato.philips-hue.color';\n        }\n        else if (instance instanceof CycleAction) {\n            action = 'com.elgato.philips-hue.cycle';\n        }\n        else if (instance instanceof BrightnessAction) {\n            action = 'com.elgato.philips-hue.brightness';\n        }\n\t\telse if (instance instanceof BrightnessRelAction) {\n            action = 'com.elgato.philips-hue.brightness-rel';\n        }\n        else if (instance instanceof SceneAction) {\n            action = 'com.elgato.philips-hue.scene';\n        }\n\n        // If no bridge is set for this action\n        if (!('bridge' in settings)) {\n            // Sort the bridges alphabetically\n            let bridgeIDsSorted = Object.keys(cache.data).sort((a, b) => {\n                return cache.data[a].name.localeCompare(cache.data[b].name);\n            });\n\n            // Set the bridge automatically to the first one\n            settings.bridge = bridgeIDsSorted[0];\n\n            // Save the settings\n            saveSettings(action, inContext, settings);\n        }\n\n        // Find the configured bridge\n        let bridgeCache = cache.data[settings.bridge];\n\n        // If no light is set for this action\n        if (!('light' in settings)) {\n            // First try to set a group, because scenes only support groups\n            // If the bridge has at least one group\n            if (Object.keys(bridgeCache.groups).length > 0) {\n                // Sort the groups automatically\n                let groupIDsSorted = Object.keys(bridgeCache.groups).sort((a, b) => {\n                    return bridgeCache.groups[a].name.localeCompare(bridgeCache.groups[b].name);\n                });\n\n                // Set the light automatically to the first group\n                settings.light = groupIDsSorted[0];\n\n                // Save the settings\n                saveSettings(action, inContext, settings);\n            }\n            else if (Object.keys(bridgeCache.lights).length > 0) {\n                // Sort the lights automatically\n                let lightIDsSorted = Object.keys(bridgeCache.lights).sort((a, b) => {\n                    return bridgeCache.lights[a].name.localeCompare(bridgeCache.lights[b].name);\n                });\n\n                // Set the light automatically to the first light\n                settings.light = lightIDsSorted[0];\n\n                // Save the settings\n                saveSettings(action, inContext, settings);\n            }\n        }\n\n        // If a callback function was given\n        if (inCallback !== undefined) {\n            // Execute the callback function\n            inCallback();\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/js/brightnessAction.js",
    "content": "/**\n@file      brightnessAction.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\nfunction BrightnessAction(inContext, inSettings, jsn) {\n  this.property = 'brightness';\n  // Inherit from PropertyAction\n  PropertyAction.call(this, inContext, inSettings, jsn);\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/js/brightnessRelAction.js",
    "content": "/**\n@file      brightnessRelAction.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\n// Prototype which represents a brightness action\nfunction BrightnessRelAction(inContext, inSettings) {\n    // Init BrightnessRelAction\n    let instance = this;\n\n    // Inherit from Action\n    Action.call(this, inContext, inSettings);\n\n    // Set the default values\n    setDefaults();\n\n    // Public function called on key up event\n    this.onKeyUp = (inContext, inSettings, inCoordinates, inUserDesiredState, inState) => {\n        // If onKeyUp was triggered manually, load settings\n        if (inSettings === undefined) {\n            inSettings = instance.getSettings();\n        }\n\n        // Set icon according to relative value\n        setState(inContext, inSettings.brightnessRel >= 0 ? 0 : 1);\n\n        // Check if any bridge is configured\n        if (!('bridge' in inSettings)) {\n            log('No bridge configured');\n            showAlert(inContext);\n            return;\n        }\n\n        // Check if the configured bridge is in the cache\n        if (!(inSettings.bridge in cache.data)) {\n            log(`Bridge ${inSettings.bridge} not found in cache`);\n            showAlert(inContext);\n            return;\n        }\n\n        // Find the configured bridge\n        let bridgeCache = cache.data[inSettings.bridge];\n\n        // Check if any light is configured\n        if (!('light' in inSettings)) {\n            log('No light or group configured');\n            showAlert(inContext);\n            return;\n        }\n\n        // Check if the configured light or group is in the cache\n        if (!(inSettings.light in bridgeCache.lights || inSettings.light in bridgeCache.groups)) {\n            log(`Light or group ${inSettings.light} not found in cache`);\n            showAlert(inContext);\n            return;\n        }\n\n        // Check if any brightness is configured\n        if (!('brightnessRel' in inSettings)) {\n            log('No relative brightness configured');\n            showAlert(inContext);\n            return;\n        }\n\n        // Create a bridge instance\n        let bridge = new Bridge(bridgeCache.ip, bridgeCache.id, bridgeCache.username);\n\n        // Create a light or group object\n        let objCache, obj;\n        if (inSettings.light.indexOf('l') !== -1) {\n            objCache = bridgeCache.lights[inSettings.light];\n            obj = new Light(bridge, objCache.id);\n        }\n        else {\n            objCache = bridgeCache.groups[inSettings.light];\n            obj = new Group(bridge, objCache.id);\n        }\n\n        // Convert brightness\n        let brightness;\n        if (objCache.power) {\n            let brightnessRel = (objCache.brightness / 2.54) + parseInt(inSettings.brightnessRel);\n            brightness = Math.round(brightnessRel * 2.54);\n        }\n        else {\n            brightness = parseInt(inSettings.brightnessRel);\n        }\n\n        if (brightness > 254) {\n            brightness = 254;\n        }\n        else if (brightness < 0) {\n            brightness = 0;\n        }\n\n        // Turn lights off if brightness is 0\n        if (brightness <= 0) {\n            obj.setPower(false, (inSuccess, inError) => {\n                if (inSuccess) {\n                    objCache.power = false;\n                }\n                else {\n                    log(inError);\n                    showAlert(inContext);\n                }\n            });\n        }\n        else {\n            // Set light or group state\n            obj.setBrightness(brightness, (inSuccess, inError) => {\n                if (inSuccess) {\n                    objCache.brightness = brightness;\n                }\n                else {\n                    log(inError);\n                    showAlert(inContext);\n                }\n            });\n        }\n    };\n\n    // Before overwriting parent method, save a copy of it\n    let actionNewCacheAvailable = this.newCacheAvailable;\n\n    // Public function called when new cache is available\n    this.newCacheAvailable = inCallback => {\n        // Call actions newCacheAvailable method\n        actionNewCacheAvailable.call(instance, () => {\n            // Set defaults\n            setDefaults();\n\n            // Call the callback function\n            inCallback();\n        });\n    };\n\n    // Private function to set the defaults\n    function setDefaults() {\n        // Get the settings and the context\n        let settings = instance.getSettings();\n        let context = instance.getContext();\n\n        // If brightness is already set for this action\n        if ('brightnessRel' in settings) {\n            return;\n        }\n\n        // Set the relative brightness to 0\n        settings.brightnessRel = 0;\n\n        // Save the settings\n        saveSettings('com.elgato.philips-hue.brightness-rel', context, settings);\n    }\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/js/colorAction.js",
    "content": "/**\n@file      colorAction.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\n// Prototype which represents a color action\nfunction ColorAction(inContext, inSettings) {\n    // Init ColorAction\n    let instance = this;\n\n    // Inherit from Action\n    Action.call(this, inContext, inSettings);\n\n    // Set the default values\n    setDefaults();\n\n    // Public function called on key up event\n    this.onKeyUp = (inContext) => {\n\n      const settings = this.getVerifiedSettings(inContext, 'color');\n      if(false === settings) return;\n      let bridgeCache = cache.data[settings.bridge];\n\n        // Create a bridge instance\n        let bridge = new Bridge(bridgeCache.ip, bridgeCache.id, bridgeCache.username);\n\n        // Create a light or group object\n        let objCache, obj;\n        if (settings.light.indexOf('l') !== -1) {\n            objCache = bridgeCache.lights[settings.light];\n            obj = new Light(bridge, objCache.id);\n        }\n        else {\n            objCache = bridgeCache.groups[settings.light];\n            obj = new Group(bridge, objCache.id);\n        }\n\n        // Check if this is a color or temperature light\n        if (settings.color.indexOf('#') !== -1) {\n            // Convert light color to hardware independent XY color\n            let xy = Bridge.hex2xy(settings.color);\n\n            // Set light or group state\n            obj.setXY(xy, (inSuccess, inError) => {\n                if (inSuccess) {\n                    objCache.xy = xy;\n                }\n                else {\n                    log(inError);\n                    showAlert(inContext);\n                }\n            });\n        }\n        else {\n            // Note: Some lights do not support the full range\n            let min = 153.0;\n            let max = 500.0;\n\n            let minK = 2000.0;\n            let maxK = 6500.0;\n\n            // Convert light color\n            let percentage = (settings.color - minK) / (maxK - minK);\n            let invertedPercentage = -1 * (percentage - 1.0);\n            let temperature = Math.round(invertedPercentage * (max - min) + min);\n\n            // Set light or group state\n            obj.setTemperature(temperature, (inSuccess, inError) => {\n                if (inSuccess) {\n                    objCache.ct = temperature;\n                }\n                else {\n                    log(inError);\n                    showAlert(inContext);\n                }\n            });\n        }\n    };\n\n    // Before overwriting parent method, save a copy of it\n    let actionNewCacheAvailable = this.newCacheAvailable;\n\n    // Public function called when new cache is available\n    this.newCacheAvailable = inCallback => {\n        // Call actions newCacheAvailable method\n        actionNewCacheAvailable.call(instance, () => {\n            // Set defaults\n            setDefaults();\n\n            // Call the callback function\n            inCallback();\n        });\n    };\n\n    // Private function to set the defaults\n    function setDefaults() {\n        // Get the settings and the context\n        let settings = instance.getSettings();\n        let context = instance.getContext();\n\n        // Check if any bridge is configured\n        if (!('bridge' in settings)) {\n            return;\n        }\n\n        // Check if the configured bridge is in the cache\n        if (!(settings.bridge in cache.data)) {\n            return;\n        }\n\n        // Find the configured bridge\n        let bridgeCache = cache.data[settings.bridge];\n\n        // Check if a light was set for this action\n        if (!('light' in settings)) {\n            return;\n        }\n\n        // Check if the configured light or group is in the cache\n        if (!(settings.light in bridgeCache.lights || settings.light in bridgeCache.groups)) {\n            return;\n        }\n\n        // Get a light or group cache\n        let lightCache;\n        if (settings.light.indexOf('l-') !== -1) {\n            lightCache = bridgeCache.lights[settings.light];\n        }\n        else {\n            lightCache = bridgeCache.groups[settings.light];\n        }\n\n        // Check if any color is configured\n        if ('color' in settings) {\n            // Check if the set color is supported by the light\n            if (settings.color.charAt(0) === '#' && lightCache.xy != null) {\n                return;\n            }\n            else if (settings.color.charAt(0) !== '#' && lightCache.xy == null) {\n                return;\n            }\n        }\n\n        // Check if the light supports all colors\n        if (lightCache.xy != null) {\n            // Set white as the default color\n            settings.color = '#ffffff';\n        }\n        else {\n            // Set white as the default temperature\n            settings.color = '4250';\n        }\n\n        // Save the settings\n        saveSettings('com.elgato.philips-hue.color', context, settings);\n    }\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/js/cycleAction.js",
    "content": "/**\n@file      cycleAction.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\n// Prototype which represents a color action\nfunction CycleAction(inContext, inSettings) {\n  // Init CycleAction\n  let instance = this;\n\n  // Index of current active Color\n  let currentColor = -1;\n\n  // Inherit from Action\n  Action.call(this, inContext, inSettings);\n\n  // Set the default values\n  setDefaults();\n\n  // Public function called on key up event\n  this.onKeyUp = (inContext) => {\n    const settings = this.getVerifiedSettings(inContext, 'colors');\n    if(false === settings) return;\n\n    let bridgeCache = cache.data[settings.bridge];\n\n    // Create a bridge instance\n    let bridge = new Bridge(bridgeCache.ip, bridgeCache.id, bridgeCache.username);\n\n    // Create a light or group object\n    let objCache, obj;\n    if(settings.light.indexOf('l') !== -1) {\n      objCache = bridgeCache.lights[settings.light];\n      obj = new Light(bridge, objCache.id);\n    }\n    else {\n      objCache = bridgeCache.groups[settings.light];\n      obj = new Group(bridge, objCache.id);\n    }\n\n    // Reset current Color index\n    if(currentColor + 1 >= settings.colors.length) {\n      currentColor = -1;\n    }\n\n    let colorIndex = currentColor + 1;\n\n    // Check if this is a color or temperature light\n    if(settings.colors[colorIndex].indexOf('#') !== -1) {\n      // Convert light color to hardware independent XY color\n      let xy = Bridge.hex2xy(settings.colors[colorIndex]);\n\n      // Set light or group state\n      obj.setXY(xy, (inSuccess, inError) => {\n        if(inSuccess) {\n          objCache.xy = xy;\n          ++currentColor;\n        }\n        else {\n          log(inError);\n          showAlert(inContext);\n        }\n      });\n    }\n    else {\n      // Note: Some lights do not support the full range\n      let min = 153.0;\n      let max = 500.0;\n\n      let minK = 2000.0;\n      let maxK = 6500.0;\n\n      // Convert light color\n      let percentage = (settings.colors[colorIndex] - minK) / (maxK - minK);\n      let invertedPercentage = -1 * (percentage - 1.0);\n      let temperature = Math.round(invertedPercentage * (max - min) + min);\n\n      // Set light or group state\n      obj.setTemperature(temperature, (inSuccess, inError) => {\n        if(inSuccess) {\n          objCache.ct = temperature;\n          ++currentColor;\n        }\n        else {\n          log(inError);\n          showAlert(inContext);\n        }\n      });\n    }\n  };\n\n  // Before overwriting parent method, save a copy of it\n  let actionNewCacheAvailable = this.newCacheAvailable;\n\n  // Public function called when new cache is available\n  this.newCacheAvailable = inCallback => {\n    // Call actions newCacheAvailable method\n    actionNewCacheAvailable.call(instance, () => {\n      // Set defaults\n      setDefaults();\n\n      // Call the callback function\n      inCallback();\n    });\n  };\n\n  // Private function to set the defaults\n  function setDefaults() {\n    // Get the settings and the context\n    let settings = instance.getSettings();\n    let context = instance.getContext();\n\n    // Check if any bridge is configured\n    if(!('bridge' in settings)) {\n      return;\n    }\n\n    // Check if the configured bridge is in the cache\n    if(!(settings.bridge in cache.data)) {\n      return;\n    }\n\n    // Find the configured bridge\n    let bridgeCache = cache.data[settings.bridge];\n\n    // Check if a light was set for this action\n    if(!('light' in settings)) {\n      return;\n    }\n\n    // Check if the configured light or group is in the cache\n    if(!(settings.light in bridgeCache.lights || settings.light in bridgeCache.groups)) {\n      return;\n    }\n\n    // Get a light or group cache\n    let lightCache;\n    if(settings.light.indexOf('l-') !== -1) {\n      lightCache = bridgeCache.lights[settings.light];\n    }\n    else {\n      lightCache = bridgeCache.groups[settings.light];\n    }\n\n    // Check if any color is configured\n    if('colors' in settings) {\n      // Check if the set color is supported by the light\n      if(settings.colors[0].charAt(0) === '#' && lightCache.xy != null) {\n        return;\n      }\n      else if(settings.colors[0].charAt(0) !== '#' && lightCache.xy == null) {\n        return;\n      }\n    }\n\n    // Check if the light supports all colors\n    if(lightCache.xy != null) {\n      // Set white as the default color\n      settings.colors = ['#ff0000', '#00ff00', '#0000ff'];\n    }\n    else {\n      // Set white as the default temperature\n      settings.colors = ['2230', '4250', '6410'];\n    }\n\n    // Save the settings\n    saveSettings('com.elgato.philips-hue.cycle', context, settings);\n  }\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/js/main.js",
    "content": "/**\n@file      main.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\n// Global web socket\nvar websocket = null;\n\n// Global cache\nvar cache = {};\n\n// Global settings\nvar globalSettings = {};\n\nconst throttleDialRotate = Utils.throttle((fn) => {\n  if (fn) fn();\n}, 60);\n\nconst debounceDialRotate = Utils.debounce((jsonObj) => {\n  console.log('debounceDialRotate', jsonObj);\n}, 300);\n\n// Setup the websocket and handle communication\nfunction connectElgatoStreamDeckSocket(inPort, inPluginUUID, inRegisterEvent, inInfo) {\n    // Create array of currently used actions\n    let actions = {};\n    window.MACTIONS = actions;\n    // Create a cache\n    cache = new Cache();\n\n    // Open the web socket to Stream Deck\n    // Use 127.0.0.1 because Windows needs 300ms to resolve localhost\n    websocket = new WebSocket(`ws://127.0.0.1:${inPort}`);\n    const _info = JSON.parse(inInfo);\n    const [ version, major, minor, build ] = _info.application.version.split(\".\").map(e => parseInt(e, 10));\n    const hasDialPress = version == 6 && major < 4;\n\n    // Web socket is connected\n    websocket.onopen = () => {\n        // Register plugin to Stream Deck\n        registerPluginOrPI(inRegisterEvent, inPluginUUID);\n\n        // Request the global settings of the plugin\n        requestGlobalSettings(inPluginUUID);\n    };\n\n    document.addEventListener('updateActions', (e) => {\n      // updateAction carries the sender of the event so we can skip it\n      const sender = e.detail?.sender;\n      Object.keys(actions).forEach(inContext => {\n        if(actions[inContext].updateAction) {\n          // don't update the sender\n          if(actions[inContext] === sender) return;\n          actions[inContext].updateAction();\n        }\n      });\n    }, false);\n\n    // Add event listener\n    document.addEventListener('newCacheAvailable', () => {\n        // When a new cache is available\n        Object.keys(actions).forEach(inContext => {\n            // Inform all used actions that a new cache is available\n            actions[inContext].newCacheAvailable(() => {\n                let action;\n\n                // Find out type of action\n                if (actions[inContext] instanceof PowerAction) {\n                    action = 'com.elgato.philips-hue.power';\n                }\n                else if (actions[inContext] instanceof ColorAction) {\n                    action = 'com.elgato.philips-hue.color';\n                }\n                else if (actions[inContext] instanceof CycleAction) {\n                    action = 'com.elgato.philips-hue.cycle';\n                }\n                else if (actions[inContext] instanceof BrightnessAction) {\n                    action = 'com.elgato.philips-hue.brightness';\n                    if(actions[inContext].updateAction) {\n                      actions[inContext].updateAction();\n                    }\n                }\n                else if (actions[inContext] instanceof TemperatureAction) {\n                  action = 'com.elgato.philips-hue.temperature';\n                  if(actions[inContext].updateAction) {\n                    actions[inContext].updateAction();\n                  }\n              }\n                else if (actions[inContext] instanceof BrightnessRelAction) {\n                    action = 'com.elgato.philips-hue.brightness-rel';\n                }\n                else if (actions[inContext] instanceof SceneAction) {\n                    action = 'com.elgato.philips-hue.scene';\n                }\n\n                // Inform PI of new cache\n                sendToPropertyInspector(action, inContext, cache.data);\n            });\n        });\n    }, false);\n\n    // Web socked received a message\n    websocket.onmessage = inEvent => {\n        // Parse parameter from string to object\n        let jsonObj = JSON.parse(inEvent.data);\n\n        // Extract payload information\n        let event = jsonObj['event'];\n        let action = jsonObj['action'];\n        let context = jsonObj['context'];\n        let jsonPayload = jsonObj['payload'];\n        let settings;\n\n        if(event === 'dialRotate') {\n          if(actions[context]?.onDialRotate) {\n            throttleDialRotate(() => {\n              actions[context].onDialRotate(jsonObj);\n            });\n            // debounceDialRotate(jsonObj);\n            // actions[context].onDialRotate(jsonObj);\n          }\n        } else if(!hasDialPress && event === 'dialUp') {\n            if(actions[ context ]?.onDialUp) {\n                actions[ context ].onDialUp(jsonObj);\n            }\n        } else if(!hasDialPress && event === 'dialDown') {\n            if(actions[ context ]?.onDialDown) {\n                actions[ context ].onDialDown(jsonObj);\n            }\n        } else if(hasDialPress && event === 'dialPress') {\n          if(actions[context]?.onDialPress) {\n            actions[context].onDialPress(jsonObj);\n          }\n        } else if(event === 'touchTap') {\n          if(actions[context]?.onTouchTap) {\n            actions[context].onTouchTap(jsonObj);\n          }\n        } else if (event === 'keyUp') {\n            settings = jsonPayload['settings'];\n            let coordinates = jsonPayload['coordinates'];\n            let userDesiredState = jsonPayload['userDesiredState'];\n            let state = jsonPayload['state'];\n\n            // Send onKeyUp event to actions\n            if (context in actions) {\n                actions[context].onKeyUp(context, settings, coordinates, userDesiredState, state);\n            }\n\n            // Refresh the cache\n            cache.refresh();\n        }\n        else if (event === 'willAppear') {\n            settings = jsonPayload['settings'];\n\n            // If this is the first visible action\n            if (Object.keys(actions).length === 0) {\n                // Start polling\n                cache.startPolling();\n            }\n\n            // Add current instance is not in actions array\n            if (!(context in actions)) {\n                // Add current instance to array\n                if (action === 'com.elgato.philips-hue.power') {\n                    actions[context] = new PowerAction(context, settings);\n                }\n                else if (action === 'com.elgato.philips-hue.color') {\n                    actions[context] = new ColorAction(context, settings);\n                }\n                else if (action === 'com.elgato.philips-hue.cycle') {\n                    actions[context] = new CycleAction(context, settings);\n                }\n                else if (action === 'com.elgato.philips-hue.brightness') {\n                    actions[context] = new BrightnessAction(context, settings, jsonObj);\n                }\n                else if (action === 'com.elgato.philips-hue.temperature') {\n                  actions[context] = new TemperatureAction(context, settings, jsonObj);\n              }\n                else if (action === 'com.elgato.philips-hue.brightness-rel') {\n                    actions[context] = new BrightnessRelAction(context, settings);\n                }\n                else if (action === 'com.elgato.philips-hue.scene') {\n                    actions[context] = new SceneAction(context, settings);\n                }\n            }\n        }\n        else if (event === 'willDisappear') {\n            // Remove current instance from array\n            if (context in actions) {\n                delete actions[context];\n            }\n\n            // If this is the last visible action\n            if (Object.keys(actions).length === 0) {\n                // Stop polling\n                cache.stopPolling();\n            }\n        }\n        else if (event === 'didReceiveGlobalSettings') {\n            // Set global settings\n            globalSettings = jsonPayload['settings'];\n\n            // If at least one action is active\n            if (Object.keys(actions).length > 0) {\n                // Refresh the cache\n                cache.refresh();\n            }\n        }\n        else if (event === 'didReceiveSettings') {\n            settings = jsonPayload['settings'];\n\n            // Set settings\n            if (context in actions) {\n                actions[context].setSettings(settings);\n            }\n\n            // Refresh the cache\n            cache.refresh();\n        }\n        else if (event === 'propertyInspectorDidAppear') {\n            // Send cache to PI\n            sendToPropertyInspector(action, context, cache.data);\n        }\n        else if (event === 'sendToPlugin') {\n            let piEvent = jsonPayload['piEvent'];\n\n            if (piEvent === 'valueChanged') {\n                // Only color, brightness and scene support live preview\n                if (action !== 'com.elgato.philips-hue.power' && action !== 'com.elgato.philips-hue.cycle') {\n                    // Send manual onKeyUp event to action\n                    if (context in actions) {\n                        actions[context].onKeyUp(context);\n                    }\n                }\n            } else if (piEvent === 'lightsChanged') {\n                // console.log(\"lightsChanged\", action, context, jsonPayload);\n                if (context in actions) {\n                  if(actions[context].updateDisplay) {\n                    actions[context].updateDisplay();\n                  };\n                }\n            }\n        }\n    };\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/js/philips/cache.js",
    "content": "/**\n@file      cache.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\n// Prototype for a data cache\nfunction Cache() {\n    // Init Cache\n    let instance = this;\n\n    // Refresh time of the cache  in seconds\n    let autoRefreshTime = 60;\n\n    // Private timer instance\n    let timer = null;\n\n    // Private bridge discovery\n    let discovery = null;\n\n    // Public variable containing the cached data\n    this.data = {};\n\n    // Private function to discover all bridges on the network\n    function buildDiscovery(inCallback) {\n        // Check if discovery ran already\n        if (discovery != null) {\n            inCallback(true);\n            return;\n        }\n\n        // Init discovery variable to indicate that it ran already\n        discovery = {};\n\n        // Run discovery\n        Bridge.discover((inSuccess, inBridges) => {\n            // If the discovery was not successful\n            if (!inSuccess) {\n                log(inBridges);\n                inCallback(false);\n                return;\n            }\n\n            // For all discovered bridges\n            inBridges.forEach(inBridge => {\n                // Add new bridge to discovery object\n                discovery[inBridge.getID()] = {\n                    ip: inBridge.getIP()\n                };\n            });\n\n            inCallback(true);\n        });\n    }\n\n    // Gather all required information by a Bridge via ID\n    function refreshBridge(pairedBridgeID, pairedBridge) {\n        // Older Bridges in Settings may have the ID stored inside the object\n        if (!pairedBridge.id) {\n            pairedBridge.id = pairedBridgeID;\n        }\n\n        // Older Bridges in Settings may have no IP stored\n        if (!pairedBridge.ip) {\n            // Trying to receive the IP trough auto-discovery\n            if (discovery[pairedBridge.id]) {\n                pairedBridge.ip = discovery[pairedBridge.id].ip;\n            }\n\n            // If no IP can be found for this Bridge we need to stop here\n            else {\n                log(`No IP found for paired Bridge ID: ${pairedBridge.id}`);\n                return;\n            }\n        }\n\n        // Create a bridge instance\n        let bridge = new Bridge(pairedBridge.ip, pairedBridge.id, pairedBridge.username);\n\n        // Create bridge cache\n        let bridgeCache = { 'lights': {}, 'groups': {} };\n        bridgeCache.id = bridge.getID();\n        bridgeCache.ip = bridge.getIP();\n        bridgeCache.username = bridge.getUsername();\n\n        // Load the bridge name\n        bridge.getName((inSuccess, inName) => {\n            // If getName was not successful\n            if (!inSuccess) {\n                log(inName);\n                return;\n            }\n\n            // Save the name\n            bridgeCache.name = inName;\n\n            // Add bridge to the cache\n            // instance.data[bridge.getID()] = bridgeCache;\n\n            // Request all lights of the bridge\n            bridge.getLights((inSuccess, inLights) => {\n                // If getLights was not successful\n                if (!inSuccess) {\n                    log(inLights);\n                    return;\n                }\n\n                // Create cache for each light\n                inLights.forEach(inLight => {\n                    // Add light to cache\n                    bridgeCache.lights['l-' + inLight.getID()] = {\n                        id: inLight.getID(),\n                        name: inLight.getName(),\n                        type: inLight.getType(),\n                        power: inLight.getPower(),\n                        brightness: inLight.getBrightness(),\n                        xy: inLight.getXY(),\n                        temperature: inLight.getTemperature(),\n                        originalValue: inLight.originalValue,\n                    };\n                });\n\n                // Request all groups of the bridge\n                bridge.getGroups((inSuccess, inGroups) => {\n                    // If getGroups was not successful\n                    if (!inSuccess) {\n                        log(inGroups);\n                        return;\n                    }\n\n                    // Create cache for each group\n                    inGroups.forEach(inGroup => {\n                        // Add group to cache\n                        bridgeCache.groups['g-' + inGroup.getID()] = {\n                            id: inGroup.getID(),\n                            name: inGroup.getName(),\n                            type: inGroup.getType(),\n                            power: inGroup.getPower(),\n                            brightness: inGroup.getBrightness(),\n                            xy: inGroup.getXY(),\n                            temperature: inGroup.getTemperature(),\n                            scenes: {},\n                        };\n\n                        // If this is the last group\n                        if (Object.keys(bridgeCache.groups).length === inGroups.length) {\n                            // Request all scenes of the bridge\n                            bridge.getScenes((inSuccess, inScenes) => {\n                                // If getScenes was not successful\n                                if (!inSuccess) {\n                                    log(inScenes);\n                                    return;\n                                }\n\n                                // Create cache for each scene\n                                inScenes.forEach(inScene => {\n                                    // Check if this is a group scene\n                                    if (inScene.getType() !== 'GroupScene') {\n                                        return;\n                                    }\n\n                                    // If scenes group is in cache\n                                    if ('g-' + inScene.getGroup() in bridgeCache.groups) {\n                                        // Add scene to cache\n                                        bridgeCache.groups['g-' + inScene.getGroup()].scenes[inScene.getID()] = {\n                                            id: inScene.getID(),\n                                            name: inScene.getName(),\n                                            type: inScene.getType(),\n                                            group: inScene.getGroup(),\n                                        };\n                                    }\n                                });\n                                // console.log(bridgeCache);\n                                instance.data[bridge.getID()] = bridgeCache;\n                                // Inform keys that updated cache is available\n                                let event = new CustomEvent('newCacheAvailable');\n                                document.dispatchEvent(event);\n                            });\n                        }\n                    });\n                });\n            });\n        });\n    }\n\n    // Public function to start polling\n    this.startPolling = () => {\n        // Log to the global log file\n        log('Start polling to create cache');\n\n        // Start a timer\n        instance.refresh();\n        timer = setInterval(instance.refresh, autoRefreshTime * 1000);\n    }\n\n    // Public function to stop polling\n    this.stopPolling = () => {\n        // Log to the global log file\n        log('Stop polling to create cache');\n\n        // Invalidate the timer\n        clearInterval(timer);\n        timer = null;\n    }\n\n    this.refresh = Utils.debounce(function () {\n        // Build discovery if necessary\n        buildDiscovery(() => {\n            if (globalSettings.bridges) {\n                Object.keys(globalSettings.bridges).forEach(bridgeID => refreshBridge(bridgeID, globalSettings.bridges[bridgeID]));\n            }\n        })\n    }, 200); // avoid multiple calls in a short time\n\n    // Private function to build a cache\n    this.refresh2 = () => {\n        // Build discovery if necessary\n        buildDiscovery(() => {\n            if (globalSettings.bridges) {\n                Object.keys(globalSettings.bridges).forEach(bridgeID => refreshBridge(bridgeID, globalSettings.bridges[bridgeID]));\n            }\n        })\n    };\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/js/philips/meethue.js",
    "content": "/**\n@file      meethue.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\nconst MDEBOUNCEDELAYMS = 80;\n\n// Prototype which represents a Philips Hue bridge\nfunction Bridge(ip = null, id = null, username = null) {\n    // Init Bridge\n    let instance = this;\n\n    // Public function to pair with a bridge\n    this.pair = (callback) => {\n        if (ip) {\n            let url = `http://${ip}/api`;\n            let xhr = new XMLHttpRequest();\n            xhr.responseType = 'json';\n            xhr.open('POST', url, true);\n            xhr.timeout = 2500;\n            \n            xhr.onload = () => {\n                if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {\n                    if (xhr.response !== undefined && xhr.response != null) {\n                        let result = xhr.response[0];\n\n                        if ('success' in result) {\n                            username = result['success']['username'];\n                            callback(true, result);\n                        }\n                        else {\n                            let message = result['error']['description'];\n                            callback(false, message);\n                        }\n                    }\n                    else {\n                        callback(false, 'Bridge response is undefined or null.');\n                    }\n                }\n                else {\n                    callback(false, 'Could not connect to the bridge.');\n                }\n            };\n\n            xhr.onerror = () => {\n                callback(false, 'Unable to connect to the bridge.');\n            };\n\n            xhr.ontimeout = () => {\n                callback(false, 'Connection to the bridge timed out.');\n            };\n\n            let obj = {};\n            obj.devicetype = 'stream_deck';\n            let data = JSON.stringify(obj);\n            xhr.send(data);\n        }\n        else {\n            callback(false, 'No IP address given.');\n        }\n    };\n\n    // Public function to retrieve the username\n    this.getUsername = () => {\n        return username;\n    };\n\n    // Public function to retrieve the IP address\n    this.getIP = () => {\n        return ip;\n    };\n\n    // Public function to retrieve the ID\n    this.getID = () => {\n        return id;\n    };\n\n    // Public function to retrieve the name\n    this.getName = callback => {\n        let url = `http://${ip}/api/${username}/config`;\n        let xhr = new XMLHttpRequest();\n        xhr.responseType = 'json';\n        xhr.open('GET', url, true);\n        xhr.timeout = 5000;\n\n        xhr.onload = () => {\n            if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {\n                let result = xhr.response;\n\n                if (result !== undefined && result != null) {\n                    if ('name' in result) {\n                        let name = result['name'];\n                        callback(true, name);\n                    }\n                    else {\n                        let message = result[0]['error']['description'];\n                        callback(false, message);\n                    }\n                }\n                else {\n                    callback(false, 'Bridge response is undefined or null.');\n                }\n            }\n            else {\n                callback(false, 'Could not connect to the bridge.');\n            }\n        };\n\n        xhr.onerror = () => {\n            callback(false, 'Unable to connect to the bridge.');\n        };\n\n        xhr.ontimeout = () => {\n            callback(false, 'Connection to the bridge timed out.');\n        };\n\n        xhr.send();\n    };\n\n    // Private function to retrieve objects\n    function getMeetHues(type, callback) {\n        let url;\n\n        if (type === 'light') {\n            url = `http://${ip}/api/${username}/lights`;\n        }\n        else if (type === 'group') {\n            url = `http://${ip}/api/${username}/groups`;\n        }\n        else if (type === 'scene') {\n            url = `http://${ip}/api/${username}/scenes`;\n        }\n        else {\n            callback(false, 'Type does not exist.');\n            return;\n        }\n\n        let xhr = new XMLHttpRequest();\n        xhr.responseType = 'json';\n        xhr.open('GET', url, true);\n        xhr.timeout = 5000;\n    \n        xhr.onload = () => {\n            if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {\n                let result = xhr.response;\n\n                if (result !== undefined && result != null) {\n                    if (!Array.isArray(result)) {\n                        let objects = [];\n\n                        Object.keys(result).forEach(key => {\n                            let value = result[key];\n\n                            if (type === 'light') {\n                              // console.log(\"Light\", value.name, value.capabilities?.control);\n                              let light = new Light(instance, key, value.name, value.type, value.state.on, value.state.bri, value.state.xy, value.state.ct);\n                              light.originalValue = value;\n                                objects.push(light);\n                            }\n                            else if (type === 'group') {\n                                objects.push(new Group(instance, key, value.name, value.type, value.state.all_on, value.action.bri, value.action.xy, value.action.ct));\n                            }\n                            else if (type === 'scene') {\n                                objects.push(new Scene(instance, key, value.name, value.type, value.group));\n                            }\n                        });\n\n                        callback(true, objects);\n                    }\n                    else {\n                        let message = result[0]['error']['description'];\n                        callback(false, message);\n                    }\n                }\n                else {\n                    callback(false, 'Bridge response is undefined or null.');\n                }\n            }\n            else {\n                callback(false, 'Unable to get objects of type ' + type + '.');\n            }\n        };\n\n        xhr.onerror = () => {\n            callback(false, 'Unable to connect to the bridge.');\n        };\n\n        xhr.ontimeout = () => {\n            callback(false, 'Connection to the bridge timed out.');\n        };\n\n        xhr.send();\n    }\n\n    // Public function to retrieve the lights\n    this.getLights = callback => {\n        getMeetHues('light', callback);\n    };\n\n    // Public function to retrieve the groups\n    this.getGroups = callback => {\n        getMeetHues('group', callback);\n    };\n\n    // Public function to retrieve the scenes\n    this.getScenes = callback => {\n        getMeetHues('scene', callback);\n    };\n}\n\n// Static function to discover bridges\nBridge.discover = callback => {\n    let url = 'https://discovery.meethue.com';\n    let xhr = new XMLHttpRequest();\n    xhr.responseType = 'json';\n    xhr.open('GET', url, true);\n    xhr.timeout = 10000;\n\n    xhr.onload = () => {\n        if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {\n            if (xhr.response !== undefined && xhr.response != null) {\n                let bridges = [];\n\n                xhr.response.forEach(bridge => {\n                    bridges.push(new Bridge(bridge.internalipaddress, bridge.id));\n                });\n\n                callback(true, bridges);\n            }\n            else {\n                callback(false, 'Meethue server response is undefined or null.');\n            }\n        }\n        else {\n            callback(false, 'Unable to discover bridges.');\n        }\n    };\n\n    xhr.onerror = () => {\n        callback(false, 'Unable to connect to the internet.');\n    };\n\n    xhr.ontimeout = () => {\n        callback(false, 'Connection to the internet timed out.');\n    };\n\n    xhr.send();\n};\n\n// Check if a Bridge is available under a certain IP address\n// If a username is set it will check that too\nBridge.check = (ip, username, callback) => {\n    let url = username ? `http://${ip}/api/${username}config` : `http://${ip}/api/config`;\n    let xhr = new XMLHttpRequest();\n    xhr.responseType = 'json';\n    xhr.open('GET', url, true);\n    xhr.timeout = 10000;\n\n    xhr.onload = () => {\n        if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200 &&\n            xhr.response !== undefined && xhr.response != null &&\n            xhr.response.hasOwnProperty('bridgeid') &&\n            (!username || xhr.response.hasOwnProperty('ipaddress'))\n        ) {\n            // at this point the bridge has been found and added to list\n            callback(true, {\n                ip: ip,\n                id: xhr.response.bridgeid.toLowerCase(),\n            });\n        }\n\n        callback(false);\n    };\n\n    xhr.onerror = xhr.ontimeout = () => {\n        callback(false);\n    };\n\n    xhr.send();\n};\n\n// Static function to convert hex to rgb\nBridge.hex2rgb = inHex => {\n    // Remove hash if it exists\n    if (inHex.charAt(0) === '#') {\n        inHex = inHex.substr(1);\n    }\n\n    // Split hex into RGB components\n    let rgbArray = inHex.match(/.{1,2}/g);\n\n    // Convert RGB component into decimals\n    let red = parseInt(rgbArray[0], 16);\n    let green = parseInt(rgbArray[1], 16);\n    let blue = parseInt(rgbArray[2], 16);\n\n    return {\n        r: red,\n        g: green,\n        b: blue,\n    };\n}\n\n// Static function to convert rgb to hex\nBridge.rgb2hex = inRGB => {\n    return '#' + ((1 << 24) + (inRGB.r << 16) + (inRGB.g << 8) + inRGB.b).toString(16).slice(1);\n}\n\n// Static function to convert rgb to hsv\nBridge.rgb2hsv = inRGB => {\n    // Calculate the brightness and saturation value\n    let max = Math.max(inRGB.r, inRGB.g, inRGB.b);\n    let min = Math.min(inRGB.r, inRGB.g, inRGB.b);\n    let d = max - min;\n    let s = (max === 0 ? 0 : d / max);\n    let v = max / 255;\n\n    // Calculate the hue value\n    let h;\n\n    switch (max) {\n        case min:\n            h = 0;\n            break;\n        case inRGB.r:\n            h = (inRGB.g - inRGB.b) + d * (inRGB.g < inRGB.b ? 6: 0);\n            h /= 6 * d;\n            break;\n        case inRGB.g:\n            h = (inRGB.b - inRGB.r) + d * 2;\n            h /= 6 * d;\n            break;\n        case inRGB.b:\n            h = (inRGB.r - inRGB.g) + d * 4;\n            h /= 6 * d;\n            break;\n    }\n\n    return {h, s, v};\n}\n\n// Static function to convert hsv to rgb\nBridge.hsv2rgb = inHSV => {\n    let r = null;\n    let g = null;\n    let b = null;\n\n    let i = Math.floor(inHSV.h * 6);\n    let f = inHSV.h * 6 - i;\n    let p = inHSV.v * (1 - inHSV.s);\n    let q = inHSV.v * (1 - f * inHSV.s);\n    let t = inHSV.v * (1 - (1 - f) * inHSV.s);\n\n    // Calculate red, green and blue\n    switch (i % 6) {\n        case 0:\n            r = inHSV.v;\n            g = t;\n            b = p;\n            break;\n        case 1:\n            r = q;\n            g = inHSV.v;\n            b = p;\n            break;\n        case 2:\n            r = p;\n            g = inHSV.v;\n            b = t;\n            break;\n        case 3:\n            r = p;\n            g = q;\n            b = inHSV.v;\n            break;\n        case 4:\n            r = t;\n            g = p;\n            b = inHSV.v;\n            break;\n        case 5:\n            r = inHSV.v;\n            g = p;\n            b = q;\n            break;\n    }\n\n    // Convert rgb values to int\n    let red = Math.round(r * 255);\n    let green = Math.round(g * 255);\n    let blue = Math.round(b * 255);\n\n    return {\n        r: red,\n        g: green,\n        b: blue,\n    };\n}\n\n// Static function to convert hex to hsv\nBridge.hex2hsv = inHex => {\n    // Convert hex to rgb\n    let rgb = Bridge.hex2rgb(inHex);\n\n    // Convert rgb to hsv\n    return Bridge.rgb2hsv(rgb);\n}\n\n// Static function to convert hsv to hex\nBridge.hsv2hex = inHSV => {\n    // Convert hsv to rgb\n    let rgb = Bridge.hsv2rgb(inHSV);\n\n    // Convert rgb to hex\n    return Bridge.rgb2hex(rgb);\n}\n\n// Static function to convert hex to xy\nBridge.hex2xy = inHex => {\n    // Convert hex to rgb\n    let rgb = Bridge.hex2rgb(inHex);\n\n    // Concert RGB components to floats\n    let red = rgb.r / 255;\n    let green = rgb.g / 255;\n    let blue = rgb.b / 255;\n\n    // Convert RGB to XY\n    let r = red > 0.04045 ? Math.pow(((red + 0.055) / 1.055), 2.4000000953674316) : red / 12.92;\n    let g = green > 0.04045 ? Math.pow(((green + 0.055) / 1.055), 2.4000000953674316) : green / 12.92;\n    let b = blue > 0.04045 ? Math.pow(((blue + 0.055) / 1.055), 2.4000000953674316) : blue / 12.92;\n    let x = r * 0.664511 + g * 0.154324 + b * 0.162028;\n    let y = r * 0.283881 + g * 0.668433 + b * 0.047685;\n    let z = r * 8.8E-5 + g * 0.07231 + b * 0.986039;\n\n    // Convert XYZ zo XY\n    let xy = [x / (x + y + z), y / (x + y + z)];\n\n    if (isNaN(xy[0])) {\n      xy[0] = 0.0;\n    }\n\n    if (isNaN(xy[1])) {\n      xy[1] = 0.0;\n    }\n\n    return xy;\n};\n\n// Prototype which represents a Philips Hue object\nfunction MeetHue(bridge = null, id = null, name = null, type = null) {\n    // Init MeetHue\n    let instance = this;\n\n    // Override in child prototype\n    let url = null;\n    this.originalValue = null;\n\n    // Public function to retrieve the type\n    this.getType = () => {\n        return type;\n    };\n\n    // Public function to retrieve the name\n    this.getName = () => {\n        return name;\n    };\n\n    // Public function to retrieve the ID\n    this.getID = () => {\n        return id;\n    };\n\n    // Public function to retrieve the URL\n    this.getURL = () => {\n        return url;\n    };\n\n    // Public function to set the URL\n    this.setURL = inURL => {\n        url = inURL;\n    }\n\n    // Public function to set light state\n    this.setState = (state, callback) => {\n        // Check if the URL was set\n        if (instance.getURL() == null) {\n            callback(false, 'URL is not set.');\n            return;\n        }\n\n        let xhr = new XMLHttpRequest();\n        xhr.responseType = 'json';\n        xhr.open('PUT', instance.getURL(), true);\n        xhr.timeout = 2500;\n\n        xhr.onload = () => {\n            if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {\n                if (xhr.response !== undefined && xhr.response != null) {\n                    let result = xhr.response[0];\n\n                    if ('success' in result) {\n                        callback(true, result);\n                    }\n                    else {\n                        let message = result['error']['description'];\n                        callback(false, message);\n                    }\n                }\n                else {\n                    callback(false, 'Bridge response is undefined or null.');\n                }\n            }\n            else {\n                callback(false, 'Could not set state.');\n            }\n        };\n\n        xhr.onerror = () => {\n            callback(false, 'Unable to connect to the bridge.');\n        };\n\n        xhr.ontimeout = () => {\n            callback(false, 'Connection to the bridge timed out.');\n        };\n\n        let data = JSON.stringify(state);\n        xhr.send(data);\n    };\n}\n\n// Prototype which represents a scene\nfunction Scene(bridge = null, id = null, name = null, type = null, group = null) {\n    // Init Scene\n    let instance = this;\n\n    // Inherit from MeetHue\n    MeetHue.call(this, bridge, id, name, type);\n\n    // Set the URL\n    this.setURL(`http://${bridge.getIP()}/api/${bridge.getUsername()}/groups/0/action`);\n\n    // Public function to retrieve the group\n    this.getGroup = () => {\n        return group;\n    };\n\n    // Public function to set the scene\n    this.on = callback => {\n        // Define state object\n        let state = {};\n        state.scene = id;\n\n        // Send new state\n        instance.setState(state, callback);\n    };\n}\n\n// Prototype which represents an illumination\nfunction Illumination(bridge = null, id = null, name = null, type = null, power = null, brightness = null, xy = null, temperature = null) {\n    // Init Illumination\n    let instance = this;\n\n    // Inherit from MeetHue\n    MeetHue.call(this, bridge, id, name, type);\n\n    // Public function to retrieve the power state\n    this.getPower = () => {\n        return power;\n    };\n\n    // Public function to retrieve the brightness\n    this.getBrightness = () => {\n        return brightness;\n    };\n\n    // Public function to retrieve xy\n    this.getXY = () => {\n        return xy;\n    };\n\n    // Public function to retrieve the temperature\n    this.getTemperature = () => {\n        return temperature;\n    };\n\n    // Public function to set the power status of the light\n    this.setPower = (power, callback) => {\n        // Define state object\n        let state = {};\n        state.on = power;\n\n        // Send new state\n        instance.setState(state, callback);\n    };\n\n    // Public function to set the brightness\n    this.setBrightness = Utils.debounce((brightness, callback) => {\n      // Define state object\n      let state = {};\n      state.bri = brightness;\n\n      // To modify the brightness, the light needs to be on\n      state.on = true;\n      // Send new state\n      instance.setState(state, callback);\n  }, MDEBOUNCEDELAYMS);\n\n    // Public function set the xy value\n    this.setXY = (xy, callback) => {\n        // Define state object\n        let state = {};\n        state.xy = xy;\n\n        // To modify the color, the light needs to be on\n        state.on = true;\n\n        // Send new state\n        instance.setState(state, callback);\n    };\n\n    // Public function set the temperature value\n    this.setTemperature = Utils.debounce((temperature, callback) => {\n        // Define state object\n        let state = {};\n        state.ct = temperature;\n\n        // To modify the temperature, the light needs to be on\n        state.on = true;\n\n        // Send new state\n        instance.setState(state, callback);\n    }, MDEBOUNCEDELAYMS);\n}\n\n// Prototype which represents a light\nfunction Light(bridge = null, id = null, name = null, type = null, power = null, brightness = null, xy = null, temperature = null) {\n    // Inherit from Illumination\n    Illumination.call(this, bridge, id, name, type, power, brightness, xy, temperature);\n\n    // Set the URL\n    this.setURL(`http://${bridge.getIP()}/api/${bridge.getUsername()}/lights/${id}/state`);\n}\n\n// Prototype which represents a group\nfunction Group(bridge = null, id = null, name = null, type = null, power = null, brightness = null, xy = null, temperature = null) {\n    // Inherit from Illumination\n    Illumination.call(this, bridge, id, name, type, power, brightness, xy, temperature);\n\n    // Set the URL\n    this.setURL(`http://${bridge.getIP()}/api/${bridge.getUsername()}/groups/${id}/action`);\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/js/powerAction.js",
    "content": "/**\n@file      powerAction.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\n// Prototype which represents a power action\nfunction PowerAction(inContext, inSettings) {\n    // Init PowerAction\n    let instance = this;\n\n    // Inherit from Action\n    Action.call(this, inContext, inSettings);\n\n    // Update the state\n    updateState();\n\n    this.updateAction = function() {\n      updateState();\n    };\n\n    // Public function called on key up event\n    this.onKeyUp = (inContext, inSettings, inCoordinates, inUserDesiredState, inState) => {\n        const settings = this.getVerifiedSettings(inContext);\n        if(false === settings) return;\n  \n        let bridgeCache = cache.data[settings.bridge];\n        // Create a bridge instance\n        let bridge = new Bridge(bridgeCache.ip, bridgeCache.id, bridgeCache.username);\n\n        // Create a light or group object\n        let objCache, obj;\n        if (settings.light.indexOf('l-') !== -1) {\n            objCache = bridgeCache.lights[settings.light];\n            obj = new Light(bridge, objCache.id);\n        }\n        else {\n            objCache = bridgeCache.groups[settings.light];\n            obj = new Group(bridge, objCache.id);\n        }\n\n        // Check for multi action\n        let targetState;\n        if (inUserDesiredState !== undefined) {\n            targetState = !inUserDesiredState;\n        }\n        else {\n            targetState = !objCache.power;\n        }\n\n        // Set light or group state\n        obj.setPower(targetState, (success, error) => {\n            if (success) {\n                setActionState(inContext, targetState ? 0 : 1);\n                objCache.power = targetState;\n                cache.refresh();\n            }\n            else {\n                log(error);\n                setActionState(inContext, inState);\n                showAlert(inContext);\n            }\n        });\n    };\n\n    // Before overwriting parent method, save a copy of it\n    let actionNewCacheAvailable = this.newCacheAvailable;\n\n    // Public function called when new cache is available\n    this.newCacheAvailable = inCallback => {\n        // Call actions newCacheAvailable method\n        actionNewCacheAvailable.call(instance, () => {\n            // Update the state\n            updateState();\n\n            // Call the callback function\n            inCallback();\n        });\n    };\n\n    function updateState() {\n        // Get the settings and the context\n        let settings = instance.getSettings();\n        let context = instance.getContext();\n\n        // Check if any bridge is configured\n        if (!('bridge' in settings)) {\n            return;\n        }\n\n        // Check if the configured bridge is in the cache\n        if (!(settings.bridge in cache.data)) {\n            return;\n        }\n\n        // Find the configured bridge\n        let bridgeCache = cache.data[settings.bridge];\n\n        // Check if a light was set for this action\n        if (!('light' in settings)) {\n            return;\n        }\n\n        // Check if the configured light or group is in the cache\n        if (!(settings.light in bridgeCache.lights || settings.light in bridgeCache.groups)) {\n            return;\n        }\n\n        // Find out if it is a light or a group\n        let objCache;\n        if (settings.light.indexOf('l-') !== -1) {\n            objCache = bridgeCache.lights[settings.light];\n        }\n        else {\n            objCache = bridgeCache.groups[settings.light];\n        }\n\n        // Set the target state\n        let targetState = objCache.power;\n\n        // Set the new action state\n        setActionState(context, targetState ? 0 : 1);\n    }\n\n    // Private function to set the state\n    function setActionState(inContext, inState) {\n        setState(inContext, inState);\n    }\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/js/propertyAction.js",
    "content": "/**\n@file      propertyAction.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\n// Prototype which represents a brightness action\nfunction PropertyAction(inContext, inSettings, jsn) {\n\n  let instance = this;\n  this.keyIsDown = false;\n  this.actionTriggered = false;\n  const setStateFunction = `set${Utils.capitalize(this.property)}`;\n\n  // Inherit from Action\n  Action.call(this, inContext, inSettings, jsn);\n\n  // Set the default values\n  setDefaults();\n\n  this.updateAction = function() {\n    const target = this.getCurrentLightOrGroup();\n    if(target === false) return;\n    this.updateDisplay(target.objCache, this.property);\n  };\n\n  if(this.isEncoder) {\n    let timer = setInterval(() => {\n      this.updateAction();\n    }, 5000);\n  }\n\n  this.getCurrentLightOrGroup = function() {\n    let settings = this.getVerifiedSettings(inContext);\n    if(settings === false) return false; // break if settings are not valid\n    let bridgeCache = cache.data[settings.bridge]; // we have a valid bridge (was checked in getVerifiedSettings)\n    let objCache = {};\n    let obj = {};\n    let bridge = new Bridge(bridgeCache.ip, bridgeCache.id, bridgeCache.username);\n    if(settings.light.indexOf('l') !== -1) {\n      objCache = bridgeCache.lights[settings.light];\n      if(objCache) {\n        obj = new Light(bridge, objCache.id);\n      }\n    }\n    else {\n      objCache = bridgeCache.groups[settings.light];\n      if(objCache) {\n        obj = new Group(bridge, objCache.id);\n      }\n    }\n    return {obj, objCache};\n  };\n\n  this.setValue = function(inValue, jsn) {\n    const target = this.getCurrentLightOrGroup();\n    if(target) {\n      if(target.objCache.power === false) return;\n      let value = inValue ? inValue : target.objCache[this.property];\n\n      if(jsn?.payload?.ticks) {\n        let settings = this.getSettings();\n        const scaleTicks = settings?.scaleTicks || 1;\n        const multiplier = scaleTicks * jsn.payload.ticks;\n        value = Utils.minmax(parseInt(value + multiplier * 2.55), 0,255);\n        // value = parseInt(value + jsn.payload.ticks * 2.55);\n      }\n\n      // just update the panel optimistically\n      // note: this didn't work well for me, so I'm not using it\n      // this.setFeedback(inContext, parseInt(value / 2.54), 1);\n\n      target.obj[setStateFunction](value, (inSuccess, inError) => {\n        if(inSuccess) {\n          target.objCache[this.property] = value;\n          this.updateDisplay(target.objCache, this.property);\n          this.updateAllActions();\n        } else {\n          log(inError);\n          showAlert(inContext);\n        }\n      });\n    }\n\n  };\n\n  this.onDialUp = function(jsn) {\n    // console.log('onDialUp', jsn);\n    if(this.getVerifiedSettings(inContext) === false) return;\n    this.keyIsDown = false;\n    if(!this.actionTriggered) {\n      if(this.isEncoder) {\n        return this.togglePower(inContext);\n      }\n      const target = this.getCurrentLightOrGroup();\n      // check if light is off, and if it is, turn it on\n      if(target.objCache.power === false) {\n        this.togglePower(inContext);\n        this.updateDisplay(target.objCache, 'power');\n      } else {\n        // otherwise, just change the property to the configured value\n        this.onKeyUp(inContext);\n      }\n    }\n  };\n\n  this.onDialDown = function(jsn) {\n    // console.log('onDialDown', jsn);\n    if(this.getVerifiedSettings(inContext) === false) return;\n    // temporarily set a flag to mark that the key is down\n    this.keyIsDown = true;\n    this.actionTriggered = false;\n    setTimeout(function() {\n      if(instance.keyIsDown) {\n        // console.log(\"***** long keypress detected:\", instance.keyIsDown,inContext);\n        const target = instance.togglePower(inContext);\n        instance.updateDisplay(target.objCache, 'power');\n        instance.actionTriggered = true;\n      }\n      instance.keyIsDown = false;\n    }, 500);\n  };\n\n  this.onDialPress = function(jsn) {\n    if(this.getVerifiedSettings(inContext) === false) return;\n    if(jsn?.payload?.pressed === true) {  // dial pressed == down\n      this.onDialDown(jsn);\n    } else { // dial released == up\n      this.onDialUp(jsn);\n    }\n  };\n\n  this.onDialRotate = function(jsn) {\n    this.setValue(null, jsn);\n  };\n\n  this.onTouchTap = function(jsn) {\n    this.togglePower(inContext);\n  };\n\n  // Public function called on key up event\n  this.onKeyUp = (inContext) => {\n    const settings = this.getVerifiedSettings(inContext);\n    if(settings === false) return;\n    // Convert value\n    // Hack to circumvent original code that converts values from 0-255\n    let value = this.property == 'temperature' ? Number(settings[this.property]) : Math.round(settings[this.property] * 2.54);\n    this.setValue(value);\n  };\n\n  // Before overwriting parent method, save a copy of it\n  let actionNewCacheAvailable = this.newCacheAvailable;\n\n  // Public function called when new cache is available\n  this.newCacheAvailable = inCallback => {\n    // Call actions newCacheAvailable method\n    actionNewCacheAvailable.call(instance, () => {\n      // Set defaults\n      setDefaults();\n      // Call the callback function\n      inCallback();\n    });\n  };\n\n  // Private function to set the defaults\n  function setDefaults() {\n    // Get the settings and the context\n    let settings = instance.getSettings();\n    let context = instance.getContext();\n\n    // If property is already set for this action\n    if(this.property in settings) {\n      return;\n    }\n\n    // Set the property to 100\n    settings[this.property] = 100;\n\n    // Save the settings\n    saveSettings(`com.elgato.philips-hue.${this.property}`, context, settings);\n  }\n\n  // update the action and its display\n  this.updateActionIfCacheAvailable(inContext);\n\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/js/sceneAction.js",
    "content": "/**\n@file      sceneAction.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\n// Prototype which represents a scene action\nfunction SceneAction(inContext, inSettings) {\n    // Init SceneAction\n    let instance = this;\n\n    // Inherit from Action\n    Action.call(this, inContext, inSettings);\n\n    // Set the default values\n    setDefaults();\n\n    // Public function called on key up event\n    this.onKeyUp = (inContext, inSettings, inCoordinates, inUserDesiredState, inState) => {\n       \n        const settings = this.getVerifiedSettings(inContext, 'scene');\n        if(false === settings) return;\n        let bridgeCache = cache.data[settings.bridge];\n\n        // Find the configured group\n        let groupCache = bridgeCache.groups[inSettings.light];\n\n        // Check if any scene is configured\n        if (!('scene' in inSettings)) {\n            log('No scene configured');\n            showAlert(inContext);\n            return;\n        }\n\n        // Check if the configured scene is in the group cache\n        if (!(settings.scene in groupCache.scenes)) {\n            log(`Scene ${settings.scene} not found in cache`);\n            showAlert(inContext);\n            return;\n        }\n\n        // Find the configured scene\n        let sceneCache = groupCache.scenes[inSettings.scene];\n\n        // Create a bridge instance\n        let bridge = new Bridge(bridgeCache.ip, bridgeCache.id, bridgeCache.username);\n\n        // Create a scene instance\n        let scene = new Scene(bridge, sceneCache.id);\n\n        // Set scene\n        scene.on((inSuccess, inError) => {\n            // Check if setting the scene was successful\n            if (!(inSuccess)) {\n                log(inError);\n                showAlert(inContext);\n            }\n        });\n    };\n\n    // Before overwriting parent method, save a copy of it\n    let actionNewCacheAvailable = this.newCacheAvailable;\n\n    // Public function called when new cache is available\n    this.newCacheAvailable = (inCallback) => {\n        // Call actions newCacheAvailable method\n        actionNewCacheAvailable.call(instance, () => {\n            // Set defaults\n            setDefaults();\n\n            // Call the callback function\n            inCallback();\n        });\n    };\n\n    // Private function to set the defaults\n    function setDefaults() {\n        // Get the settings and the context\n        let settings = instance.getSettings();\n        let context = instance.getContext();\n\n        // Check if any bridge is configured\n        if (!('bridge' in settings)) {\n            return;\n        }\n\n        // Check if the configured bridge is in the cache\n        if (!(settings.bridge in cache.data)) {\n            return;\n        }\n\n        // Find the configured bridge\n        let bridgeCache = cache.data[settings.bridge];\n\n        // Check if a light was set for this action\n        if (!('light' in settings)) {\n            return;\n        }\n\n        // Check if the light was set to a group\n        if (!(settings.light.indexOf('g-') !== -1)) {\n            return;\n        }\n\n        // Check if the configured group is in the cache\n        if (!(settings.light in bridgeCache.groups)) {\n            return;\n        }\n\n        // Find the configured group\n        let groupCache = bridgeCache.groups[settings.light];\n\n        // Check if a scene was configured for this action\n        if ('scene' in settings) {\n            // Check if the scene is part of the set group\n            if (settings.scene in groupCache.scenes) {\n                return;\n            }\n        }\n\n        // Check if the group has at least one scene\n        if (!(Object.keys(groupCache.scenes).length > 0)) {\n            return;\n        }\n\n        // Sort the scenes alphabetically\n        let sceneIDsSorted = Object.keys(groupCache.scenes).sort((a, b) => {\n            return groupCache.scenes[a].name.localeCompare(groupCache.scenes[b].name);\n        });\n\n        // Set the action automatically to the first one\n        settings.scene = sceneIDsSorted[0];\n\n        // Save the settings\n        saveSettings('com.elgato.philips-hue.scene', context, settings);\n    }\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/js/temperatureAction.js",
    "content": "/**\n@file      temperatureAction.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\n/**\n * Color temperature range of Philips lights is: 2200K to 6500K ==  455 to 154 Mired.   //154 is the coolest, 500 is the warmest\n */\nconst percentOfRange = (value, min = 0, max = 100) => {\n  return parseInt((max - min) * (value / 100) + min + 1);\n};\n\nfunction TemperatureAction(inContext, inSettings, jsn) {\n  this.property = 'temperature';\n  const setStateFunction = `set${Utils.capitalize(this.property)}`;\n  // Inherit from PropertyAction\n  PropertyAction.call(this, inContext, inSettings, jsn);\n\n  // setValue is sent from the 'keyUp' event and\n  // contains the value of the slider (0-100)\n  this.setValue = (inValue, jsn) => {\n    const target = this.getCurrentLightOrGroup();\n    if(target === false) return;\n    if(target.objCache.power === false) return;\n    const ct = target.objCache?.originalValue?.capabilities?.control?.ct;\n    if(!ct) return;\n    let value = inValue ? percentOfRange(inValue, ct.min, ct.max) : target.objCache[this.property];\n    if(jsn?.payload?.ticks) {\n      const settings = this.getSettings();\n      const scaleTicks = settings?.scaleTicks || 1;\n      const multiplier = scaleTicks * jsn.payload.ticks;\n      let addThis = (ct.max - ct.min) * (multiplier / 100);\n      addThis = addThis > 0 ? Math.floor(addThis) : Math.ceil(addThis);\n      value = Utils.minmax(parseInt(value + addThis), ct.min, ct.max);\n    }\n\n    target.obj[setStateFunction](value, (inSuccess, inError) => {\n      if(inSuccess) {\n        target.objCache[this.property] = value;\n        this.updateDisplay(target.objCache, this.property, jsn);\n        this.updateAllActions();\n      } else {\n        log(inError);\n        showAlert(inContext);\n      }\n    });\n  };\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/js/timers.js",
    "content": "/* global ESDTimerWorker */\n/*eslint no-unused-vars: \"off\"*/\n/*eslint-env es6*/\n\nlet ESDTimerWorker = new Worker(URL.createObjectURL(\n\tnew Blob([timerFn.toString().replace(/^[^{]*{\\s*/, '').replace(/\\s*}[^}]*$/, '')], {type: 'text/javascript'})\n));\nESDTimerWorker.timerId = 1;\nESDTimerWorker.timers = {};\nconst ESDDefaultTimeouts = {\n\ttimeout: 0,\n\tinterval: 10\n};\n\nObject.freeze(ESDDefaultTimeouts);\n\nfunction _setTimer(callback, delay, type, params) {\n\tconst id = ESDTimerWorker.timerId++;\n\tESDTimerWorker.timers[id] = {callback, params};\n\tESDTimerWorker.onmessage = (e) => {\n\t\tif (ESDTimerWorker.timers[e.data.id]) {\n\t\t\tif (e.data.type === 'clearTimer') {\n\t\t\t\tdelete ESDTimerWorker.timers[e.data.id];\n\t\t\t} else {\n\t\t\t\tconst cb = ESDTimerWorker.timers[e.data.id].callback;\n\t\t\t\tif (cb && typeof cb === 'function') cb(...ESDTimerWorker.timers[e.data.id].params);\n\t\t\t}\n\t\t}\n\t};\n\tESDTimerWorker.postMessage({type, id, delay});\n\treturn id;\n}\n\nfunction _setTimeoutESD(...args) {\n\tlet [callback, delay = 0, ...params] = [...args];\n\treturn _setTimer(callback, delay, 'setTimeout', params);\n}\n\nfunction _setIntervalESD(...args) {\n\tlet [callback, delay = 0, ...params] = [...args];\n\treturn _setTimer(callback, delay, 'setInterval', params);\n}\n\nfunction _clearTimeoutESD(id) {\n\tESDTimerWorker.postMessage({type: 'clearTimeout', id}); //     ESDTimerWorker.postMessage({type: 'clearInterval', id}); = same thing\n\tdelete ESDTimerWorker.timers[id];\n}\n\nwindow.setTimeout = _setTimeoutESD;\nwindow.setInterval = _setIntervalESD;\nwindow.clearTimeout = _clearTimeoutESD; //timeout and interval share the same timer-pool\nwindow.clearInterval = _clearTimeoutESD;\n\n/** This is our worker-code\n *  It is executed in it's own (global) scope\n *  which is wrapped above @ `let ESDTimerWorker`\n */\n\nfunction timerFn() {\n\t/*eslint indent: [\"error\", 4, { \"SwitchCase\": 1 }]*/\n\n\tlet timers = {};\n\tlet debug = false;\n\tlet supportedCommands = ['setTimeout', 'setInterval', 'clearTimeout', 'clearInterval'];\n\n\tfunction log(e) {\n\t\tconsole.log('Worker-Info::Timers', timers);\n\t}\n\n\tfunction clearTimerAndRemove(id) {\n\t\tif (timers[id]) {\n\t\t\tif (debug) console.log('clearTimerAndRemove', id, timers[id], timers);\n\t\t\tclearTimeout(timers[id]);\n\t\t\tdelete timers[id];\n\t\t\tpostMessage({type: 'clearTimer', id: id});\n\t\t\tif (debug) log();\n\t\t}\n\t}\n\n\tonmessage = function (e) {\n\t\t// first see, if we have a timer with this id and remove it\n\t\t// this automatically fulfils clearTimeout and clearInterval\n\t\tsupportedCommands.includes(e.data.type) && timers[e.data.id] && clearTimerAndRemove(e.data.id);\n\t\tif (e.data.type === 'setTimeout') {\n\t\t\ttimers[e.data.id] = setTimeout(() => {\n\t\t\t\tpostMessage({id: e.data.id});\n\t\t\t\tclearTimerAndRemove(e.data.id); //cleaning up\n\t\t\t}, Math.max(e.data.delay || 0));\n\t\t} else if (e.data.type === 'setInterval') {\n\t\t\ttimers[e.data.id] = setInterval(() => {\n\t\t\t\tpostMessage({id: e.data.id});\n\t\t\t}, Math.max(e.data.delay || ESDDefaultTimeouts.interval));\n\t\t}\n\t};\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/js/utils.js",
    "content": "/**\n@file      utils.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\n// Register the plugin or PI\nfunction registerPluginOrPI(inEvent, inUUID) {\n    if (websocket) {\n        websocket.send(JSON.stringify({\n            event: inEvent,\n            uuid: inUUID,\n        }));\n\t}\n}\n\n// Save settings\nfunction saveSettings(inAction, inUUID, inSettings) {\n    if (websocket) {\n        websocket.send(JSON.stringify({\n             action: inAction,\n             event: 'setSettings',\n             context: inUUID,\n             payload: inSettings,\n         }));\n    }\n}\n\n// Save global settings\nfunction saveGlobalSettings(inUUID) {\n    if (websocket) {\n        websocket.send(JSON.stringify({\n             event: 'setGlobalSettings',\n             context: inUUID,\n             payload: globalSettings,\n         }));\n    }\n}\n\n// Request global settings for the plugin\nfunction requestGlobalSettings(inUUID) {\n    if (websocket) {\n        websocket.send(JSON.stringify({\n            event: 'getGlobalSettings',\n            context: inUUID,\n        }));\n    }\n}\n\n// Log to the global log file\nfunction logToFile(inMessage) {\n    // Log to the developer console\n    let timeString = new Date().toLocaleString();\n    // Log to the Stream Deck log file\n    if (websocket) {\n        websocket.send(JSON.stringify({\n            event: 'logMessage',\n            payload: {\n                message: inMessage,\n            },\n        }));\n    }\n}\n\nconst log = console.log.bind(\n  console,\n  '%c [HUE]',\n  'color: #66c',\n);\n\nconst wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));\n\n// const debug = true;\n// Object.defineProperty(this, \"log\", {\n//   get: function () {\n//     return debug ? console.log.bind(window.console, test(), '[DEBUG]') : function(){};\n//    }\n// });\n\n\nconst debounce = (callback, time = 800) => {\n  let timer;\n  return (...args) => {\n      clearTimeout(timer);\n      timer = setTimeout(() => callback(...args), time);\n  };\n};\n\n// Show alert icon on the key\nfunction showAlert(inUUID) {\n    if (websocket) {\n        websocket.send(JSON.stringify({\n            event: 'showAlert',\n            context: inUUID,\n        }));\n    }\n}\n\n// Set the state of a key\nfunction setState(inContext, inState) {\n    if (websocket) {\n        websocket.send(JSON.stringify({\n            event: 'setState',\n            context: inContext,\n            payload: {\n                state: inState,\n            },\n        }));\n    }\n}\n\n// Set data to PI\nfunction sendToPropertyInspector(inAction, inContext, inData) {\n    if (websocket) {\n        websocket.send(JSON.stringify({\n            action: inAction,\n            event: 'sendToPropertyInspector',\n            context: inContext,\n            payload: inData,\n        }));\n    }\n}\n\n// Set data to plugin\nfunction sendToPlugin(inAction, inContext, inData) {\n    if (websocket) {\n        websocket.send(JSON.stringify({\n            action: inAction,\n            event: 'sendToPlugin',\n            context: inContext,\n            payload: inData,\n        }));\n    }\n}\n\n// Send feedback to the Stream Deck+ panel (SD+)\nfunction setFeedback(inContext, inPayload) {\n  if (websocket) {\n      websocket.send(JSON.stringify({\n           event: 'setFeedback',\n           context: inContext,\n           payload: inPayload,\n       }));\n  }\n}\n\n// Load the localizations\nfunction getLocalization(inLanguage, inCallback) {\n    let url = `../${inLanguage}.json`;\n    let xhr = new XMLHttpRequest();\n    xhr.open('GET', url, true);\n\n    xhr.onload = () => {\n        if (xhr.readyState === XMLHttpRequest.DONE) {\n            try {\n                let data = JSON.parse(xhr.responseText);\n                let localization = data['Localization'];\n                inCallback(true, localization);\n            }\n            catch(e) {\n                inCallback(false, 'Localizations is not a valid json.');\n            }\n        }\n        else {\n            inCallback(false, 'Could not load the localizations.');\n        }\n    };\n\n    xhr.onerror = () => {\n        inCallback(false, 'An error occurred while loading the localizations.');\n    };\n\n    xhr.ontimeout = () => {\n        inCallback(false, 'Localization timed out.');\n    };\n\n    xhr.send();\n}\n\nconst Utils = {};\nUtils.debounce = function(func, wait = 100) {\n  let timeout;\n  return function(...args) {\n      clearTimeout(timeout);\n      timeout = setTimeout(() => {\n          func.apply(this, args);\n      }, wait);\n  };\n};\n\n\nUtils.throttle = function(fn, threshold = 250, context) {\n  let last, timer;\n  return function() {\n      var ctx = context || this;\n      var now = new Date().getTime(),\n          args = arguments;\n      if(last && now < last + threshold) {\n          clearTimeout(timer);\n          timer = setTimeout(function() {\n              last = now;\n              fn.apply(ctx, args);\n          }, threshold);\n      } else {\n          last = now;\n          fn.apply(ctx, args);\n      }\n  };\n};\n\nUtils.capitalize = function(str) {\n  return str.charAt(0).toUpperCase() + str.slice(1);\n};\n\nUtils.minmax = function(v = 0, min = 0, max = 100) {\n  return Math.min(max, Math.max(min, v));\n};\n\nUtils.percent = (value, min, max) => {\n  return ((value - min) / (max - min)) * 100;\n};\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/setup/css/main.css",
    "content": "/**\n@file      main.css\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\nhtml, body {\n  margin: 0;\n  color: #e6e6e6;\n  background-color: #2d2d2d;\n  font-family: 'Helvetica Light', 'Helvetica', Arial, sans-serif;\n  height: 100%;\n  -webkit-user-select: none;\n  user-select: none;\n  overflow: hidden;\n}\n\nh1, h2 {\n  text-align: center;\n}\n\nh1 {\n  margin-top: 10px;\n  margin-bottom: 10px;\n}\n\nh2 {\n  color: #969696;\n  font-weight: 100;\n  margin-bottom: 5px;\n}\n\n.header {\n  padding: 25px 25px 5px;\n}\n\n#content {\n  padding: 0 25px 15px;\n}\n\n.main {\n  min-width: 400px;\n}\n\n@media (min-width: 600px) {\n  .main {\n    max-width: 500px;\n    height: 100%;\n    width: 100%;\n    display: table;\n    margin-left: auto;\n    margin-right: auto;\n  }\n\n  .center {\n    display: table-cell;\n    height: 100%;\n    vertical-align: middle;\n  }\n\n  .border {\n    border: 1px solid #3c3c3c;\n    border-radius: 5px;\n    margin: 10px;\n    min-height: 625px;\n  }\n}\n\np, li {\n  line-height: 1.5;\n  text-align: center;\n}\n\n.status-bar {\n  display: table;\n  width: 100%;\n  border-spacing: 3px;\n  border-collapse: separate;\n}\n\n.status-row {\n  display: table-row;\n}\n\n.status-cell {\n  display: table-cell;\n  background-color: #3d3d3d;\n  height: 10px;\n}\n\n.status-cell.active {\n  background-color: #007dff;\n}\n\n.image {\n  width: 250px;\n  height: auto;\n  display: block;\n  margin-left: auto;\n  margin-right: auto;\n  padding-top: 5px;\n  padding-bottom: 20px;\n}\n\nlabel {\n  width: 100%;\n  font-weight: bold;\n  text-align: center;\n  display: inline-block;\n  box-sizing: border-box;\n}\n\ninput {\n  width: 65%;\n  color: #fff;\n  border: 1px solid #8d8d8d;\n  background-color: #3d3d3d;\n  border-radius: 4px;\n  padding: 8px;\n  margin: 8px auto 16px;\n  text-align: center;\n  font-size: 1rem;\n  display: block;\n  box-sizing: border-box;\n}\n\ninput:focus, input:active {\n  border-color: #007dff;\n}\n\n.button, .button-main, .button-transparent {\n  padding: 8px 50px;\n  display: table;\n  margin: 8px auto;\n  cursor: pointer;\n  border-radius: 4px;\n}\n\n.button.block, .button-main.block {\n  width: 65%;\n  padding: 8px 16px;\n  display: block;\n  box-sizing: border-box;\n  text-align: center;\n}\n\n.button {\n  background-color: #3d3d3d;\n}\n\n.button-main {\n  background-color: #535353;\n}\n\n.button:hover, .button-main:hover {\n  background-color: #007dff;\n}\n\n.button-transparent:hover {\n  color: #969696;\n}\n\n.hide {\n  display: none;\n}\n\n.error-container > div {\n  color: #fff;\n  background-color: rgba(255, 0, 0, .2);\n  border: 1px solid rgba(255, 0, 0, .5);\n  border-radius: 4px;\n  text-align: center;\n  width: 80%;\n  box-sizing: border-box;\n  margin: 0 auto 24px;\n  padding: 8px;\n}\n\n#loader {\n  width: 40px;\n  height: 40px;\n  background-color: #666;\n  margin: 20px auto;\n  -webkit-animation: sk-rotateplane 1.2s infinite ease-in-out;\n  animation: sk-rotateplane 1.2s infinite ease-in-out;\n}\n\n@-webkit-keyframes sk-rotateplane {\n  0% {\n    -webkit-transform: perspective(120px)\n  }\n  50% {\n    -webkit-transform: perspective(120px) rotateY(180deg)\n  }\n  100% {\n    -webkit-transform: perspective(120px) rotateY(180deg) rotateX(180deg)\n  }\n}\n\n@keyframes sk-rotateplane {\n  0% {\n    transform: perspective(120px) rotateX(0deg) rotateY(0deg);\n    -webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg)\n  }\n  50% {\n    transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);\n    -webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg)\n  }\n  100% {\n    transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);\n    -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);\n  }\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/setup/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <title>com.elgato.philips-hue.setup</title>\n    <meta name='viewport' content='viewport-fit=cover, width=device-width, initial-scale=1'>\n    <meta charset=\"UTF-8\">\n    <!-- Import style sheets -->\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"css/main.css\">\n    <!-- Import scripts -->\n    <script src=\"../plugin/js/philips/meethue.js\"></script>\n    <script src=\"../plugin/js/utils.js\"></script>\n    <script src=\"js/introView.js\"></script>\n    <script src=\"js/discoveryView.js\"></script>\n    <script src=\"js/manualView.js\"></script>\n    <script src=\"js/pairingView.js\"></script>\n    <script src=\"js/saveView.js\"></script>\n    <script src=\"js/main.js\"></script>\n  </head>\n  <body oncontextmenu=\"return false;\">\n    <!-- Center view horizontally -->\n    <div class=\"main\">\n      <!-- Center view vertically -->\n      <div class=\"center\">\n        <!-- Add a border around the view on big screens -->\n        <div class=\"border\">\n          <!-- Status bar indicating the current view -->\n          <div class=\"status-bar\">\n            <div class=\"status-row\">\n              <div id=\"status-intro\" class=\"status-cell\"></div>\n              <div id=\"status-discovery\" class=\"status-cell\"></div>\n              <div id=\"status-pairing\" class=\"status-cell\"></div>\n              <div id=\"status-save\" class=\"status-cell\"></div>\n            </div>\n          </div>\n          <!-- General header element -->\n          <div class=\"header\">\n            <h2>Philips Hue</h2>\n            <h1 id=\"title\"></h1>\n          </div>\n          <!-- Main view element -->\n          <div id=\"content\"></div>\n        </div>\n      </div>\n    </div>\n  </body>\n</html>\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/setup/js/discoveryView.js",
    "content": "/**\n@file      discoveryView.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\n// Load the discovery view\nfunction loadDiscoveryView() {\n    // Delay the result for 1.5 seconds\n    let resultDelay = 1500;\n\n    // Set the status bar\n    setStatusBar('discovery');\n\n    // Fill the title\n    document.getElementById('title').innerHTML = localization['Discovery']['Title'];\n\n    // Fill the content area\n    document.getElementById('content').innerHTML = `\n        <p>&nbsp;</p>\n        <img class=\"image\" src=\"images/bridge.png\" alt=\"${localization['Discovery']['Title']}\">\n        <div id=\"loader\"></div>\n    `;\n\n    // Start the discovery\n    autoDiscovery();\n\n    // Discover all bridges\n    function autoDiscovery() {\n        Bridge.discover((status, data) => {\n            if (status) {\n                // Bridge discovery request was successful\n                bridges = data;\n\n                // Delay displaying the result\n                setTimeout(() => {\n                    if (bridges.length === 0) {\n                        // No bridges were found\n\n                        // Fill the title\n                        document.getElementById('title').innerHTML = localization['Discovery']['TitleNone'];\n\n                        // Fill the content area\n                        document.getElementById('content').innerHTML = `\n                            <p>${localization['Discovery']['DescriptionNone']}</p>\n                            <img class=\"image\" src=\"images/bridge_not_found.png\" alt=\"${localization['Discovery']['TitleNone']}\">\n                            <div class=\"button\" id=\"retry\">${localization['Discovery']['Retry']}</div>\n                            <div class=\"button-transparent\" id=\"close\">${localization['Discovery']['Close']}</div>\n                        `;\n\n                        // Add event listener\n                        document.getElementById('retry').addEventListener('click', retry);\n                        document.addEventListener('enterPressed', retry);\n\n                        document.getElementById('close').addEventListener('click', close);\n                        document.addEventListener('escPressed', close);\n                    }\n                    else {\n                        // At least one bridge was found\n                        let content;\n\n                        if (bridges.length === 1) {\n                            // Exactly one bridge was found\n\n                            // Fill the title\n                            document.getElementById('title').innerHTML = localization['Discovery']['TitleOne'];\n\n                            // Fill the content area\n                            content = `\n                                <p>${localization['Discovery']['DescriptionFound']}</p>\n                                <img class=\"image\" src=\"images/bridge.png\" alt=\"${localization['Discovery']['TitleOne']}\">\n                            `;\n                        }\n                        else {\n                            // At least 2 bridges were found\n\n                            // Fill the title\n                            document.getElementById('title').innerHTML = localization['Discovery']['TitleMultiple'].replace('{{ number }}', bridges.length);\n\n                            // Fill the content area\n                            content = `\n                                <p>${localization['Discovery']['DescriptionFound']}</p>\n                                <img class=\"image\" src=\"images/bridge_multiple.png\" alt=\"${localization['Discovery']['TitleMultiple'].replace('{{ number }}', bridges.length)}\">\n                            `;\n                        }\n\n                        document.getElementById('content').innerHTML = content + `\n                            <div class=\"button\" id=\"pair\">${localization['Discovery']['Pair']}</div>\n                            <div class=\"button-transparent\" id=\"retry\">${localization['Discovery']['Retry']}</div>\n                        `;\n\n                        // Add event listener\n                        document.getElementById('pair').addEventListener('click', pair);\n                        document.addEventListener('enterPressed', pair);\n\n                        document.getElementById('retry').addEventListener('click', retry);\n                        document.addEventListener('escPressed', retry);\n                    }\n                }, resultDelay);\n            }\n            else {\n                // An error occurred while contacting the meethue discovery service\n                document.getElementById('content').innerHTML = `<p>${data}</p>`;\n            }\n        });\n    }\n\n    // Open pairing view\n    function pair() {\n        unloadDiscoveryView();\n        loadPairingView();\n    }\n\n    // Retry discovery by reloading the view\n    function retry() {\n        unloadDiscoveryView();\n        loadDiscoveryView();\n    }\n\n    // Close the window\n    function close() {\n        window.close();\n    }\n\n    // Unload view\n    function unloadDiscoveryView() {\n        // Remove event listener\n        document.removeEventListener('enterPressed', retry);\n        document.removeEventListener('enterPressed', pair);\n        document.removeEventListener('escPressed', close);\n        document.removeEventListener('escPressed', retry);\n    }\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/setup/js/introView.js",
    "content": "/**\n@file      introView.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\n// Load the intro view\nfunction loadIntroView() {\n    // Set the status bar\n    setStatusBar('intro');\n\n    // Fill the title\n    document.getElementById('title').innerHTML = localization['Intro']['Title'];\n\n    // Fill the content area\n    document.getElementById('content').innerHTML = `\n        <p>${localization['Intro']['Description']}</p>\n        <img class=\"image\" src=\"images/bridge.png\" alt=\"${localization['Intro']['Title']}\">\n        <div class=\"button-main block\" id=\"start\">${localization['Intro']['Start']}</div>\n        <div class=\"button block\" id=\"manual\">${localization['Intro']['Manual']}</div>\n        <div class=\"button-transparent\" id=\"close\">${localization['Intro']['Close']}</div>\n    `;\n\n    // Add event listener\n    document.getElementById('start').addEventListener('click', startPairing);\n    document.addEventListener('enterPressed', startPairing);\n\n    document.getElementById('manual').addEventListener('click', startManual);\n\n    document.getElementById('close').addEventListener('click', close);\n    document.addEventListener('escPressed', close);\n\n    // Load the pairing view\n    function startPairing() {\n        unloadIntroView();\n        loadDiscoveryView();\n    }\n\n    // Load the manual view\n    function startManual() {\n        unloadIntroView();\n        loadManualView();\n    }\n\n    // Close the window\n    function close() {\n        window.close();\n    }\n\n    // Unload view\n    function unloadIntroView() {\n        // Remove event listener\n        document.removeEventListener('enterPressed', startPairing);\n        document.removeEventListener('escPressed', close);\n    }\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/setup/js/main.js",
    "content": "/**\n@file      main.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\n// Global variable containing the localizations\nvar localization = null;\n\n// Global variable containing the discovered bridges\nvar bridges = [];\n\n// Global variable containing the paired bridge\nvar bridge = null;\n\n// Global function to set the status bar to the correct view\nfunction setStatusBar(view) {\n    // Remove active status from all status cells\n    let statusCells = document.getElementsByClassName('status-cell');\n    Array.from(statusCells).forEach((cell) => {\n        cell.classList.remove('active');\n    });\n\n    // Set it only to the current one\n    document.getElementById('status-' + view).classList.add('active');\n}\n\n// Main function run after the page is fully loaded\nwindow.onload = () => {\n    // Bind enter and ESC keys\n    document.addEventListener('keydown', (e) => {\n        if (e.key === 'Enter') {\n            let event = new CustomEvent('enterPressed');\n            document.dispatchEvent(event);\n        }\n        else if (e.key === 'Esc' || e.key === 'Escape') {\n            let event = new CustomEvent('escPressed');\n            document.dispatchEvent(event);\n        }\n    });\n\n    // Get the url parameter\n    let url = new URL(window.location.href);\n    let language = url.searchParams.get('language');\n\n    // Load the localizations\n    getLocalization(language, (inStatus, inLocalization) => {\n        if (inStatus) {\n            // Save the localizations globally\n            localization = inLocalization['Setup'];\n\n            // Show the intro view\n            loadIntroView();\n        }\n        else {\n            document.getElementById('content').innerHTML = `<p>${inLocalization}</p>`;\n        }\n    });\n};\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/setup/js/manualView.js",
    "content": "/**\n@file      manualView.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\n// Load the manual view\nfunction loadManualView() {\n    // Set the status bar\n    setStatusBar('discovery');\n\n    // Fill the title\n    document.getElementById('title').innerHTML = localization['Manual']['Title'];\n\n    // Fill the content area\n    document.getElementById('content').innerHTML = `\n        <p>${localization['Manual']['Description']}</p>\n        <br />\n        <div id=\"ip-validation\" class=\"error-container\"></div>\n        <label>${localization['Manual']['IPAddress']}</label>\n        <input type=\"text\" id=\"ip\" />\n        <div class=\"button block\" id=\"check\">${localization['Manual']['Check']}</div>\n        <div class=\"button-transparent\" id=\"close\">${localization['Manual']['Close']}</div>\n    `;\n\n    // Set cursor to input field\n    document.getElementById('ip').focus();\n\n    // Add event listener\n    document.getElementById('check').addEventListener('click', check);\n    document.addEventListener('enterPressed', check);\n\n    document.getElementById('close').addEventListener('click', close);\n    document.addEventListener('escPressed', close);\n\n    // Print error message\n    function printError(error) {\n        document.getElementById('ip-validation').innerHTML = `<div class=\"error\">${error}</div>`;\n    }\n\n    // Check ip address\n    function check() {\n        let ip = document.getElementById('ip').value.trim();\n\n        // check if input is empty\n        if (!ip) {\n            printError(localization['Manual']['Error']['Empty']);\n            return;\n        }\n\n        // check if ip is invalid\n        let ipV4Regex = '^(?:25[0-5]|2[0-4]\\\\d|1\\\\d\\\\d|[1-9]\\\\d|\\\\d)(?:\\\\.(?:25[0-5]|2[0-4]\\\\d|1\\\\d\\\\d|[1-9]\\\\d|\\\\d)){3}$';\n        if (!(new RegExp(ipV4Regex)).test(ip)) {\n            printError(localization['Manual']['Error']['Invalid']);\n            return;\n        }\n\n        Bridge.check(ip, null, (success, data) => {\n            if (success) {\n                bridges = [\n                    new Bridge(data.ip, data.id),\n                ];\n\n                pair();\n            }\n            else {\n                printError(localization['Manual']['Error']['Unreachable']);\n            }\n        });\n    }\n\n    // Open pairing view\n    function pair() {\n        unloadManualView();\n        loadPairingView();\n    }\n\n    // Close the window\n    function close() {\n        window.close();\n    }\n\n    // Unload view\n    function unloadManualView() {\n        // Remove event listener\n        document.removeEventListener('enterPressed', check);\n        document.removeEventListener('escPressed', close);\n    }\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/setup/js/pairingView.js",
    "content": "/**\n@file      pairingView.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\n// Load the pairing view\nfunction loadPairingView() {\n    // Time used to automatically pair bridges\n    let autoPairingTimeout = 30;\n\n    // Define local timer\n    let timer = null;\n\n    // Set the status bar\n    setStatusBar('pairing');\n\n    // Fill the title\n    document.getElementById('title').innerHTML = localization['Pairing']['Title'];\n\n    // Fill the content area\n    document.getElementById('content').innerHTML = `\n        <p>${localization['Pairing']['Description']}</p>\n        <img class=\"image\" src=\"images/bridge_pressed.png\" alt=\"${localization['Pairing']['Title']}\">\n        <div id=\"loader\"></div>\n        <div id=\"controls\"></div>\n    `;\n\n    // Start the pairing\n    autoPairing();\n\n    // For n seconds try to connect to the bridge automatically\n    function autoPairing() {\n        // Define local timer counter\n        let timerCounter = 0;\n\n        // Start a new timer to auto connect to the bridges\n        timer = setInterval(() => {\n            if (timerCounter < autoPairingTimeout) {\n                // Try to connect for n seconds\n                pair();\n                timerCounter++;\n            }\n            else {\n                // If auto connect was not successful for n times,\n                // stop auto connecting and show controls\n\n                // Stop the timer\n                clearInterval(timer);\n                timer = null;\n\n                // Hide the loader animation\n                document.getElementById('loader').classList.add('hide');\n\n                // Show manual user controls instead\n                document.getElementById('controls').innerHTML = `\n                    <div class=\"button\" id=\"retry\">${localization['Pairing']['Retry']}</div>\n                    <div class=\"button-transparent\" id=\"close\">${localization['Pairing']['Close']}</div>\n                `;\n\n                // Add event listener\n                document.getElementById('retry').addEventListener('click', retry);\n                document.addEventListener('enterPressed', retry);\n\n                document.getElementById('close').addEventListener('click', close);\n                document.addEventListener('escPressed', close);\n            }\n        }, 1000)\n    }\n\n    // Try to pair with all discovered bridges\n    function pair() {\n        bridges.forEach(item => {\n            item.pair((status, data) => {\n                if (status) {\n                    // Pairing was successful\n                    bridge = item;\n\n                    // Show the save view\n                    unloadPairingView();\n                    loadSaveView();\n                }\n            });\n        });\n    }\n\n    // Retry pairing by reloading the view\n    function retry() {\n        unloadPairingView();\n        loadPairingView();\n    }\n\n    // Close the window\n    function close() {\n        window.close();\n    }\n\n    // Unload view\n    function unloadPairingView() {\n        // Stop the timer\n        clearInterval(timer);\n        timer = null;\n\n        // Remove event listener\n        document.removeEventListener('escPressed', retry);\n        document.removeEventListener('enterPressed', close);\n    }\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/setup/js/saveView.js",
    "content": "/**\n@file      saveView.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code is licensed under the MIT-style license found in the LICENSE file.\n*/\n\n// Load the save view\nfunction loadSaveView() {\n    // Set the status bar\n    setStatusBar('save');\n\n    // Fill the title\n    document.getElementById('title').innerHTML = localization['Save']['Title'];\n\n    // Fill the content area\n    document.getElementById('content').innerHTML = `\n        <p>${localization['Save']['Description']}</p>\n        <img class=\"image\" src=\"images/bridge_paired.png\" alt=\"${localization['Save']['Title']}\">\n        <div class=\"button\" id=\"close\">${localization['Save']['Save']}</div>\n    `;\n\n    // Add event listener\n    document.getElementById('close').addEventListener('click', close);\n    document.addEventListener('enterPressed', close);\n\n    // Save the bridge\n    window.opener.document.dispatchEvent(new CustomEvent('saveBridge', {\n        detail: {\n            ip: bridge.getIP(),\n            id: bridge.getID(),\n            username: bridge.getUsername(),\n        }\n    }));\n\n    // Close this window\n    function close() {\n        window.close();\n    }\n}\n"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/zh_CN.json",
    "content": "{\n  \"Name\": \"Philips Hue\",\n  \"Category\": \"Philips Hue\",\n  \"Description\": \"控制您的 Philips Hue 灯。\",\n  \"com.elgato.philips-hue.power\": {\n    \"Name\": \"开/关\",\n    \"Tooltip\": \"打开或关闭灯。\",\n    \"States\": [\n      {\n        \"Name\": \"开\"\n      },\n      {\n        \"Name\": \"关\"\n      }\n    ]\n  },\n  \"com.elgato.philips-hue.color\": {\n    \"Name\": \"颜色\",\n    \"Tooltip\": \"设置灯的颜色。\"\n  },\n  \"com.elgato.philips-hue.cycle\": {\n    \"Name\": \"颜色循环\",\n    \"Tooltip\": \"在灯光颜色之间循环。\"\n  },\n  \"com.elgato.philips-hue.brightness\": {\n    \"Name\": \"亮度\",\n    \"Tooltip\": \"设置灯的亮度。\",\n    \"Encoder\": {\n      \"TriggerDescription\": {\n        \"Rotate\": \"调整亮度\",\n        \"Touch\": \"打开/关闭灯光\"\n      }\n    }\n  },\n  \"com.elgato.philips-hue.brightness-rel\": {\n    \"Name\": \"相对亮度\",\n    \"Tooltip\": \"相对于当前亮度，调节照明亮度。\"\n  },\n  \"com.elgato.philips-hue.scene\": {\n    \"Name\": \"场景\",\n    \"Tooltip\": \"设置场景。\"\n  },\n  \"Localization\": {\n    \"PI\": {\n      \"Bridge\": \"桥接\",\n      \"NoBridges\": \"无需桥接\",\n      \"AddBridge\": \"新增\",\n      \"Lights\": \"灯\",\n      \"Group\": \"组\",\n      \"LightsTitle\": \"灯\",\n      \"GroupsTitle\": \"组\",\n      \"NoLights\": \"没有灯\",\n      \"NoGroups\": \"没有组\",\n      \"Color\": \"颜色\",\n      \"Colors\": \"颜色\",\n      \"Temperature\": \"温度\",\n      \"Brightness\": \"亮度\",\n      \"Steps\": \"步长\",\n      \"Scene\": \"场景\",\n      \"NoScenes\": \"没有场景\"\n    },\n    \"Setup\": {\n      \"Intro\": {\n        \"Title\": \"添加桥接\",\n        \"Description\": \"与 Philips Hue 桥接配对以控制您的灯。\",\n        \"Start\": \"发现桥接\",\n        \"Manual\": \"手动添加桥接\",\n        \"Close\": \"现在不\"\n      },\n      \"Discovery\": {\n        \"Title\": \"正在发现桥接...\",\n        \"TitleNone\": \"没有找到桥接\",\n        \"TitleOne\": \"找到一个桥接\",\n        \"TitleMultiple\": \"找到 {{ number }} 个桥接\",\n        \"DescriptionFound\": \"现在开始配对？\",\n        \"DescriptionNone\": \"确保桥接打开并已联网。\",\n        \"Pair\": \"配对\",\n        \"Close\": \"关闭\",\n        \"Retry\": \"重试\"\n      },\n      \"Manual\": {\n        \"Title\": \"手动添加桥接\",\n        \"Description\": \"如果不支持自动发现，则需要通过 IP 地址手动将 Philips Hue 桥接添加到您的网络中。\",\n        \"IPAddress\": \"桥接的 IP 地址\",\n        \"Check\": \"验证输入\",\n        \"Close\": \"关闭\",\n        \"Error\": {\n          \"Empty\": \"请添加桥接的 IP 地址。\",\n          \"Invalid\": \"输入的 IP 地址格式无效。请检查输入。\",\n          \"Unreachable\": \"桥接无法接通。请检查 IP 地址。\"\n        }\n      },\n      \"Pairing\": {\n        \"Title\": \"正在配对...\",\n        \"Description\": \"现在请按桥接上的链接按钮。\",\n        \"Close\": \"关闭\",\n        \"Retry\": \"重试\"\n      },\n      \"Save\": {\n        \"Title\": \"桥接已添加\",\n        \"Description\": \"桥接配对成功。\",\n        \"Save\": \"完成\"\n      }\n    }\n  }\n}\n"
  }
]