diff --git "a/PREVIEW/\344\272\214\347\273\264\347\240\201 QR code.png" "b/PREVIEW/\344\272\214\347\273\264\347\240\201 QR code.png"
new file mode 100644
index 0000000..461c814
Binary files /dev/null and "b/PREVIEW/\344\272\214\347\273\264\347\240\201 QR code.png" differ
diff --git "a/PREVIEW/\345\260\201\351\235\242 Cover.png" "b/PREVIEW/\345\260\201\351\235\242 Cover.png"
new file mode 100644
index 0000000..6053c43
Binary files /dev/null and "b/PREVIEW/\345\260\201\351\235\242 Cover.png" differ
diff --git "a/PREVIEW/\351\242\204\350\247\210 Preview.png" "b/PREVIEW/\351\242\204\350\247\210 Preview.png"
new file mode 100644
index 0000000..dce927b
Binary files /dev/null and "b/PREVIEW/\351\242\204\350\247\210 Preview.png" differ
diff --git a/PROJECT/GPS-PFD/audio/Boeing_10.mp3 b/PROJECT/GPS-PFD/audio/Boeing_10.mp3
new file mode 100644
index 0000000..ec849d2
Binary files /dev/null and b/PROJECT/GPS-PFD/audio/Boeing_10.mp3 differ
diff --git a/PROJECT/GPS-PFD/audio/Boeing_100.mp3 b/PROJECT/GPS-PFD/audio/Boeing_100.mp3
new file mode 100644
index 0000000..131acde
Binary files /dev/null and b/PROJECT/GPS-PFD/audio/Boeing_100.mp3 differ
diff --git a/PROJECT/GPS-PFD/audio/Boeing_1000.mp3 b/PROJECT/GPS-PFD/audio/Boeing_1000.mp3
new file mode 100644
index 0000000..730479c
Binary files /dev/null and b/PROJECT/GPS-PFD/audio/Boeing_1000.mp3 differ
diff --git a/PROJECT/GPS-PFD/audio/Boeing_20.mp3 b/PROJECT/GPS-PFD/audio/Boeing_20.mp3
new file mode 100644
index 0000000..b4b6406
Binary files /dev/null and b/PROJECT/GPS-PFD/audio/Boeing_20.mp3 differ
diff --git a/PROJECT/GPS-PFD/audio/Boeing_200.mp3 b/PROJECT/GPS-PFD/audio/Boeing_200.mp3
new file mode 100644
index 0000000..25810cb
Binary files /dev/null and b/PROJECT/GPS-PFD/audio/Boeing_200.mp3 differ
diff --git a/PROJECT/GPS-PFD/audio/Boeing_2500.mp3 b/PROJECT/GPS-PFD/audio/Boeing_2500.mp3
new file mode 100644
index 0000000..31856f8
Binary files /dev/null and b/PROJECT/GPS-PFD/audio/Boeing_2500.mp3 differ
diff --git a/PROJECT/GPS-PFD/audio/Boeing_30.mp3 b/PROJECT/GPS-PFD/audio/Boeing_30.mp3
new file mode 100644
index 0000000..03c86f5
Binary files /dev/null and b/PROJECT/GPS-PFD/audio/Boeing_30.mp3 differ
diff --git a/PROJECT/GPS-PFD/audio/Boeing_300.mp3 b/PROJECT/GPS-PFD/audio/Boeing_300.mp3
new file mode 100644
index 0000000..8189080
Binary files /dev/null and b/PROJECT/GPS-PFD/audio/Boeing_300.mp3 differ
diff --git a/PROJECT/GPS-PFD/audio/Boeing_40.mp3 b/PROJECT/GPS-PFD/audio/Boeing_40.mp3
new file mode 100644
index 0000000..65f297d
Binary files /dev/null and b/PROJECT/GPS-PFD/audio/Boeing_40.mp3 differ
diff --git a/PROJECT/GPS-PFD/audio/Boeing_400.mp3 b/PROJECT/GPS-PFD/audio/Boeing_400.mp3
new file mode 100644
index 0000000..4efcc13
Binary files /dev/null and b/PROJECT/GPS-PFD/audio/Boeing_400.mp3 differ
diff --git a/PROJECT/GPS-PFD/audio/Boeing_50.mp3 b/PROJECT/GPS-PFD/audio/Boeing_50.mp3
new file mode 100644
index 0000000..bf44d30
Binary files /dev/null and b/PROJECT/GPS-PFD/audio/Boeing_50.mp3 differ
diff --git a/PROJECT/GPS-PFD/audio/Boeing_500.mp3 b/PROJECT/GPS-PFD/audio/Boeing_500.mp3
new file mode 100644
index 0000000..4b5f1a9
Binary files /dev/null and b/PROJECT/GPS-PFD/audio/Boeing_500.mp3 differ
diff --git a/PROJECT/GPS-PFD/audio/Boeing_AirspeedLow.mp3 b/PROJECT/GPS-PFD/audio/Boeing_AirspeedLow.mp3
new file mode 100644
index 0000000..9ca6ebc
Binary files /dev/null and b/PROJECT/GPS-PFD/audio/Boeing_AirspeedLow.mp3 differ
diff --git a/PROJECT/GPS-PFD/audio/Boeing_ApproachingMinimums.mp3 b/PROJECT/GPS-PFD/audio/Boeing_ApproachingMinimums.mp3
new file mode 100644
index 0000000..79ab148
Binary files /dev/null and b/PROJECT/GPS-PFD/audio/Boeing_ApproachingMinimums.mp3 differ
diff --git a/PROJECT/GPS-PFD/audio/Boeing_Minimums.mp3 b/PROJECT/GPS-PFD/audio/Boeing_Minimums.mp3
new file mode 100644
index 0000000..102a76b
Binary files /dev/null and b/PROJECT/GPS-PFD/audio/Boeing_Minimums.mp3 differ
diff --git a/PROJECT/GPS-PFD/audio/Boeing_Overspeed.mp3 b/PROJECT/GPS-PFD/audio/Boeing_Overspeed.mp3
new file mode 100644
index 0000000..1f5d13f
Binary files /dev/null and b/PROJECT/GPS-PFD/audio/Boeing_Overspeed.mp3 differ
diff --git a/PROJECT/GPS-PFD/audio/Common_BankAngle.mp3 b/PROJECT/GPS-PFD/audio/Common_BankAngle.mp3
new file mode 100644
index 0000000..eb41503
Binary files /dev/null and b/PROJECT/GPS-PFD/audio/Common_BankAngle.mp3 differ
diff --git a/PROJECT/GPS-PFD/audio/Common_DontSink.mp3 b/PROJECT/GPS-PFD/audio/Common_DontSink.mp3
new file mode 100644
index 0000000..9d7d33e
Binary files /dev/null and b/PROJECT/GPS-PFD/audio/Common_DontSink.mp3 differ
diff --git a/PROJECT/GPS-PFD/audio/Common_GlideSlope.mp3 b/PROJECT/GPS-PFD/audio/Common_GlideSlope.mp3
new file mode 100644
index 0000000..f690ddb
Binary files /dev/null and b/PROJECT/GPS-PFD/audio/Common_GlideSlope.mp3 differ
diff --git a/PROJECT/GPS-PFD/audio/Common_PullUp.mp3 b/PROJECT/GPS-PFD/audio/Common_PullUp.mp3
new file mode 100644
index 0000000..44cf3a4
Binary files /dev/null and b/PROJECT/GPS-PFD/audio/Common_PullUp.mp3 differ
diff --git a/PROJECT/GPS-PFD/audio/Common_SinkRate.mp3 b/PROJECT/GPS-PFD/audio/Common_SinkRate.mp3
new file mode 100644
index 0000000..859e0cc
Binary files /dev/null and b/PROJECT/GPS-PFD/audio/Common_SinkRate.mp3 differ
diff --git "a/PROJECT/GPS-PFD/docs/GPS-PFD \350\257\264\346\230\216\346\226\207\346\241\243.pdf" "b/PROJECT/GPS-PFD/docs/GPS-PFD \350\257\264\346\230\216\346\226\207\346\241\243.pdf"
new file mode 100644
index 0000000..829392d
Binary files /dev/null and "b/PROJECT/GPS-PFD/docs/GPS-PFD \350\257\264\346\230\216\346\226\207\346\241\243.pdf" differ
diff --git a/PROJECT/GPS-PFD/images/Icon.png b/PROJECT/GPS-PFD/images/Icon.png
new file mode 100644
index 0000000..10845b5
Binary files /dev/null and b/PROJECT/GPS-PFD/images/Icon.png differ
diff --git a/PROJECT/GPS-PFD/images/Icon_Large.png b/PROJECT/GPS-PFD/images/Icon_Large.png
new file mode 100644
index 0000000..6158877
Binary files /dev/null and b/PROJECT/GPS-PFD/images/Icon_Large.png differ
diff --git a/PROJECT/GPS-PFD/images/Icon_Maskable.png b/PROJECT/GPS-PFD/images/Icon_Maskable.png
new file mode 100644
index 0000000..c8083a0
Binary files /dev/null and b/PROJECT/GPS-PFD/images/Icon_Maskable.png differ
diff --git a/PROJECT/GPS-PFD/images/Icon_Small.png b/PROJECT/GPS-PFD/images/Icon_Small.png
new file mode 100644
index 0000000..8fae758
Binary files /dev/null and b/PROJECT/GPS-PFD/images/Icon_Small.png differ
diff --git a/PROJECT/GPS-PFD/images/Preview.jpg b/PROJECT/GPS-PFD/images/Preview.jpg
new file mode 100644
index 0000000..04261a4
Binary files /dev/null and b/PROJECT/GPS-PFD/images/Preview.jpg differ
diff --git a/PROJECT/GPS-PFD/index.html b/PROJECT/GPS-PFD/index.html
new file mode 100644
index 0000000..479574f
--- /dev/null
+++ b/PROJECT/GPS-PFD/index.html
@@ -0,0 +1,2223 @@
+
+
+
+
+
+
+
+
+
+
+ GPS-PFD
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PFD
+
+
+
+
+ -
+ 姿态仪状态
+
+ -
+
+
+
+ -
+
+ - 9090
+
+
+
+ - 8080
+
+
+
+ - 7070
+
+
+
+ - 6060
+
+
+
+ - 5050
+
+
+
+ - 4040
+
+
+
+ - 3030
+
+
+
+ - 2020
+
+
+
+ - 1010
+
+
+
+
+
+
+
+ - 1010
+
+
+
+ - 2020
+
+
+
+ - 3030
+
+
+
+ - 4040
+
+
+
+ - 5050
+
+
+
+ - 6060
+
+
+
+ - 7070
+
+
+
+ - 8080
+
+
+
+ - 9090
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+
+
+ -
+ GPS 状态
+ 未知
+
+ -
+ 加速计状态
+ 未知
+
+
+
+ -
+ 地速
+ 未知
+
+ -
+ 真空速
+ 未知
+
+ -
+ 风
+ 未知
+
+
+
+
+
+
+
+ -
+ 速度模式
+ 未知
+
+ -
+ 姿态模式
+ 未知
+
+ -
+ 高度模式
+ 未知
+
+
+
+
+ 飞航模式
+
+
+
+ -
+ 空速表状态
+
+ -
+
+ - 1000
+
+ - 980
+
+ - 960
+
+ - 940
+
+ - 920
+
+ - 900
+
+ - 880
+
+ - 860
+
+ - 840
+
+ - 820
+
+ - 800
+
+ - 780
+
+ - 760
+
+ - 740
+
+ - 720
+
+ - 700
+
+ - 680
+
+ - 660
+
+ - 640
+
+ - 620
+
+ - 600
+
+ - 580
+
+ - 560
+
+ - 540
+
+ - 520
+
+ - 500
+
+ - 480
+
+ - 460
+
+ - 440
+
+ - 420
+
+ - 400
+
+ - 380
+
+ - 360
+
+ - 340
+
+ - 320
+
+ - 300
+
+ - 280
+
+ - 260
+
+ - 240
+
+ - 220
+
+ - 200
+
+ - 180
+
+ - 160
+
+ - 140
+
+ - 120
+
+ - 100
+
+ - 80
+
+ - 60
+
+ - 40
+
+ - 20
+
+ - 0
+
+
+ -
+
+
+
+ -
+
+
+ -
+ 马赫数
+
+
+
+
+
+ -
+ 高度表状态
+
+ -
+
+ - 50000
+
+ - 49800
+
+ - 49600
+
+ - 49400
+
+ - 49200
+
+ - 49000
+
+ - 48800
+
+ - 48600
+
+ - 48400
+
+ - 48200
+
+ - 48000
+
+ - 47800
+
+ - 47600
+
+ - 47400
+
+ - 47200
+
+ - 47000
+
+ - 46800
+
+ - 46600
+
+ - 46400
+
+ - 46200
+
+ - 46000
+
+ - 45800
+
+ - 45600
+
+ - 45400
+
+ - 45200
+
+ - 45000
+
+ - 44800
+
+ - 44600
+
+ - 44400
+
+ - 44200
+
+ - 44000
+
+ - 43800
+
+ - 43600
+
+ - 43400
+
+ - 43200
+
+ - 43000
+
+ - 42800
+
+ - 42600
+
+ - 42400
+
+ - 42200
+
+ - 42000
+
+ - 41800
+
+ - 41600
+
+ - 41400
+
+ - 41200
+
+ - 41000
+
+ - 40800
+
+ - 40600
+
+ - 40400
+
+ - 40200
+
+ - 40000
+
+ - 39800
+
+ - 39600
+
+ - 39400
+
+ - 39200
+
+ - 39000
+
+ - 38800
+
+ - 38600
+
+ - 38400
+
+ - 38200
+
+ - 38000
+
+ - 37800
+
+ - 37600
+
+ - 37400
+
+ - 37200
+
+ - 37000
+
+ - 36800
+
+ - 36600
+
+ - 36400
+
+ - 36200
+
+ - 36000
+
+ - 35800
+
+ - 35600
+
+ - 35400
+
+ - 35200
+
+ - 35000
+
+ - 34800
+
+ - 34600
+
+ - 34400
+
+ - 34200
+
+ - 34000
+
+ - 33800
+
+ - 33600
+
+ - 33400
+
+ - 33200
+
+ - 33000
+
+ - 32800
+
+ - 32600
+
+ - 32400
+
+ - 32200
+
+ - 32000
+
+ - 31800
+
+ - 31600
+
+ - 31400
+
+ - 31200
+
+ - 31000
+
+ - 30800
+
+ - 30600
+
+ - 30400
+
+ - 30200
+
+ - 30000
+
+ - 29800
+
+ - 29600
+
+ - 29400
+
+ - 29200
+
+ - 29000
+
+ - 28800
+
+ - 28600
+
+ - 28400
+
+ - 28200
+
+ - 28000
+
+ - 27800
+
+ - 27600
+
+ - 27400
+
+ - 27200
+
+ - 27000
+
+ - 26800
+
+ - 26600
+
+ - 26400
+
+ - 26200
+
+ - 26000
+
+ - 25800
+
+ - 25600
+
+ - 25400
+
+ - 25200
+
+ - 25000
+
+ - 24800
+
+ - 24600
+
+ - 24400
+
+ - 24200
+
+ - 24000
+
+ - 23800
+
+ - 23600
+
+ - 23400
+
+ - 23200
+
+ - 23000
+
+ - 22800
+
+ - 22600
+
+ - 22400
+
+ - 22200
+
+ - 22000
+
+ - 21800
+
+ - 21600
+
+ - 21400
+
+ - 21200
+
+ - 21000
+
+ - 20800
+
+ - 20600
+
+ - 20400
+
+ - 20200
+
+ - 20000
+
+ - 19800
+
+ - 19600
+
+ - 19400
+
+ - 19200
+
+ - 19000
+
+ - 18800
+
+ - 18600
+
+ - 18400
+
+ - 18200
+
+ - 18000
+
+ - 17800
+
+ - 17600
+
+ - 17400
+
+ - 17200
+
+ - 17000
+
+ - 16800
+
+ - 16600
+
+ - 16400
+
+ - 16200
+
+ - 16000
+
+ - 15800
+
+ - 15600
+
+ - 15400
+
+ - 15200
+
+ - 15000
+
+ - 14800
+
+ - 14600
+
+ - 14400
+
+ - 14200
+
+ - 14000
+
+ - 13800
+
+ - 13600
+
+ - 13400
+
+ - 13200
+
+ - 13000
+
+ - 12800
+
+ - 12600
+
+ - 12400
+
+ - 12200
+
+ - 12000
+
+ - 11800
+
+ - 11600
+
+ - 11400
+
+ - 11200
+
+ - 11000
+
+ - 10800
+
+ - 10600
+
+ - 10400
+
+ - 10200
+
+ - 10000
+
+ - 9800
+
+ - 9600
+
+ - 9400
+
+ - 9200
+
+ - 9000
+
+ - 8800
+
+ - 8600
+
+ - 8400
+
+ - 8200
+
+ - 8000
+
+ - 7800
+
+ - 7600
+
+ - 7400
+
+ - 7200
+
+ - 7000
+
+ - 6800
+
+ - 6600
+
+ - 6400
+
+ - 6200
+
+ - 6000
+
+ - 5800
+
+ - 5600
+
+ - 5400
+
+ - 5200
+
+ - 5000
+
+ - 4800
+
+ - 4600
+
+ - 4400
+
+ - 4200
+
+ - 4000
+
+ - 3800
+
+ - 3600
+
+ - 3400
+
+ - 3200
+
+ - 3000
+
+ - 2800
+
+ - 2600
+
+ - 2400
+
+ - 2200
+
+ - 2000
+
+ - 1800
+
+ - 1600
+
+ - 1400
+
+ - 1200
+
+ - 1000
+
+ - 800
+
+ - 600
+
+ - 400
+
+ - 200
+
+ - 000
+
+ - -200
+
+ - -400
+
+ - -600
+
+ - -800
+
+ - -1000
+
+ - -1200
+
+ - -1400
+
+ - -1600
+
+ - -1800
+
+ - -2000
+
+
+ -
+
+
+
+ -
+
+
+ -
+ 米制高度
+
+
+
+
+
+ -
+ 垂直速度表状态
+
+ -
+
+ - 6
+
+ - 2
+
+ - 1
+
+
+
+ - 1
+
+ - 2
+
+ - 6
+
+
+ -
+
+
+ -
+ 垂直速度
+
+
+
+
+
+ -
+ 朝向指示器状态
+
+ -
+
+ - 0
+
+ - 1
+
+ - 2
+
+ - 3
+
+ - 4
+
+ - 5
+
+ - 6
+
+ - 7
+
+ - 8
+
+ - 9
+
+ - 10
+
+ - 11
+
+ - 12
+
+ - 13
+
+ - 14
+
+ - 15
+
+ - 16
+
+ - 17
+
+ - 18
+
+ - 19
+
+ - 20
+
+ - 21
+
+ - 22
+
+ - 23
+
+ - 24
+
+ - 25
+
+ - 26
+
+ - 27
+
+ - 28
+
+ - 29
+
+ - 30
+
+ - 31
+
+ - 32
+
+ - 33
+
+ - 34
+
+ - 35
+
+
+
+ -
+
+
+
+
+
+ 测距仪
+ 距离
+ 剩余时间
+
+
+
+ -
+
+
+
+ 无线电
+ 高度
+
+
+
+
+
+ 决断高度
+ 未知
+
+
+ 警告信息
+
+
+
+ ???
+
+
+ ???
+
+
+ ???
+
+
+ ???
+
+
+ ???
+
+
+ ???
+
+
+
+
+
+
+ 设定
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+
+ 帮助
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+
+
+
+
+ 正在初始化...
+
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+ 对话框文本
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+
+
+
diff --git a/PROJECT/GPS-PFD/manifests/manifest.json b/PROJECT/GPS-PFD/manifests/manifest.json
new file mode 100644
index 0000000..8cb2ade
--- /dev/null
+++ b/PROJECT/GPS-PFD/manifests/manifest.json
@@ -0,0 +1,35 @@
+{
+ "lang": "zh-CN",
+ "name": "GPS-PFD",
+ "short_name": "GPS-PFD",
+ "icons": [
+ {
+ "src": "../images/Icon.png",
+ "type": "image/png",
+ "sizes": "192x192"
+ },
+ {
+ "src": "../images/Icon_Large.png",
+ "type": "image/png",
+ "sizes": "512x512"
+ },
+ {
+ "src": "../images/Icon_Maskable.png",
+ "type": "image/png",
+ "sizes": "512x512",
+ "purpose": "maskable"
+ }
+ ],
+
+ "description": "利用 GPS 与传感器,在移动设备上模拟飞机驾驶舱的 PFD。这是一个试验性项目。",
+ "screenshots": [
+ {
+ "src": "../images/Preview.jpg",
+ "type": "image/jpg",
+ "sizes": "1280x720"
+ }
+ ],
+
+ "display": "standalone",
+ "background_color": "#A0E0FF"
+}
diff --git a/PROJECT/GPS-PFD/script_ServiceWorker.js b/PROJECT/GPS-PFD/script_ServiceWorker.js
new file mode 100644
index 0000000..3573f76
--- /dev/null
+++ b/PROJECT/GPS-PFD/script_ServiceWorker.js
@@ -0,0 +1,79 @@
+// For SamToki.github.io/GPS-PFD
+// Released under GNU GPL v3 open source license.
+// © 2025 SAM TOKI STUDIO
+
+// Initialization
+ // Declare variables
+ "use strict";
+ // Unsaved
+ const CacheName = "GPS-PFD_v0.13";
+
+// Listeners
+ // Service worker (https://learn.microsoft.com/en-us/microsoft-edge/progressive-web-apps-chromium/how-to/#step-5---add-a-service-worker)
+ self.addEventListener("install", Event => {
+ Event.waitUntil((async() => {
+ const CacheContent = await caches.open(CacheName);
+ CacheContent.addAll([
+ "index.html",
+ "images/Icon_Small.png",
+ "../styles/common.css",
+ "../styles/common_Dark.css",
+ "../styles/common_Genshin.css",
+ "../styles/common_HighContrast.css",
+ "styles/style.css",
+ "styles/style_Dark.css",
+ "styles/style_Genshin.css",
+ "styles/style_HighContrast.css",
+ // "styles/style_PFD???.css",
+ "../scripts/common.js",
+ "../scripts/common_UserDataRepairer.js",
+ "scripts/script.js",
+ "manifests/manifest.json",
+ "images/Icon.png",
+ "images/Icon_Large.png",
+ "images/Icon_Maskable.png",
+ "images/Preview.jpg",
+ "../cursors/BTRAhoge.cur",
+ "../cursors/Genshin.cur",
+ "../cursors/GenshinFurina.cur",
+ "../cursors/GenshinNahida.cur",
+ "../images/Background.jpg",
+ "../audio/Beep.mp3",
+ "audio/Boeing_10.mp3",
+ "audio/Boeing_20.mp3",
+ "audio/Boeing_30.mp3",
+ "audio/Boeing_40.mp3",
+ "audio/Boeing_50.mp3",
+ "audio/Boeing_100.mp3",
+ "audio/Boeing_200.mp3",
+ "audio/Boeing_300.mp3",
+ "audio/Boeing_400.mp3",
+ "audio/Boeing_500.mp3",
+ "audio/Boeing_1000.mp3",
+ "audio/Boeing_2500.mp3",
+ "audio/Boeing_AirspeedLow.mp3",
+ "audio/Boeing_ApproachingMinimums.mp3",
+ "audio/Boeing_Minimums.mp3",
+ "audio/Boeing_Overspeed.mp3",
+ "audio/Common_BankAngle.mp3",
+ "audio/Common_DontSink.mp3",
+ "audio/Common_GlideSlope.mp3",
+ "audio/Common_PullUp.mp3",
+ "audio/Common_SinkRate.mp3",
+ "docs/GPS-PFD 说明文档.pdf"
+ ]);
+ })());
+ });
+ self.addEventListener("fetch", Event => {
+ Event.respondWith((async() => {
+ const CacheContent = await caches.open(CacheName);
+ const CachedResponse = await CacheContent.match(Event.request);
+ if(CachedResponse) {
+ return CachedResponse;
+ } else {
+ const FetchResponse = await fetch(Event.request);
+ CacheContent.put(Event.request, FetchResponse.clone());
+ return FetchResponse;
+ }
+ })());
+ });
diff --git a/PROJECT/GPS-PFD/scripts/script.js b/PROJECT/GPS-PFD/scripts/script.js
new file mode 100644
index 0000000..7638a5c
--- /dev/null
+++ b/PROJECT/GPS-PFD/scripts/script.js
@@ -0,0 +1,4174 @@
+// For SamToki.github.io/GPS-PFD
+// Released under GNU GPL v3 open source license.
+// © 2025 SAM TOKI STUDIO
+
+// Initialization
+ // Declare variables
+ "use strict";
+ // Unsaved
+ const CurrentVersion = 0.13;
+ var PFD0 = {
+ RawData: {
+ GPS: {
+ Position: {
+ Lat: null, Lon: null, Accuracy: null
+ },
+ Speed: null,
+ Altitude: {
+ Altitude: null, Accuracy: null
+ },
+ Heading: null,
+ Timestamp: 0
+ },
+ Accel: {
+ Accel: {
+ Absolute: {
+ X: null, Y: null, Z: null
+ },
+ AbsoluteWithGravity: {
+ X: null, Y: null, Z: null
+ },
+ Relative: {
+ Forward: 0, Right: 0, Upward: 0
+ },
+ RelativeWithGravity: {
+ Forward: 0, Right: 0, Upward: 0
+ },
+ Aligned: {
+ Forward: 0, Right: 0, Upward: 0
+ }
+ },
+ Attitude: {
+ Original: {
+ Pitch: 0, Roll: 0
+ },
+ Aligned: {
+ Pitch: 0, Roll: 0
+ }
+ },
+ Speed: {
+ Vector: {
+ Forward: 0, Right: 0, Upward: 0
+ },
+ Speed: 0
+ },
+ Altitude: 0,
+ Interval: null,
+ Timestamp: 0
+ },
+ Manual: {
+ Attitude: {
+ Pitch: 0, Roll: 0
+ },
+ Speed: 0, Altitude: 0
+ }
+ },
+ Status: {
+ GPS: {
+ IsPositionAvailable: false, IsPositionAccurate: false,
+ IsSpeedAvailable: false, IsAltitudeAvailable: false, IsAltitudeAccurate: false, IsHeadingAvailable: false
+ },
+ IsAccelAvailable: false,
+ IsDecisionAltitudeActive: false
+ },
+ Stats: {
+ ClockTime: 0, PreviousClockTime: Date.now(),
+ Attitude: {
+ Pitch: 0, Pitch2: 0, Roll: 0
+ },
+ Speed: {
+ Speed: 0, Vertical: 0, Pitch: 0,
+ GS: 0, GSDisplay: 0, TAS: 0, TASDisplay: 0,
+ Wind: {
+ Heading: 0, RelativeHeading: 0 // Wind "heading" is the opposite of wind direction.
+ },
+ IAS: 0, TapeDisplay: 0, PreviousTapeDisplay: 0, BalloonDisplay: [0, 0, 0, 0],
+ Trend: 0, TrendDisplay: 0,
+ MachNumber: 0
+ },
+ Altitude: {
+ Altitude: 0, TapeDisplay: 0, PreviousTapeDisplay: 0, BalloonDisplay: [0, 0, 0, 0, 0],
+ Trend: 0, TrendDisplay: 0,
+ RadioDisplay: 0,
+ DecisionTimestamp: 0
+ },
+ Heading: {
+ Heading: 0, Display: 0
+ },
+ DME: {
+ Lat: 0, Lon: 0,
+ Distance: 0, ETA: 0
+ },
+ FlightModeTimestamp: 0
+ },
+ Alert: {
+ Active: {
+ AttitudeWarning: "", SpeedWarning: "", AltitudeCallout: "", AltitudeWarning: ""
+ },
+ NowPlaying: {
+ AttitudeWarning: "", SpeedWarning: "", AltitudeCallout: "", AltitudeWarning: ""
+ }
+ }
+ };
+ Automation.ClockPFD = null;
+
+ // Saved
+ var Subsystem = {
+ Display: {
+ PFDStyle: "Default",
+ FlipPFDVertically: false,
+ KeepScreenOn: false
+ },
+ Audio: {
+ Scheme: "Boeing",
+ AttitudeAlertVolume: 0, SpeedAlertVolume: 0, AltitudeAlertVolume: 0
+ },
+ I18n: {
+ AlwaysUseEnglishTerminologyOnPFD: false,
+ SpeedUnit: "Knot", DistanceUnit: "NauticalMile", AltitudeUnit: "Feet", VerticalSpeedUnit: "FeetPerMin", TemperatureUnit: "Celsius", PressureUnit: "Hectopascal"
+ }
+ },
+ PFD = {
+ Attitude: {
+ IsEnabled: true,
+ Mode: "Accel",
+ Offset: {
+ Pitch: 0, Roll: 0
+ }
+ },
+ Speed: {
+ Mode: "GPS",
+ IASAlgorithm: "SimpleAlgorithm",
+ AirportTemperature: {
+ Departure: 288.15, Arrival: 288.15
+ },
+ RelativeHumidity: {
+ Departure: 50, Arrival: 50
+ },
+ QNH: {
+ Departure: 1013.25, Arrival: 1013.25
+ },
+ Wind: {
+ Direction: 0, Speed: 0
+ },
+ SpeedLimit: {
+ Min: 0, MaxOnFlapsUp: 277.5, MaxOnFlapsFull: 277.5
+ }
+ },
+ Altitude: {
+ Mode: "GPS",
+ AirportElevation: {
+ Departure: 0, Arrival: 0
+ },
+ DecisionHeight: 76.2
+ },
+ DME: {
+ IsEnabled: false,
+ AirportCoordinates: {
+ Departure: {
+ Lat: 0, Lon: 0
+ },
+ Arrival: {
+ Lat: 0, Lon: 0
+ }
+ }
+ },
+ FlightMode: {
+ FlightMode: "DepartureGround",
+ AutoSwitchFlightModeAndSwapAirportData: true
+ },
+ Flaps: 0
+ };
+
+ // Load
+ window.onload = Load();
+ function Load() {
+ // User data
+ if(localStorage.System != undefined) {
+ System = JSON.parse(localStorage.getItem("System"));
+ }
+ switch(System.I18n.Language) {
+ case "Auto":
+ // navigator.language ...
+ break;
+ case "en-US":
+ /* ChangeCursorOverall("wait");
+ window.location.replace("index_en-US.html"); */
+ ShowDialog("System_LanguageUnsupported",
+ "Caution",
+ "Sorry, this webpage currently does not support English (US).",
+ "", "", "", "OK");
+ break;
+ case "ja-JP":
+ ShowDialog("System_LanguageUnsupported",
+ "Caution",
+ "すみません。このページは日本語にまだサポートしていません。",
+ "", "", "", "OK");
+ break;
+ case "zh-CN":
+ break;
+ case "zh-TW":
+ ShowDialog("System_LanguageUnsupported",
+ "Caution",
+ "抱歉,本網頁暫不支援繁體中文。",
+ "", "", "", "確定");
+ break;
+ default:
+ AlertSystemError("The value of System.I18n.Language \"" + System.I18n.Language + "\" in function Load is invalid.");
+ break;
+ }
+ if(System.Version.GPSPFD != undefined) {
+ if(Math.trunc(CurrentVersion) - Math.trunc(System.Version.GPSPFD) >= 1) {
+ ShowDialog("System_MajorUpdateDetected",
+ "Info",
+ "检测到大版本更新。若您继续使用旧版本的用户数据,则有可能发生兼容性问题。敬请留意。",
+ "", "", "", "确定");
+ System.Version.GPSPFD = CurrentVersion;
+ }
+ } else {
+ System.Version.GPSPFD = CurrentVersion;
+ }
+ if(localStorage.GPSPFD_Subsystem != undefined) {
+ Subsystem = JSON.parse(localStorage.getItem("GPSPFD_Subsystem"));
+ }
+ if(localStorage.GPSPFD_PFD != undefined) {
+ PFD = JSON.parse(localStorage.getItem("GPSPFD_PFD"));
+ }
+
+ // Refresh
+ RefreshSystem();
+ RefreshSubsystem();
+ RefreshPFD();
+
+ // PWA
+ navigator.serviceWorker.register("script_ServiceWorker.js").then(function(ServiceWorkerRegistration) {
+ // Detect update (https://stackoverflow.com/a/41896649)
+ ServiceWorkerRegistration.addEventListener("updatefound", function() {
+ const ServiceWorkerInstallation = ServiceWorkerRegistration.installing;
+ ServiceWorkerInstallation.addEventListener("statechange", function() {
+ if(ServiceWorkerInstallation.state == "installed" && navigator.serviceWorker.controller != null) {
+ Show("Label_HelpPWANewVersionReady");
+ ShowDialog("System_PWANewVersionReady",
+ "Info",
+ "新版本已就绪,将在下次启动时生效。",
+ "", "", "", "确定");
+ }
+ });
+ });
+
+ // Read service worker status (https://github.com/GoogleChrome/samples/blob/gh-pages/service-worker/registration-events/index.html)
+ switch(true) {
+ case ServiceWorkerRegistration.installing != null:
+ ChangeText("Label_SettingsPWAServiceWorkerRegistration", "等待生效");
+ break;
+ case ServiceWorkerRegistration.waiting != null:
+ ChangeText("Label_SettingsPWAServiceWorkerRegistration", "等待更新");
+ Show("Label_HelpPWANewVersionReady");
+ ShowDialog("System_PWANewVersionReady",
+ "Info",
+ "新版本已就绪,将在下次启动时生效。",
+ "", "", "", "确定");
+ break;
+ case ServiceWorkerRegistration.active != null:
+ ChangeText("Label_SettingsPWAServiceWorkerRegistration", "已生效");
+ break;
+ default:
+ break;
+ }
+ if(navigator.serviceWorker.controller != null) {
+ ChangeText("Label_SettingsPWAServiceWorkerController", "已生效");
+ } else {
+ ChangeText("Label_SettingsPWAServiceWorkerController", "未生效");
+ }
+ });
+
+ // Ready
+ setTimeout(HideToast, 0);
+ if(System.DontShowAgain.includes("GPSPFD_System_Welcome") == false) {
+ ShowDialog("System_Welcome",
+ "Info",
+ "欢迎使用 GPS-PFD。若您是首次使用,请先阅读「使用前须知」。",
+ "不再提示", "", "了解更多", "关闭");
+ }
+ }
+
+// Refresh
+ // Webpage
+ function RefreshWebpage() {
+ ShowDialog("System_RefreshingWebpage",
+ "Info",
+ "正在刷新网页...",
+ "", "", "", "确定");
+ ChangeCursorOverall("wait");
+ window.location.reload();
+ }
+
+ // System
+ function RefreshSystem() {
+ // Topbar
+ if(IsMobileLayout() == false) {
+ HideHorizontally("Button_Nav");
+ ChangeInert("DropctrlGroup_Nav", false);
+ } else {
+ Show("Button_Nav");
+ ChangeInert("DropctrlGroup_Nav", true);
+ }
+
+ // Fullscreen
+ if(IsFullscreen() == false) {
+ Show("Topbar");
+ ChangeText("Button_PFDToggleFullscreen", "全屏");
+ } else {
+ Hide("Topbar");
+ ChangeText("Button_PFDToggleFullscreen", "退出全屏");
+ }
+
+ // Settings
+ // Display
+ if(window.matchMedia("(prefers-contrast: more)").matches == false) {
+ ChangeDisabled("Combobox_SettingsTheme", false);
+ } else {
+ System.Display.Theme = "HighContrast";
+ ChangeDisabled("Combobox_SettingsTheme", true);
+ }
+ ChangeValue("Combobox_SettingsTheme", System.Display.Theme);
+ switch(System.Display.Theme) {
+ case "Auto":
+ ChangeLink("ThemeVariant_Common", "../styles/common_Dark.css");
+ ChangeMediaCondition("ThemeVariant_Common", "(prefers-color-scheme: dark)");
+ ChangeLink("ThemeVariant_Style", "styles/style_Dark.css");
+ ChangeMediaCondition("ThemeVariant_Style", "(prefers-color-scheme: dark)");
+ break;
+ case "Light":
+ ChangeLink("ThemeVariant_Common", "");
+ ChangeMediaCondition("ThemeVariant_Common", "");
+ ChangeLink("ThemeVariant_Style", "");
+ ChangeMediaCondition("ThemeVariant_Style", "");
+ break;
+ case "Dark":
+ ChangeLink("ThemeVariant_Common", "../styles/common_Dark.css");
+ ChangeMediaCondition("ThemeVariant_Common", "");
+ ChangeLink("ThemeVariant_Style", "styles/style_Dark.css");
+ ChangeMediaCondition("ThemeVariant_Style", "");
+ break;
+ case "Genshin":
+ ChangeLink("ThemeVariant_Common", "../styles/common_Genshin.css");
+ ChangeMediaCondition("ThemeVariant_Common", "");
+ ChangeLink("ThemeVariant_Style", "styles/style_Genshin.css");
+ ChangeMediaCondition("ThemeVariant_Style", "");
+ break;
+ case "HighContrast":
+ ChangeLink("ThemeVariant_Common", "../styles/common_HighContrast.css");
+ ChangeMediaCondition("ThemeVariant_Common", "");
+ ChangeLink("ThemeVariant_Style", "styles/style_HighContrast.css");
+ ChangeMediaCondition("ThemeVariant_Style", "");
+ break;
+ default:
+ AlertSystemError("The value of System.Display.Theme \"" + System.Display.Theme + "\" in function RefreshSystem is invalid.");
+ break;
+ }
+ ChangeValue("Combobox_SettingsCursor", System.Display.Cursor);
+ switch(System.Display.Cursor) {
+ case "Default":
+ ChangeCursorOverall("");
+ break;
+ case "BTRAhoge":
+ case "Genshin":
+ case "GenshinFurina":
+ case "GenshinNahida":
+ ChangeCursorOverall("url(../cursors/" + System.Display.Cursor + ".cur), auto");
+ break;
+ default:
+ AlertSystemError("The value of System.Display.Cursor \"" + System.Display.Cursor + "\" in function RefreshSystem is invalid.");
+ break;
+ }
+ ChangeChecked("Checkbox_SettingsBlurBgImage", System.Display.BlurBgImage);
+ if(System.Display.BlurBgImage == true) {
+ AddClass("BgImage", "Blur");
+ } else {
+ RemoveClass("BgImage", "Blur");
+ }
+ ChangeValue("Combobox_SettingsHotkeyIndicators", System.Display.HotkeyIndicators);
+ switch(System.Display.HotkeyIndicators) {
+ case "Disabled":
+ FadeHotkeyIndicators();
+ break;
+ case "ShowOnWrongKeyPress":
+ case "ShowOnAnyKeyPress":
+ break;
+ case "AlwaysShow":
+ ShowHotkeyIndicators();
+ break;
+ default:
+ AlertSystemError("The value of System.Display.HotkeyIndicators \"" + System.Display.HotkeyIndicators + "\" in function RefreshSystem is invalid.");
+ break;
+ }
+ if(window.matchMedia("(prefers-reduced-motion: reduce)").matches == false) {
+ ChangeDisabled("Combobox_SettingsAnim", false);
+ } else {
+ System.Display.Anim = 0;
+ ChangeDisabled("Combobox_SettingsAnim", true);
+ }
+ ChangeValue("Combobox_SettingsAnim", System.Display.Anim);
+ ChangeAnimOverall(System.Display.Anim);
+
+ // Audio
+ ChangeChecked("Checkbox_SettingsPlayAudio", System.Audio.PlayAudio);
+ if(System.Audio.PlayAudio == true) {
+ Show("Ctrl_SettingsAudioScheme");
+ Show("Ctrl_SettingsAttitudeAlertVolume");
+ Show("Ctrl_SettingsSpeedAlertVolume");
+ Show("Ctrl_SettingsAltitudeAlertVolume");
+ ChangeValue("Combobox_SettingsAudioScheme", Subsystem.Audio.Scheme);
+ ChangeValue("Slider_SettingsAttitudeAlertVolume", Subsystem.Audio.AttitudeAlertVolume);
+ if(Subsystem.Audio.AttitudeAlertVolume > 0) {
+ ChangeText("Label_SettingsAttitudeAlertVolume", Subsystem.Audio.AttitudeAlertVolume + "%");
+ } else {
+ ChangeText("Label_SettingsAttitudeAlertVolume", "禁用");
+ }
+ ChangeVolume("Audio_AttitudeAlert", Subsystem.Audio.AttitudeAlertVolume);
+ ChangeValue("Slider_SettingsSpeedAlertVolume", Subsystem.Audio.SpeedAlertVolume);
+ if(Subsystem.Audio.SpeedAlertVolume > 0) {
+ ChangeText("Label_SettingsSpeedAlertVolume", Subsystem.Audio.SpeedAlertVolume + "%");
+ } else {
+ ChangeText("Label_SettingsSpeedAlertVolume", "禁用");
+ }
+ ChangeVolume("Audio_SpeedAlert", Subsystem.Audio.SpeedAlertVolume);
+ ChangeValue("Slider_SettingsAltitudeAlertVolume", Subsystem.Audio.AltitudeAlertVolume);
+ if(Subsystem.Audio.AltitudeAlertVolume > 0) {
+ ChangeText("Label_SettingsAltitudeAlertVolume", Subsystem.Audio.AltitudeAlertVolume + "%");
+ } else {
+ ChangeText("Label_SettingsAltitudeAlertVolume", "禁用");
+ }
+ ChangeVolume("Audio_AltitudeAlert", Subsystem.Audio.AltitudeAlertVolume);
+ } else {
+ StopAllAudio();
+ Hide("Ctrl_SettingsAudioScheme");
+ Hide("Ctrl_SettingsAttitudeAlertVolume");
+ Hide("Ctrl_SettingsSpeedAlertVolume");
+ Hide("Ctrl_SettingsAltitudeAlertVolume");
+ }
+
+ // PWA
+ if(window.matchMedia("(display-mode: standalone)").matches == true) {
+ ChangeText("Label_SettingsPWAStandaloneDisplay", "是");
+ } else {
+ ChangeText("Label_SettingsPWAStandaloneDisplay", "否");
+ }
+
+ // Dev
+ ChangeChecked("Checkbox_SettingsTryToOptimizePerformance", System.Dev.TryToOptimizePerformance);
+ if(System.Dev.TryToOptimizePerformance == true) {
+ AddClass("Html", "TryToOptimizePerformance");
+ } else {
+ RemoveClass("Html", "TryToOptimizePerformance");
+ }
+ ChangeChecked("Checkbox_SettingsShowDebugOutlines", System.Dev.ShowDebugOutlines);
+ if(System.Dev.ShowDebugOutlines == true) {
+ AddClass("Html", "ShowDebugOutlines");
+ } else {
+ RemoveClass("Html", "ShowDebugOutlines");
+ }
+ ChangeChecked("Checkbox_SettingsUseJapaneseGlyph", System.Dev.UseJapaneseGlyph);
+ if(System.Dev.UseJapaneseGlyph == true) {
+ ChangeLanguage("Html", "ja-JP");
+ } else {
+ ChangeLanguage("Html", "zh-CN");
+ }
+ ChangeValue("Textbox_SettingsFont", System.Dev.Font);
+ ChangeFont("Html", System.Dev.Font);
+
+ // User data
+ ChangeValue("Textbox_SettingsUserDataImport", "");
+
+ // Save user data
+ localStorage.setItem("System", JSON.stringify(System));
+ }
+ function RefreshSubsystem() {
+ // Settings
+ // Display
+ ChangeValue("Combobox_SettingsPFDStyle", Subsystem.Display.PFDStyle);
+ HideHorizontally("Ctnr_PFDDefaultPanel");
+ HideHorizontally("Ctnr_PFDBoeingPanel");
+ HideHorizontally("Ctnr_PFDAirbusPanel");
+ HideHorizontally("Ctnr_PFDHUDPanel");
+ HideHorizontally("Ctnr_PFDBocchi737Panel");
+ HideHorizontally("Ctnr_PFDAnalogGaugesPanel");
+ HideHorizontally("Ctnr_PFDAutomobileSpeedometerPanel");
+ RemoveClass("PFD", "PFDStyleIsDefault");
+ RemoveClass("PFD", "PFDStyleIsBoeing");
+ RemoveClass("PFD", "PFDStyleIsAirbus");
+ RemoveClass("PFD", "PFDStyleIsHUD");
+ RemoveClass("PFD", "PFDStyleIsBocchi737");
+ RemoveClass("PFD", "PFDStyleIsAnalogGauges");
+ RemoveClass("PFD", "PFDStyleIsAutomobileSpeedometer");
+ switch(Subsystem.Display.PFDStyle) {
+ case "Default":
+ Show("Ctnr_PFDDefaultPanel");
+ AddClass("PFD", "PFDStyleIsDefault");
+ break;
+ case "Boeing":
+ case "Airbus":
+ case "HUD":
+ case "Bocchi737":
+ case "AnalogGauges":
+ case "AutomobileSpeedometer":
+ AlertSystemError("A PFD style which is still under construction was selected.");
+ break;
+ default:
+ AlertSystemError("The value of Subsystem.Display.PFDStyle \"" + Subsystem.Display.PFDStyle + "\" in function RefreshSubsystem is invalid.");
+ break;
+ }
+ ChangeChecked("Checkbox_PFDOptionsFlipPFDVertically", Subsystem.Display.FlipPFDVertically);
+ ChangeChecked("Checkbox_SettingsFlipPFDVertically", Subsystem.Display.FlipPFDVertically);
+ ChangeChecked("Checkbox_PFDOptionsKeepScreenOn", Subsystem.Display.KeepScreenOn);
+ ChangeChecked("Checkbox_SettingsKeepScreenOn", Subsystem.Display.KeepScreenOn);
+ if(Subsystem.Display.KeepScreenOn == true) {
+ RequestScreenWakeLock();
+ } else {
+ ReleaseScreenWakeLock();
+ }
+
+ // Audio
+ if(System.Audio.PlayAudio == true) {
+ ChangeValue("Combobox_SettingsAudioScheme", Subsystem.Audio.Scheme);
+ ChangeValue("Slider_SettingsAttitudeAlertVolume", Subsystem.Audio.AttitudeAlertVolume);
+ if(Subsystem.Audio.AttitudeAlertVolume > 0) {
+ ChangeText("Label_SettingsAttitudeAlertVolume", Subsystem.Audio.AttitudeAlertVolume + "%");
+ } else {
+ ChangeText("Label_SettingsAttitudeAlertVolume", "禁用");
+ }
+ ChangeVolume("Audio_AttitudeAlert", Subsystem.Audio.AttitudeAlertVolume);
+ ChangeValue("Slider_SettingsSpeedAlertVolume", Subsystem.Audio.SpeedAlertVolume);
+ if(Subsystem.Audio.SpeedAlertVolume > 0) {
+ ChangeText("Label_SettingsSpeedAlertVolume", Subsystem.Audio.SpeedAlertVolume + "%");
+ } else {
+ ChangeText("Label_SettingsSpeedAlertVolume", "禁用");
+ }
+ ChangeVolume("Audio_SpeedAlert", Subsystem.Audio.SpeedAlertVolume);
+ ChangeValue("Slider_SettingsAltitudeAlertVolume", Subsystem.Audio.AltitudeAlertVolume);
+ if(Subsystem.Audio.AltitudeAlertVolume > 0) {
+ ChangeText("Label_SettingsAltitudeAlertVolume", Subsystem.Audio.AltitudeAlertVolume + "%");
+ } else {
+ ChangeText("Label_SettingsAltitudeAlertVolume", "禁用");
+ }
+ ChangeVolume("Audio_AltitudeAlert", Subsystem.Audio.AltitudeAlertVolume);
+ }
+
+ // I18n
+ ChangeChecked("Checkbox_SettingsAlwaysUseEnglishTerminologyOnPFD", Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD);
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ switch(Subsystem.Display.PFDStyle) {
+ case "Default":
+ ChangeText("Label_PFDDefaultPanelGPSStatusTitle", "GPS 状态");
+ ChangeText("Label_PFDDefaultPanelAccelStatusTitle", "加速计状态");
+ ChangeText("Label_PFDDefaultPanelGSTitle", "地速");
+ ChangeText("Label_PFDDefaultPanelTASTitle", "真空速");
+ ChangeText("Label_PFDDefaultPanelWindTitle", "风");
+ ChangeText("Label_PFDDefaultPanelFlapsTitle", "襟翼");
+ ChangeText("Label_PFDDefaultPanelAttitudeModeTitle", "姿态模式");
+ ChangeText("Label_PFDDefaultPanelSpeedModeTitle", "速度模式");
+ ChangeText("Label_PFDDefaultPanelAltitudeModeTitle", "高度模式");
+ ChangeText("Label_PFDDefaultPanelDMETitle", "测距仪");
+ ChangeText("Label_PFDDefaultPanelDecisionAltitudeTitle", "决断高度");
+ break;
+ case "Boeing":
+ case "Airbus":
+ case "HUD":
+ case "Bocchi737":
+ case "AnalogGauges":
+ case "AutomobileSpeedometer":
+ AlertSystemError("A PFD style which is still under construction was selected.");
+ break;
+ default:
+ AlertSystemError("The value of Subsystem.Display.PFDStyle \"" + Subsystem.Display.PFDStyle + "\" in function RefreshSubsystem is invalid.");
+ break;
+ }
+ } else {
+ switch(Subsystem.Display.PFDStyle) {
+ case "Default":
+ ChangeText("Label_PFDDefaultPanelGPSStatusTitle", "GPS STS");
+ ChangeText("Label_PFDDefaultPanelAccelStatusTitle", "ACCEL STS");
+ ChangeText("Label_PFDDefaultPanelGSTitle", "GS");
+ ChangeText("Label_PFDDefaultPanelTASTitle", "TAS");
+ ChangeText("Label_PFDDefaultPanelWindTitle", "WIND");
+ ChangeText("Label_PFDDefaultPanelFlapsTitle", "FLAPS");
+ ChangeText("Label_PFDDefaultPanelAttitudeModeTitle", "ATT MODE");
+ ChangeText("Label_PFDDefaultPanelSpeedModeTitle", "SPD MODE");
+ ChangeText("Label_PFDDefaultPanelAltitudeModeTitle", "ALT MODE");
+ ChangeText("Label_PFDDefaultPanelDMETitle", "DME");
+ ChangeText("Label_PFDDefaultPanelDecisionAltitudeTitle", "DA");
+ break;
+ case "Boeing":
+ case "Airbus":
+ case "HUD":
+ case "Bocchi737":
+ case "AnalogGauges":
+ case "AutomobileSpeedometer":
+ AlertSystemError("A PFD style which is still under construction was selected.");
+ break;
+ default:
+ AlertSystemError("The value of Subsystem.Display.PFDStyle \"" + Subsystem.Display.PFDStyle + "\" in function RefreshSubsystem is invalid.");
+ break;
+ }
+ }
+ switch(true) {
+ case Subsystem.I18n.SpeedUnit == "KilometerPerHour" && Subsystem.I18n.DistanceUnit == "Kilometer" &&
+ Subsystem.I18n.AltitudeUnit == "Meter" && Subsystem.I18n.VerticalSpeedUnit == "MeterPerSec" &&
+ Subsystem.I18n.PressureUnit == "Hectopascal" && Subsystem.I18n.TemperatureUnit == "Celsius":
+ ChangeValue("Combobox_SettingsMeasurementUnitsPreset", "AllMetric");
+ break;
+ case Subsystem.I18n.SpeedUnit == "Knot" && Subsystem.I18n.DistanceUnit == "NauticalMile" &&
+ Subsystem.I18n.AltitudeUnit == "Feet" && Subsystem.I18n.VerticalSpeedUnit == "FeetPerMin" &&
+ Subsystem.I18n.PressureUnit == "Hectopascal" && Subsystem.I18n.TemperatureUnit == "Celsius":
+ ChangeValue("Combobox_SettingsMeasurementUnitsPreset", "CivilAviation");
+ break;
+ default:
+ ChangeValue("Combobox_SettingsMeasurementUnitsPreset", "");
+ break;
+ }
+ ChangeValue("Combobox_SettingsSpeedUnit", Subsystem.I18n.SpeedUnit);
+ switch(Subsystem.I18n.SpeedUnit) {
+ case "KilometerPerHour":
+ ChangeMax("Textbox_SettingsWindSpeed", "999");
+ ChangePlaceholder("Textbox_SettingsWindSpeed", "0~999");
+ ChangeTooltip("Textbox_SettingsWindSpeed", "0~999");
+ ChangeMax("Textbox_SettingsSpeedLimitMin", "981");
+ ChangePlaceholder("Textbox_SettingsSpeedLimitMin", "0~981");
+ ChangeTooltip("Textbox_SettingsSpeedLimitMin", "0~981");
+ ChangeMin("Textbox_SettingsSpeedLimitMaxOnFlapsUp", "15");
+ ChangeMax("Textbox_SettingsSpeedLimitMaxOnFlapsUp", "999");
+ ChangePlaceholder("Textbox_SettingsSpeedLimitMaxOnFlapsUp", "18~999");
+ ChangeTooltip("Textbox_SettingsSpeedLimitMaxOnFlapsUp", "18~999");
+ ChangeMin("Textbox_SettingsSpeedLimitMaxOnFlapsFull", "15");
+ ChangeMax("Textbox_SettingsSpeedLimitMaxOnFlapsFull", "999");
+ ChangePlaceholder("Textbox_SettingsSpeedLimitMaxOnFlapsFull", "18~999");
+ ChangeTooltip("Textbox_SettingsSpeedLimitMaxOnFlapsFull", "18~999");
+ break;
+ case "MilePerHour":
+ ChangeMax("Textbox_SettingsWindSpeed", "621");
+ ChangePlaceholder("Textbox_SettingsWindSpeed", "0~621");
+ ChangeTooltip("Textbox_SettingsWindSpeed", "0~621");
+ ChangeMax("Textbox_SettingsSpeedLimitMin", "610");
+ ChangePlaceholder("Textbox_SettingsSpeedLimitMin", "0~610");
+ ChangeTooltip("Textbox_SettingsSpeedLimitMin", "0~610");
+ ChangeMin("Textbox_SettingsSpeedLimitMaxOnFlapsUp", "10");
+ ChangeMax("Textbox_SettingsSpeedLimitMaxOnFlapsUp", "621");
+ ChangePlaceholder("Textbox_SettingsSpeedLimitMaxOnFlapsUp", "11~621");
+ ChangeTooltip("Textbox_SettingsSpeedLimitMaxOnFlapsUp", "11~621");
+ ChangeMin("Textbox_SettingsSpeedLimitMaxOnFlapsFull", "10");
+ ChangeMax("Textbox_SettingsSpeedLimitMaxOnFlapsFull", "621");
+ ChangePlaceholder("Textbox_SettingsSpeedLimitMaxOnFlapsFull", "11~621");
+ ChangeTooltip("Textbox_SettingsSpeedLimitMaxOnFlapsFull", "11~621");
+ break;
+ case "Knot":
+ ChangeMax("Textbox_SettingsWindSpeed", "539");
+ ChangePlaceholder("Textbox_SettingsWindSpeed", "0~539");
+ ChangeTooltip("Textbox_SettingsWindSpeed", "0~539");
+ ChangeMax("Textbox_SettingsSpeedLimitMin", "530");
+ ChangePlaceholder("Textbox_SettingsSpeedLimitMin", "0~530");
+ ChangeTooltip("Textbox_SettingsSpeedLimitMin", "0~530");
+ ChangeMin("Textbox_SettingsSpeedLimitMaxOnFlapsUp", "10");
+ ChangeMax("Textbox_SettingsSpeedLimitMaxOnFlapsUp", "539");
+ ChangePlaceholder("Textbox_SettingsSpeedLimitMaxOnFlapsUp", "10~539");
+ ChangeTooltip("Textbox_SettingsSpeedLimitMaxOnFlapsUp", "10~539");
+ ChangeMin("Textbox_SettingsSpeedLimitMaxOnFlapsFull", "10");
+ ChangeMax("Textbox_SettingsSpeedLimitMaxOnFlapsFull", "539");
+ ChangePlaceholder("Textbox_SettingsSpeedLimitMaxOnFlapsFull", "10~539");
+ ChangeTooltip("Textbox_SettingsSpeedLimitMaxOnFlapsFull", "10~539");
+ break;
+ default:
+ AlertSystemError("The value of Subsystem.I18n.SpeedUnit \"" + Subsystem.I18n.SpeedUnit + "\" in function RefreshSubsystem is invalid.");
+ break;
+ }
+ ChangeText("Label_SettingsWindSpeedUnit", Translate(Subsystem.I18n.SpeedUnit));
+ ChangeText("Label_SettingsSpeedLimitMinUnit", Translate(Subsystem.I18n.SpeedUnit));
+ ChangeText("Label_SettingsSpeedLimitOnFlapsUpUnit", Translate(Subsystem.I18n.SpeedUnit));
+ ChangeText("Label_SettingsSpeedLimitOnFlapsFullUnit", Translate(Subsystem.I18n.SpeedUnit));
+ ChangeValue("Combobox_SettingsDistanceUnit", Subsystem.I18n.DistanceUnit);
+ ChangeValue("Combobox_SettingsAltitudeUnit", Subsystem.I18n.AltitudeUnit);
+ switch(Subsystem.I18n.AltitudeUnit) {
+ case "Meter":
+ ChangeMin("Textbox_SettingsAirportElevationDeparture", "-500");
+ ChangeMax("Textbox_SettingsAirportElevationDeparture", "5000");
+ ChangeStep("Textbox_SettingsAirportElevationDeparture", "5");
+ ChangePlaceholder("Textbox_SettingsAirportElevationDeparture", "-500~5000");
+ ChangeTooltip("Textbox_SettingsAirportElevationDeparture", "-500~5000");
+ ChangeMin("Textbox_SettingsAirportElevationArrival", "-500");
+ ChangeMax("Textbox_SettingsAirportElevationArrival", "5000");
+ ChangeStep("Textbox_SettingsAirportElevationArrival", "5");
+ ChangePlaceholder("Textbox_SettingsAirportElevationArrival", "-500~5000");
+ ChangeTooltip("Textbox_SettingsAirportElevationArrival", "-500~5000");
+ ChangeMin("Textbox_SettingsDecisionHeight", "15");
+ ChangeMax("Textbox_SettingsDecisionHeight", "750");
+ ChangeStep("Textbox_SettingsDecisionHeight", "5");
+ ChangePlaceholder("Textbox_SettingsDecisionHeight", "15~750");
+ ChangeTooltip("Textbox_SettingsDecisionHeight", "15~750");
+ break;
+ case "Feet":
+ case "FeetButShowMeterBeside":
+ ChangeMin("Textbox_SettingsAirportElevationDeparture", "-1640");
+ ChangeMax("Textbox_SettingsAirportElevationDeparture", "16404");
+ ChangeStep("Textbox_SettingsAirportElevationDeparture", "10");
+ ChangePlaceholder("Textbox_SettingsAirportElevationDeparture", "-1640~16404");
+ ChangeTooltip("Textbox_SettingsAirportElevationDeparture", "-1640~16404");
+ ChangeMin("Textbox_SettingsAirportElevationArrival", "-1640");
+ ChangeMax("Textbox_SettingsAirportElevationArrival", "16404");
+ ChangeStep("Textbox_SettingsAirportElevationArrival", "10");
+ ChangePlaceholder("Textbox_SettingsAirportElevationArrival", "-1640~16404");
+ ChangeTooltip("Textbox_SettingsAirportElevationArrival", "-1640~16404");
+ ChangeMin("Textbox_SettingsDecisionHeight", "40");
+ ChangeMax("Textbox_SettingsDecisionHeight", "2461");
+ ChangeStep("Textbox_SettingsDecisionHeight", "10");
+ ChangePlaceholder("Textbox_SettingsDecisionHeight", "49~2461");
+ ChangeTooltip("Textbox_SettingsDecisionHeight", "49~2461");
+ break;
+ default:
+ AlertSystemError("The value of Subsystem.I18n.AltitudeUnit \"" + Subsystem.I18n.AltitudeUnit + "\" in function RefreshSubsystem is invalid.");
+ break;
+ }
+ ChangeText("Label_SettingsAirportElevationUnit", Translate(Subsystem.I18n.AltitudeUnit));
+ ChangeText("Label_SettingsDecisionHeightUnit", Translate(Subsystem.I18n.AltitudeUnit));
+ ChangeValue("Combobox_SettingsVerticalSpeedUnit", Subsystem.I18n.VerticalSpeedUnit);
+ ChangeValue("Combobox_SettingsTemperatureUnit", Subsystem.I18n.TemperatureUnit);
+ switch(Subsystem.I18n.TemperatureUnit) {
+ case "Celsius":
+ ChangeMin("Textbox_SettingsAirportTemperatureDeparture", "-50");
+ ChangeMax("Textbox_SettingsAirportTemperatureDeparture", "50");
+ ChangePlaceholder("Textbox_SettingsAirportTemperatureDeparture", "-50~50");
+ ChangeTooltip("Textbox_SettingsAirportTemperatureDeparture", "-50~50");
+ ChangeMin("Textbox_SettingsAirportTemperatureArrival", "-50");
+ ChangeMax("Textbox_SettingsAirportTemperatureArrival", "50");
+ ChangePlaceholder("Textbox_SettingsAirportTemperatureArrival", "-50~50");
+ ChangeTooltip("Textbox_SettingsAirportTemperatureArrival", "-50~50");
+ break;
+ case "Fahrenheit":
+ ChangeMin("Textbox_SettingsAirportTemperatureDeparture", "-58");
+ ChangeMax("Textbox_SettingsAirportTemperatureDeparture", "122");
+ ChangePlaceholder("Textbox_SettingsAirportTemperatureDeparture", "-58~122");
+ ChangeTooltip("Textbox_SettingsAirportTemperatureDeparture", "-58~122");
+ ChangeMin("Textbox_SettingsAirportTemperatureArrival", "-58");
+ ChangeMax("Textbox_SettingsAirportTemperatureArrival", "122");
+ ChangePlaceholder("Textbox_SettingsAirportTemperatureArrival", "-58~122");
+ ChangeTooltip("Textbox_SettingsAirportTemperatureArrival", "-58~122");
+ break;
+ default:
+ AlertSystemError("The value of Subsystem.I18n.TemperatureUnit \"" + Subsystem.I18n.TemperatureUnit + "\" in function RefreshSubsystem is invalid.");
+ break;
+ }
+ ChangeText("Label_SettingsAirportTemperatureUnit", Translate(Subsystem.I18n.TemperatureUnit));
+ ChangeValue("Combobox_SettingsPressureUnit", Subsystem.I18n.PressureUnit);
+ switch(Subsystem.I18n.PressureUnit) {
+ case "Hectopascal":
+ ChangeMin("Textbox_SettingsQNHDeparture", "900");
+ ChangeMax("Textbox_SettingsQNHDeparture", "1100");
+ ChangeStep("Textbox_SettingsQNHDeparture", "1");
+ ChangePlaceholder("Textbox_SettingsQNHDeparture", "900~1100");
+ ChangeTooltip("Textbox_SettingsQNHDeparture", "900~1100");
+ ChangeMin("Textbox_SettingsQNHArrival", "900");
+ ChangeMax("Textbox_SettingsQNHArrival", "1100");
+ ChangeStep("Textbox_SettingsQNHArrival", "1");
+ ChangePlaceholder("Textbox_SettingsQNHArrival", "900~1100");
+ ChangeTooltip("Textbox_SettingsQNHArrival", "900~1100");
+ break;
+ case "InchOfMercury":
+ ChangeMin("Textbox_SettingsQNHDeparture", "26.58");
+ ChangeMax("Textbox_SettingsQNHDeparture", "32.48");
+ ChangeStep("Textbox_SettingsQNHDeparture", "0.01");
+ ChangePlaceholder("Textbox_SettingsQNHDeparture", "26.58~32.48");
+ ChangeTooltip("Textbox_SettingsQNHDeparture", "26.58~32.48");
+ ChangeMin("Textbox_SettingsQNHArrival", "26.58");
+ ChangeMax("Textbox_SettingsQNHArrival", "32.48");
+ ChangeStep("Textbox_SettingsQNHArrival", "0.01");
+ ChangePlaceholder("Textbox_SettingsQNHArrival", "26.58~32.48");
+ ChangeTooltip("Textbox_SettingsQNHArrival", "26.58~32.48");
+ break;
+ default:
+ AlertSystemError("The value of Subsystem.I18n.PressureUnit \"" + Subsystem.I18n.PressureUnit + "\" in function RefreshSubsystem is invalid.");
+ break;
+ }
+ ChangeText("Label_SettingsQNHUnit", Translate(Subsystem.I18n.PressureUnit));
+
+ // Save user data
+ localStorage.setItem("GPSPFD_Subsystem", JSON.stringify(Subsystem));
+ }
+
+ // PFD
+ function ClockPFD() {
+ // Update essentials
+ PFD0.Stats.ClockTime = Date.now();
+
+ // Call
+ RefreshGPSStatus();
+ RefreshAccelStatus();
+ RefreshPFDData();
+ RefreshScale();
+ RefreshPanel();
+ RefreshPFDAudio();
+ RefreshTechInfo();
+
+ // Update previous variables
+ PFD0.Stats.PreviousClockTime = PFD0.Stats.ClockTime;
+ PFD0.Stats.Speed.PreviousTapeDisplay = PFD0.Stats.Speed.TapeDisplay;
+ PFD0.Stats.Altitude.PreviousTapeDisplay = PFD0.Stats.Altitude.TapeDisplay;
+ }
+ // Sub-functions
+ function RefreshGPSStatus() {
+ PFD0.Status.GPS = {
+ IsPositionAvailable: false, IsPositionAccurate: false,
+ IsSpeedAvailable: false, IsAltitudeAvailable: false, IsAltitudeAccurate: false, IsHeadingAvailable: false
+ };
+ if(PFD0.Stats.ClockTime - PFD0.RawData.GPS.Timestamp < 3000) {
+ if(PFD0.RawData.GPS.Position.Lat != null && PFD0.RawData.GPS.Position.Lon != null) {
+ PFD0.Status.GPS.IsPositionAvailable = true;
+ if(PFD0.RawData.GPS.Position.Accuracy <= 10) {
+ PFD0.Status.GPS.IsPositionAccurate = true;
+ }
+ }
+ if(PFD0.RawData.GPS.Speed != null) {
+ PFD0.Status.GPS.IsSpeedAvailable = true;
+ }
+ if(PFD0.RawData.GPS.Altitude.Altitude != null) {
+ PFD0.Status.GPS.IsAltitudeAvailable = true;
+ if(PFD0.RawData.GPS.Altitude.Accuracy <= 20) {
+ PFD0.Status.GPS.IsAltitudeAccurate = true;
+ }
+ }
+ if(PFD0.RawData.GPS.Heading != null) {
+ PFD0.Status.GPS.IsHeadingAvailable = true;
+ }
+ }
+ }
+ function RefreshAccelStatus() {
+ if(PFD0.Stats.ClockTime - PFD0.RawData.Accel.Timestamp < 1000 &&
+ PFD0.RawData.Accel.Accel.Absolute.X != null && PFD0.RawData.Accel.Accel.AbsoluteWithGravity.X != null) {
+ PFD0.Status.IsAccelAvailable = true;
+ } else {
+ PFD0.Status.IsAccelAvailable = false;
+ }
+ }
+ function RefreshPFDData() {
+ // Attitude
+ if(PFD.Attitude.IsEnabled == true) {
+ switch(true) {
+ case PFD.Attitude.Mode == "Accel" && PFD0.Status.IsAccelAvailable == true:
+ PFD0.Stats.Attitude.Pitch = PFD0.RawData.Accel.Attitude.Aligned.Pitch;
+ PFD0.Stats.Attitude.Roll = PFD0.RawData.Accel.Attitude.Aligned.Roll;
+ break;
+ case PFD.Attitude.Mode == "Manual":
+ PFD0.Stats.Attitude.Pitch = PFD0.RawData.Manual.Attitude.Pitch;
+ PFD0.Stats.Attitude.Roll = PFD0.RawData.Manual.Attitude.Roll;
+ break;
+ default:
+ break;
+ }
+ if(PFD0.Stats.Attitude.Pitch < -90) {
+ PFD0.Stats.Attitude.Pitch = -90;
+ }
+ if(PFD0.Stats.Attitude.Pitch > 90) {
+ PFD0.Stats.Attitude.Pitch = 90;
+ }
+ PFD0.Stats.Attitude.Pitch2 = PFD0.Stats.Attitude.Pitch;
+ if(PFD0.Stats.Attitude.Pitch2 < -20) {
+ PFD0.Stats.Attitude.Pitch2 = -20;
+ }
+ if(PFD0.Stats.Attitude.Pitch2 > 20) {
+ PFD0.Stats.Attitude.Pitch2 = 20;
+ }
+ if(PFD0.Stats.Attitude.Roll < -180) {
+ PFD0.Stats.Attitude.Roll += 360;
+ }
+ if(PFD0.Stats.Attitude.Roll > 180) {
+ PFD0.Stats.Attitude.Roll -= 360;
+ }
+ }
+
+ // Altitude (Updated before speed because speed data relies on altitude variation)
+ switch(true) {
+ case PFD.Speed.Mode == "GPS" && PFD0.Status.GPS.IsAltitudeAvailable == true:
+ PFD0.Stats.Altitude.Altitude = PFD0.RawData.GPS.Altitude.Altitude;
+ break;
+ case PFD.Altitude.Mode == "Accel" && PFD0.Status.IsAccelAvailable == true:
+ case PFD.Altitude.Mode == "DualChannel" && (PFD0.Status.GPS.IsAltitudeAvailable == true || PFD0.Status.IsAccelAvailable == true):
+ PFD0.Stats.Altitude.Altitude = PFD0.RawData.Accel.Altitude;
+ break;
+ case PFD.Altitude.Mode == "Manual":
+ PFD0.Stats.Altitude.Altitude = PFD0.RawData.Manual.Altitude;
+ break;
+ default:
+ break;
+ }
+ // Tape
+ PFD0.Stats.Altitude.TapeDisplay += (PFD0.Stats.Altitude.Altitude - PFD0.Stats.Altitude.TapeDisplay) / 50 * ((PFD0.Stats.ClockTime - PFD0.Stats.PreviousClockTime) / 30); // Use "ClockTime" here for smoother trend displaying.
+ if(PFD0.Stats.Altitude.TapeDisplay < -609.5) { // It should have been -609.6 meters. But -609.59999 can be converted to -2000.00001 feet, resulting in a display error on the balloon.
+ PFD0.Stats.Altitude.TapeDisplay = -609.5;
+ }
+ if(PFD0.Stats.Altitude.TapeDisplay > 15239.9) { // It should have been 15240 meters. Reason same as above.
+ PFD0.Stats.Altitude.TapeDisplay = 15239.9;
+ }
+
+ // Trend
+ PFD0.Stats.Altitude.Trend = (PFD0.Stats.Altitude.TapeDisplay - PFD0.Stats.Altitude.PreviousTapeDisplay) * (6000 / Math.max(PFD0.Stats.ClockTime - PFD0.Stats.PreviousClockTime, 1)); // (1) Altitude trend shows target altitude in 6 sec. (2) If refreshed too frequently, the divisor may become zero. So "Math.max" is applied here.
+ PFD0.Stats.Altitude.TrendDisplay += (PFD0.Stats.Altitude.Trend - PFD0.Stats.Altitude.TrendDisplay) / 5;
+ PFD0.Stats.Speed.Vertical = PFD0.Stats.Altitude.TrendDisplay / 6;
+
+ // Balloon
+ PFD0.Stats.Altitude.BalloonDisplay[1] = Math.trunc(ConvertUnit(PFD0.Stats.Altitude.TapeDisplay, "Meter", Subsystem.I18n.AltitudeUnit) / 10000);
+ PFD0.Stats.Altitude.BalloonDisplay[2] = Math.trunc(ConvertUnit(PFD0.Stats.Altitude.TapeDisplay, "Meter", Subsystem.I18n.AltitudeUnit) % 10000 / 1000);
+ PFD0.Stats.Altitude.BalloonDisplay[3] = Math.trunc(ConvertUnit(PFD0.Stats.Altitude.TapeDisplay, "Meter", Subsystem.I18n.AltitudeUnit) % 1000 / 100);
+ PFD0.Stats.Altitude.BalloonDisplay[4] = ConvertUnit(PFD0.Stats.Altitude.TapeDisplay, "Meter", Subsystem.I18n.AltitudeUnit) % 100;
+ if(System.Display.Anim > 0) {
+ if(PFD0.Stats.Altitude.BalloonDisplay[4] > 80) {PFD0.Stats.Altitude.BalloonDisplay[3] += ((PFD0.Stats.Altitude.BalloonDisplay[4] - 80) / 20);} // Imitating the cockpit PFD rolling digits.
+ if(PFD0.Stats.Altitude.BalloonDisplay[4] < -80) {PFD0.Stats.Altitude.BalloonDisplay[3] += ((PFD0.Stats.Altitude.BalloonDisplay[4] + 80) / 20);}
+ if(PFD0.Stats.Altitude.BalloonDisplay[3] > 9) {PFD0.Stats.Altitude.BalloonDisplay[2] += (PFD0.Stats.Altitude.BalloonDisplay[3] - 9);}
+ if(PFD0.Stats.Altitude.BalloonDisplay[3] < -9) {PFD0.Stats.Altitude.BalloonDisplay[2] += (PFD0.Stats.Altitude.BalloonDisplay[3] + 9);}
+ if(PFD0.Stats.Altitude.BalloonDisplay[2] > 9) {PFD0.Stats.Altitude.BalloonDisplay[1] += (PFD0.Stats.Altitude.BalloonDisplay[2] - 9);}
+ if(PFD0.Stats.Altitude.BalloonDisplay[2] < -9) {PFD0.Stats.Altitude.BalloonDisplay[1] += (PFD0.Stats.Altitude.BalloonDisplay[2] + 9);}
+ } else {
+ PFD0.Stats.Altitude.BalloonDisplay[4] = Math.trunc(PFD0.Stats.Altitude.BalloonDisplay[4] / 20) * 20;
+ }
+
+ // Heading (Updated before speed because speed data relies on heading)
+ if(PFD0.Status.GPS.IsHeadingAvailable == true) {
+ PFD0.Stats.Heading.Heading = PFD0.RawData.GPS.Heading;
+ }
+ PFD0.Stats.Heading.Display += (PFD0.Stats.Heading.Heading - PFD0.Stats.Heading.Display) / 5;
+
+ // Speed
+ switch(true) {
+ case PFD.Speed.Mode == "GPS" && PFD0.Status.GPS.IsSpeedAvailable == true:
+ PFD0.Stats.Speed.Speed = PFD0.RawData.GPS.Speed;
+ break;
+ case PFD.Speed.Mode == "Accel" && PFD0.Status.IsAccelAvailable == true:
+ case PFD.Speed.Mode == "DualChannel" && (PFD0.Status.GPS.IsSpeedAvailable == true || PFD0.Status.IsAccelAvailable == true):
+ PFD0.Stats.Speed.Speed = PFD0.RawData.Accel.Speed.Speed;
+ break;
+ case PFD.Speed.Mode == "Manual":
+ PFD0.Stats.Speed.Speed = PFD0.RawData.Manual.Speed;
+ break;
+ default:
+ break;
+ }
+ // Pitch
+ if(PFD0.Stats.Speed.Speed > 0) {
+ let Calc = PFD0.Stats.Speed.Vertical / PFD0.Stats.Speed.Speed;
+ if(Calc < -1) {
+ Calc = -1;
+ }
+ if(Calc > 1) {
+ Calc = 1;
+ }
+ PFD0.Stats.Speed.Pitch = Math.asin(Calc) / (Math.PI / 180);
+ } else {
+ PFD0.Stats.Speed.Pitch = 90;
+ }
+
+ // GS
+ PFD0.Stats.Speed.GS = Math.sqrt(Math.max(Math.pow(PFD0.Stats.Speed.Speed, 2) - Math.pow(PFD0.Stats.Speed.Vertical, 2), 0));
+ PFD0.Stats.Speed.GSDisplay += (PFD0.Stats.Speed.GS - PFD0.Stats.Speed.GSDisplay) / 50;
+
+ // TAS
+ if(PFD0.Status.GPS.IsHeadingAvailable == true) {
+ PFD0.Stats.Speed.Wind.Heading = PFD.Speed.Wind.Direction + 180;
+ if(PFD0.Stats.Speed.Wind.Heading >= 360) {
+ PFD0.Stats.Speed.Wind.Heading -= 360;
+ }
+ PFD0.Stats.Speed.Wind.RelativeHeading = PFD0.Stats.Speed.Wind.Heading - PFD0.Stats.Heading.Display;
+ PFD0.Stats.Speed.TAS = CalcTAS(PFD0.Stats.Speed.GS, PFD0.Stats.Speed.Wind.RelativeHeading, PFD.Speed.Wind.Speed, PFD0.Stats.Speed.Vertical);
+ } else {
+ PFD0.Stats.Speed.TAS = CalcTAS(PFD0.Stats.Speed.GS, null, null, PFD0.Stats.Speed.Vertical);
+ }
+ PFD0.Stats.Speed.TASDisplay += (PFD0.Stats.Speed.TAS - PFD0.Stats.Speed.TASDisplay) / 50;
+
+ // IAS
+ switch(PFD.FlightMode.FlightMode) {
+ case "DepartureGround":
+ case "TakeOff":
+ case "EmergencyReturn":
+ PFD0.Stats.Speed.IAS = CalcIAS(PFD.Speed.IASAlgorithm, PFD0.Stats.Speed.TAS, PFD0.Stats.Altitude.TapeDisplay,
+ PFD.Altitude.AirportElevation.Departure, PFD.Speed.AirportTemperature.Departure, PFD.Speed.RelativeHumidity.Departure, PFD.Speed.QNH.Departure,
+ PFD.Attitude.IsEnabled, Math.abs(PFD0.Stats.Attitude.Pitch - PFD0.Stats.Speed.Pitch));
+ break;
+ case "Cruise":
+ case "Land":
+ case "ArrivalGround":
+ PFD0.Stats.Speed.IAS = CalcIAS(PFD.Speed.IASAlgorithm, PFD0.Stats.Speed.TAS, PFD0.Stats.Altitude.TapeDisplay,
+ PFD.Altitude.AirportElevation.Arrival, PFD.Speed.AirportTemperature.Arrival, PFD.Speed.RelativeHumidity.Arrival, PFD.Speed.QNH.Arrival,
+ PFD.Attitude.IsEnabled, Math.abs(PFD0.Stats.Attitude.Pitch - PFD0.Stats.Speed.Pitch));
+ break;
+ default:
+ AlertSystemError("The value of PFD.FlightMode.FlightMode \"" + PFD.FlightMode.FlightMode + "\" in function RefreshPFDData is invalid.");
+ break;
+ }
+ // Tape
+ PFD0.Stats.Speed.TapeDisplay += (PFD0.Stats.Speed.IAS - PFD0.Stats.Speed.TapeDisplay) / 50 * ((PFD0.Stats.ClockTime - PFD0.Stats.PreviousClockTime) / 30);
+ if(PFD0.Stats.Speed.TapeDisplay < 0) {
+ PFD0.Stats.Speed.TapeDisplay = 0;
+ }
+ if(PFD0.Stats.Speed.TapeDisplay > 277.5) {
+ PFD0.Stats.Speed.TapeDisplay = 277.5;
+ }
+
+ // Trend
+ PFD0.Stats.Speed.Trend = (PFD0.Stats.Speed.TapeDisplay - PFD0.Stats.Speed.PreviousTapeDisplay) * (10000 / Math.max(PFD0.Stats.ClockTime - PFD0.Stats.PreviousClockTime, 1)); // Speed trend shows target speed in 10 sec.
+ PFD0.Stats.Speed.TrendDisplay += (PFD0.Stats.Speed.Trend - PFD0.Stats.Speed.TrendDisplay) / 5;
+
+ // Balloon
+ PFD0.Stats.Speed.BalloonDisplay[1] = Math.trunc(ConvertUnit(PFD0.Stats.Speed.TapeDisplay, "MeterPerSec", Subsystem.I18n.SpeedUnit) / 100);
+ PFD0.Stats.Speed.BalloonDisplay[2] = Math.trunc(ConvertUnit(PFD0.Stats.Speed.TapeDisplay, "MeterPerSec", Subsystem.I18n.SpeedUnit) % 100 / 10);
+ PFD0.Stats.Speed.BalloonDisplay[3] = ConvertUnit(PFD0.Stats.Speed.TapeDisplay, "MeterPerSec", Subsystem.I18n.SpeedUnit) % 10;
+ if(System.Display.Anim > 0) {
+ if(PFD0.Stats.Speed.BalloonDisplay[3] > 9) {PFD0.Stats.Speed.BalloonDisplay[2] += (PFD0.Stats.Speed.BalloonDisplay[3] - 9);}
+ if(PFD0.Stats.Speed.BalloonDisplay[2] > 9) {PFD0.Stats.Speed.BalloonDisplay[1] += (PFD0.Stats.Speed.BalloonDisplay[2] - 9);}
+ } else {
+ PFD0.Stats.Speed.BalloonDisplay[3] = Math.trunc(PFD0.Stats.Speed.BalloonDisplay[3]);
+ }
+
+ // Mach number
+ switch(PFD.FlightMode.FlightMode) {
+ case "DepartureGround":
+ case "TakeOff":
+ case "EmergencyReturn":
+ PFD0.Stats.Speed.MachNumber = CalcMachNumber(PFD0.Stats.Speed.TASDisplay, PFD0.Stats.Altitude.TapeDisplay, PFD.Altitude.AirportElevation.Departure, PFD.Speed.AirportTemperature.Departure);
+ break;
+ case "Cruise":
+ case "Land":
+ case "ArrivalGround":
+ PFD0.Stats.Speed.MachNumber = CalcMachNumber(PFD0.Stats.Speed.TASDisplay, PFD0.Stats.Altitude.TapeDisplay, PFD.Altitude.AirportElevation.Arrival, PFD.Speed.AirportTemperature.Arrival);
+ break;
+ default:
+ AlertSystemError("The value of PFD.FlightMode.FlightMode \"" + PFD.FlightMode.FlightMode + "\" in function RefreshPFDData is invalid.");
+ break;
+ }
+
+ // DME
+ if(PFD.DME.IsEnabled == true && PFD0.Status.GPS.IsPositionAvailable == true) {
+ PFD0.Stats.DME.Lat = PFD0.RawData.GPS.Position.Lat;
+ PFD0.Stats.DME.Lon = PFD0.RawData.GPS.Position.Lon;
+ switch(PFD.FlightMode.FlightMode) {
+ case "DepartureGround":
+ case "TakeOff":
+ case "EmergencyReturn":
+ PFD0.Stats.DME.Distance = CalcDistance(PFD0.Stats.DME.Lat, PFD0.Stats.DME.Lon, PFD.DME.AirportCoordinates.Departure.Lat, PFD.DME.AirportCoordinates.Departure.Lon);
+ break;
+ case "Cruise":
+ case "Land":
+ case "ArrivalGround":
+ PFD0.Stats.DME.Distance = CalcDistance(PFD0.Stats.DME.Lat, PFD0.Stats.DME.Lon, PFD.DME.AirportCoordinates.Arrival.Lat, PFD.DME.AirportCoordinates.Arrival.Lon);
+ break;
+ default:
+ AlertSystemError("The value of PFD.FlightMode.FlightMode \"" + PFD.FlightMode.FlightMode + "\" in function RefreshPFDData is invalid.");
+ break;
+ }
+ if(PFD0.Stats.Speed.GSDisplay > 0) {
+ PFD0.Stats.DME.ETA = PFD0.Stats.DME.Distance / PFD0.Stats.Speed.GSDisplay * 1000; // (Meter / meter per sec) = sec, sec * 1000 = millisec.
+ }
+ }
+
+ // Radio altitude
+ switch(PFD.FlightMode.FlightMode) {
+ case "DepartureGround":
+ case "TakeOff":
+ case "EmergencyReturn":
+ PFD0.Stats.Altitude.RadioDisplay = PFD0.Stats.Altitude.TapeDisplay - PFD.Altitude.AirportElevation.Departure;
+ break;
+ case "Cruise":
+ case "Land":
+ case "ArrivalGround":
+ PFD0.Stats.Altitude.RadioDisplay = PFD0.Stats.Altitude.TapeDisplay - PFD.Altitude.AirportElevation.Arrival;
+ break;
+ default:
+ AlertSystemError("The value of PFD.FlightMode.FlightMode \"" + PFD.FlightMode.FlightMode + "\" in function RefreshPFDData is invalid.");
+ break;
+ }
+
+ // Decision altitude
+ switch(PFD.FlightMode.FlightMode) {
+ case "DepartureGround":
+ case "TakeOff":
+ case "Cruise":
+ case "ArrivalGround":
+ PFD0.Status.IsDecisionAltitudeActive = false;
+ break;
+ case "Land":
+ if(PFD0.Stats.Altitude.TapeDisplay <= PFD.Altitude.AirportElevation.Arrival + PFD.Altitude.DecisionHeight) {
+ PFD0.Status.IsDecisionAltitudeActive = true;
+ if(PFD0.Stats.Altitude.PreviousTapeDisplay > PFD.Altitude.AirportElevation.Arrival + PFD.Altitude.DecisionHeight) {
+ PFD0.Stats.Altitude.DecisionTimestamp = PFD0.Stats.ClockTime;
+ }
+ }
+ break;
+ case "EmergencyReturn":
+ if(PFD0.Stats.Altitude.TapeDisplay <= PFD.Altitude.AirportElevation.Departure + PFD.Altitude.DecisionHeight) {
+ PFD0.Status.IsDecisionAltitudeActive = true;
+ if(PFD0.Stats.Altitude.PreviousTapeDisplay > PFD.Altitude.AirportElevation.Departure + PFD.Altitude.DecisionHeight) {
+ PFD0.Stats.Altitude.DecisionTimestamp = PFD0.Stats.ClockTime;
+ }
+ }
+ break;
+ default:
+ AlertSystemError("The value of PFD.FlightMode.FlightMode \"" + PFD.FlightMode.FlightMode + "\" in function RefreshPFDData is invalid.");
+ break;
+ }
+
+ // Flight mode auto switch & airport data auto swap
+ if(PFD.FlightMode.AutoSwitchFlightModeAndSwapAirportData == true) {
+ switch(PFD.FlightMode.FlightMode) {
+ case "DepartureGround":
+ if(PFD0.Stats.Speed.TapeDisplay >= 41.152 &&
+ PFD0.Stats.Altitude.TapeDisplay - PFD.Altitude.AirportElevation.Departure >= 9.144 &&
+ PFD0.Stats.Altitude.PreviousTapeDisplay - PFD.Altitude.AirportElevation.Departure < 9.144) {
+ PFD.FlightMode.FlightMode = "TakeOff";
+ PFD0.Stats.FlightModeTimestamp = PFD0.Stats.ClockTime;
+ setTimeout(RefreshPFD, 0);
+ }
+ break;
+ case "TakeOff":
+ if(PFD0.Stats.Altitude.TapeDisplay - PFD.Altitude.AirportElevation.Departure >= 914.4 &&
+ PFD0.Stats.Altitude.PreviousTapeDisplay - PFD.Altitude.AirportElevation.Departure < 914.4) {
+ PFD.FlightMode.FlightMode = "Cruise";
+ PFD0.Stats.FlightModeTimestamp = PFD0.Stats.ClockTime;
+ setTimeout(RefreshPFD, 0);
+ }
+ break;
+ case "Cruise":
+ if(PFD0.Stats.Altitude.TapeDisplay - PFD.Altitude.AirportElevation.Arrival <= 914.4 &&
+ PFD0.Stats.Altitude.PreviousTapeDisplay - PFD.Altitude.AirportElevation.Arrival > 914.4) {
+ PFD.FlightMode.FlightMode = "Land";
+ PFD0.Stats.FlightModeTimestamp = PFD0.Stats.ClockTime;
+ setTimeout(RefreshPFD, 0);
+ }
+ break;
+ case "Land":
+ if(PFD0.Stats.Altitude.TapeDisplay - PFD.Altitude.AirportElevation.Arrival <= 9.144 &&
+ PFD0.Stats.Altitude.PreviousTapeDisplay - PFD.Altitude.AirportElevation.Arrival > 9.144) {
+ PFD.FlightMode.FlightMode = "ArrivalGround";
+ PFD0.Stats.FlightModeTimestamp = PFD0.Stats.ClockTime;
+ setTimeout(RefreshPFD, 0);
+ }
+ break;
+ case "ArrivalGround":
+ if(PFD0.Stats.Speed.TapeDisplay <= 2.572 && PFD0.Stats.Speed.PreviousTapeDisplay > 2.572) {
+ PFD.FlightMode.FlightMode = "DepartureGround";
+ SwapAirportTemperatures();
+ SwapRelativeHumidity();
+ SwapQNHs();
+ SwapAirportElevations();
+ SwapAirportCoordinates();
+ PFD0.Stats.FlightModeTimestamp = PFD0.Stats.ClockTime;
+ setTimeout(RefreshPFD, 0);
+ }
+ break;
+ case "EmergencyReturn":
+ if(PFD0.Stats.Altitude.TapeDisplay - PFD.Altitude.AirportElevation.Departure <= 9.144 &&
+ PFD0.Stats.Altitude.PreviousTapeDisplay - PFD.Altitude.AirportElevation.Departure > 9.144) {
+ PFD.FlightMode.FlightMode = "DepartureGround";
+ PFD0.Stats.FlightModeTimestamp = PFD0.Stats.ClockTime;
+ setTimeout(RefreshPFD, 0);
+ }
+ break;
+ default:
+ AlertSystemError("The value of PFD.FlightMode.FlightMode \"" + PFD.FlightMode.FlightMode + "\" in function RefreshPFDData is invalid.");
+ break;
+ }
+ }
+
+ // Alerts
+ // Initialize
+ PFD0.Alert.Active = {
+ AttitudeWarning: "", SpeedWarning: "", AltitudeCallout: "", AltitudeWarning: ""
+ };
+
+ // Attitude warning
+ if(PFD.Attitude.IsEnabled == true) {
+ if((PFD.Attitude.Mode == "Accel" && PFD0.Status.IsAccelAvailable == true) ||
+ PFD.Attitude.Mode == "Manual") {
+ if(IsExcessiveBankAngle() == true) {
+ PFD0.Alert.Active.AttitudeWarning = "BankAngle";
+ }
+ }
+ }
+
+ // Speed warning
+ if((PFD.Speed.Mode == "GPS" && PFD0.Status.GPS.IsSpeedAvailable == true) ||
+ (PFD.Speed.Mode == "Accel" && PFD0.Status.IsAccelAvailable == true) ||
+ (PFD.Speed.Mode == "DualChannel" && (PFD0.Status.GPS.IsSpeedAvailable == true || PFD0.Status.IsAccelAvailable == true)) ||
+ PFD.Speed.Mode == "Manual") {
+ if(IsAirspeedLow() == true) {
+ PFD0.Alert.Active.SpeedWarning = "AirspeedLow";
+ }
+ if(IsOverspeed() == true) {
+ PFD0.Alert.Active.SpeedWarning = "Overspeed";
+ }
+ }
+
+ // Altitude callout
+ if((PFD.Altitude.Mode == "GPS" && PFD0.Status.GPS.IsAltitudeAvailable == true) ||
+ (PFD.Altitude.Mode == "Accel" && PFD0.Status.IsAccelAvailable == true) ||
+ (PFD.Altitude.Mode == "DualChannel" && (PFD0.Status.GPS.IsAltitudeAvailable == true || PFD0.Status.IsAccelAvailable == true)) ||
+ PFD.Altitude.Mode == "Manual") {
+ switch(PFD.FlightMode.FlightMode) {
+ case "DepartureGround":
+ case "Land":
+ case "ArrivalGround":
+ case "EmergencyReturn":
+ let ConvertedRadioAltitude = 0, ConvertedPreviousRadioAltitude = 0, ConvertedDecisionHeight = 0;
+ switch(PFD.FlightMode.FlightMode) {
+ case "DepartureGround":
+ case "EmergencyReturn":
+ ConvertedRadioAltitude = ConvertUnit(PFD0.Stats.Altitude.TapeDisplay - PFD.Altitude.AirportElevation.Departure, "Meter", Subsystem.I18n.AltitudeUnit);
+ ConvertedPreviousRadioAltitude = ConvertUnit(PFD0.Stats.Altitude.PreviousTapeDisplay - PFD.Altitude.AirportElevation.Departure, "Meter", Subsystem.I18n.AltitudeUnit);
+ break;
+ case "Land":
+ case "ArrivalGround":
+ ConvertedRadioAltitude = ConvertUnit(PFD0.Stats.Altitude.TapeDisplay - PFD.Altitude.AirportElevation.Arrival, "Meter", Subsystem.I18n.AltitudeUnit);
+ ConvertedPreviousRadioAltitude = ConvertUnit(PFD0.Stats.Altitude.PreviousTapeDisplay - PFD.Altitude.AirportElevation.Arrival, "Meter", Subsystem.I18n.AltitudeUnit);
+ break;
+ default:
+ AlertSystemError("The value of PFD.FlightMode.FlightMode \"" + PFD.FlightMode.FlightMode + "\" in function RefreshPFDData is invalid.");
+ break;
+ }
+ ConvertedDecisionHeight = ConvertUnit(PFD.Altitude.DecisionHeight, "Meter", Subsystem.I18n.AltitudeUnit);
+ if(ConvertedRadioAltitude <= 2500 && ConvertedPreviousRadioAltitude > 2500) {
+ PFD0.Alert.Active.AltitudeCallout = "2500";
+ }
+ if(ConvertedRadioAltitude <= 1000 && ConvertedPreviousRadioAltitude > 1000) {
+ PFD0.Alert.Active.AltitudeCallout = "1000";
+ }
+ if(ConvertedRadioAltitude <= 500 && ConvertedPreviousRadioAltitude > 500) {
+ PFD0.Alert.Active.AltitudeCallout = "500";
+ }
+ if(ConvertedRadioAltitude <= 400 && ConvertedPreviousRadioAltitude > 400) {
+ PFD0.Alert.Active.AltitudeCallout = "400";
+ }
+ if(ConvertedRadioAltitude <= 300 && ConvertedPreviousRadioAltitude > 300) {
+ PFD0.Alert.Active.AltitudeCallout = "300";
+ }
+ if(ConvertedRadioAltitude <= 200 && ConvertedPreviousRadioAltitude > 200) {
+ PFD0.Alert.Active.AltitudeCallout = "200";
+ }
+ if(ConvertedRadioAltitude <= 100 && ConvertedPreviousRadioAltitude > 100) {
+ PFD0.Alert.Active.AltitudeCallout = "100";
+ }
+ if(ConvertedRadioAltitude <= 50 && ConvertedPreviousRadioAltitude > 50) {
+ PFD0.Alert.Active.AltitudeCallout = "50";
+ }
+ if(ConvertedRadioAltitude <= 40 && ConvertedPreviousRadioAltitude > 40) {
+ PFD0.Alert.Active.AltitudeCallout = "40";
+ }
+ if(ConvertedRadioAltitude <= 30 && ConvertedPreviousRadioAltitude > 30) {
+ PFD0.Alert.Active.AltitudeCallout = "30";
+ }
+ if(ConvertedRadioAltitude <= 20 && ConvertedPreviousRadioAltitude > 20) {
+ PFD0.Alert.Active.AltitudeCallout = "20";
+ }
+ if(ConvertedRadioAltitude <= 10 && ConvertedPreviousRadioAltitude > 10) {
+ PFD0.Alert.Active.AltitudeCallout = "10";
+ }
+ if(ConvertedRadioAltitude <= (ConvertedDecisionHeight + 100) && ConvertedPreviousRadioAltitude > (ConvertedDecisionHeight + 100)) {
+ PFD0.Alert.Active.AltitudeCallout = "HundredAbove";
+ }
+ if(ConvertedRadioAltitude <= (ConvertedDecisionHeight + 80) && ConvertedPreviousRadioAltitude > (ConvertedDecisionHeight + 80)) {
+ PFD0.Alert.Active.AltitudeCallout = "ApproachingMinimums";
+ }
+ if(ConvertedRadioAltitude <= ConvertedDecisionHeight && ConvertedPreviousRadioAltitude > ConvertedDecisionHeight) {
+ PFD0.Alert.Active.AltitudeCallout = "Minimums";
+ }
+ if(ConvertedRadioAltitude <= 15 && ConvertedPreviousRadioAltitude > 15) {
+ PFD0.Alert.Active.AltitudeCallout = "Retard";
+ }
+ break;
+ case "TakeOff":
+ case "Cruise":
+ break;
+ default:
+ AlertSystemError("The value of PFD.FlightMode.FlightMode \"" + PFD.FlightMode.FlightMode + "\" in function RefreshPFDData is invalid.");
+ break;
+ }
+ }
+
+ // Altitude warning
+ if((PFD.Altitude.Mode == "GPS" && PFD0.Status.GPS.IsAltitudeAvailable == true) ||
+ (PFD.Altitude.Mode == "Accel" && PFD0.Status.IsAccelAvailable == true) ||
+ (PFD.Altitude.Mode == "DualChannel" && (PFD0.Status.GPS.IsAltitudeAvailable == true || PFD0.Status.IsAccelAvailable == true)) ||
+ PFD.Altitude.Mode == "Manual") {
+ if(IsDontSink() == true) {
+ PFD0.Alert.Active.AltitudeWarning = "DontSink";
+ }
+ if(PFD.DME.IsEnabled == true && PFD0.Status.GPS.IsPositionAvailable == true) {
+ if(IsExcessivelyBelowGlideSlope() == true) {
+ PFD0.Alert.Active.AltitudeWarning = "GlideSlope";
+ }
+ }
+ if(IsSinkRate() == true) {
+ PFD0.Alert.Active.AltitudeWarning = "SinkRate";
+ }
+ if(IsSinkRatePullUp() == true) {
+ PFD0.Alert.Active.AltitudeWarning = "PullUp";
+ }
+ }
+ }
+ function RefreshScale() {
+ let Elements = document.getElementsByClassName("PFDPanel"), ActivePFDPanelID = "Unknown";
+ for(let Looper = 0; Looper < Elements.length; Looper++) {
+ if(Elements[Looper].classList.contains("HiddenHorizontally") == false) {
+ ActivePFDPanelID = Elements[Looper].id;
+ break;
+ }
+ }
+ if(ActivePFDPanelID == "Unknown") {
+ AlertSystemError("There is not an active PFD panel.");
+ }
+ let PFDScale = Math.min((ReadWidth("PFDViewport") + 30) / ReadWidth(ActivePFDPanelID), (ReadHeight("PFDViewport") + 30) / ReadHeight(ActivePFDPanelID), 1);
+ if(Subsystem.Display.FlipPFDVertically == false) {
+ ChangeScale(ActivePFDPanelID, PFDScale);
+ } else {
+ ChangeScale(ActivePFDPanelID, PFDScale + ", " + (-PFDScale));
+ }
+ }
+ function RefreshPanel() {
+ switch(Subsystem.Display.PFDStyle) {
+ case "Default":
+ RefreshDefaultPanel();
+ break;
+ case "Boeing":
+ case "Airbus":
+ case "HUD":
+ case "Bocchi737":
+ case "AnalogGauges":
+ case "AutomobileSpeedometer":
+ AlertSystemError("A PFD style which is still under construction was selected.");
+ break;
+ default:
+ AlertSystemError("The value of Subsystem.Display.PFDStyle \"" + Subsystem.Display.PFDStyle + "\" in function RefreshPFDPanel is invalid.");
+ break;
+ }
+ }
+ // Sub-functions
+ function RefreshDefaultPanel() {
+ // Info bar
+ if(PFD0.Status.GPS.IsPositionAvailable == true) {
+ if(PFD0.Status.GPS.IsPositionAccurate == true && PFD0.Status.GPS.IsAltitudeAvailable == true && PFD0.Status.GPS.IsAltitudeAccurate == true) {
+ ChangeText("Label_PFDDefaultPanelGPSStatusValue", Translate("Normal"));
+ } else {
+ ChangeText("Label_PFDDefaultPanelGPSStatusValue", Translate("SignalWeak"));
+ }
+ RemoveClass("Label_PFDDefaultPanelGPSStatusTitle", "OrangeText");
+ RemoveClass("Label_PFDDefaultPanelGPSStatusValue", "OrangeText");
+ } else {
+ ChangeText("Label_PFDDefaultPanelGPSStatusValue", Translate("Unavailable"));
+ AddClass("Label_PFDDefaultPanelGPSStatusTitle", "OrangeText");
+ AddClass("Label_PFDDefaultPanelGPSStatusValue", "OrangeText");
+ }
+ if(PFD0.Status.IsAccelAvailable == true) {
+ ChangeText("Label_PFDDefaultPanelAccelStatusValue", Translate("Normal"));
+ RemoveClass("Label_PFDDefaultPanelAccelStatusTitle", "OrangeText");
+ RemoveClass("Label_PFDDefaultPanelAccelStatusValue", "OrangeText");
+ } else {
+ ChangeText("Label_PFDDefaultPanelAccelStatusValue", Translate("Unavailable"));
+ AddClass("Label_PFDDefaultPanelAccelStatusTitle", "OrangeText");
+ AddClass("Label_PFDDefaultPanelAccelStatusValue", "OrangeText");
+ }
+ if((PFD.Speed.Mode == "GPS" && PFD0.Status.GPS.IsSpeedAvailable == true) ||
+ (PFD.Speed.Mode == "Accel" && PFD0.Status.IsAccelAvailable == true) ||
+ (PFD.Speed.Mode == "DualChannel" && (PFD0.Status.GPS.IsSpeedAvailable == true || PFD0.Status.IsAccelAvailable == true)) ||
+ PFD.Speed.Mode == "Manual") {
+ ChangeText("Label_PFDDefaultPanelGSValue", ConvertUnit(PFD0.Stats.Speed.GSDisplay, "MeterPerSec", Subsystem.I18n.SpeedUnit).toFixed(0));
+ ChangeText("Label_PFDDefaultPanelTASValue", ConvertUnit(PFD0.Stats.Speed.TASDisplay, "MeterPerSec", Subsystem.I18n.SpeedUnit).toFixed(0));
+ } else {
+ ChangeText("Label_PFDDefaultPanelGSValue", "---");
+ ChangeText("Label_PFDDefaultPanelTASValue", "---");
+ }
+ if(PFD.Speed.Wind.Speed > 0) {
+ ChangeText("Label_PFDDefaultPanelWindValue", PFD.Speed.Wind.Direction.toString().padStart(3, "0") + "°/" + ConvertUnit(PFD.Speed.Wind.Speed, "MeterPerSec", Subsystem.I18n.SpeedUnit).toFixed(0));
+ if(PFD0.Status.GPS.IsHeadingAvailable == true) {
+ Show("PFDDefaultPanelWindDirection");
+ ChangeRotate("Needle_PFDDefaultPanelWindDirection", PFD0.Stats.Speed.Wind.RelativeHeading);
+ } else {
+ Fade("PFDDefaultPanelWindDirection");
+ }
+ } else {
+ ChangeText("Label_PFDDefaultPanelWindValue", Translate("NoWind"));
+ Fade("PFDDefaultPanelWindDirection");
+ }
+
+ // FMA
+ ChangeText("Label_PFDDefaultPanelFlightMode", Translate(PFD.FlightMode.FlightMode));
+ if(PFD0.Stats.ClockTime - PFD0.Stats.FlightModeTimestamp < 10000) {
+ AddClass("Ctnr_PFDDefaultPanelFMA2", "ModeChanged");
+ } else {
+ RemoveClass("Ctnr_PFDDefaultPanelFMA2", "ModeChanged");
+ }
+
+ // Attitude
+ Fade("Ctrl_PFDDefaultPanelAttitudeStatus");
+ Fade("Ctrl_PFDDefaultPanelAttitudeBg");
+ Fade("Ctrl_PFDDefaultPanelAttitudePitch");
+ Fade("Ctrl_PFDDefaultPanelAttitudeRoll");
+ Fade("Ctrl_PFDDefaultPanelAttitudeAircraftSymbol");
+ if(PFD.Attitude.IsEnabled == true) {
+ if((PFD.Attitude.Mode == "Accel" && PFD0.Status.IsAccelAvailable == true) ||
+ PFD.Attitude.Mode == "Manual") {
+ Show("Ctrl_PFDDefaultPanelAttitudeBg");
+ Show("Ctrl_PFDDefaultPanelAttitudePitch");
+ Show("Ctrl_PFDDefaultPanelAttitudeRoll");
+ Show("Ctrl_PFDDefaultPanelAttitudeAircraftSymbol");
+ if(System.Display.Anim > 0) {
+ ChangeAnim("Ctrl_PFDDefaultPanelAttitudeBg", "100ms");
+ ChangeAnim("Ctrl_PFDDefaultPanelAttitudePitch", "100ms");
+ ChangeAnim("Ctrl_PFDDefaultPanelAttitudeRoll", "100ms");
+ } else {
+ ChangeAnim("Ctrl_PFDDefaultPanelAttitudeBg", "");
+ ChangeAnim("Ctrl_PFDDefaultPanelAttitudePitch", "");
+ ChangeAnim("Ctrl_PFDDefaultPanelAttitudeRoll", "");
+ }
+ ChangeTop("Ctrl_PFDDefaultPanelAttitudeBg", "calc(50% - 2000px + " + 10 * PFD0.Stats.Attitude.Pitch2 * Math.cos(Math.abs(PFD0.Stats.Attitude.Roll) * (Math.PI / 180)) + "px)");
+ ChangeLeft("Ctrl_PFDDefaultPanelAttitudeBg", "calc(50% - 2000px + " + 10 * PFD0.Stats.Attitude.Pitch2 * Math.sin(PFD0.Stats.Attitude.Roll * (Math.PI / 180)) + "px)");
+ ChangeRotate("Ctrl_PFDDefaultPanelAttitudeBg", -PFD0.Stats.Attitude.Roll);
+ ChangeTop("CtrlGroup_PFDDefaultPanelAttitudePitch", "calc(50% - 900px + " + 10 * PFD0.Stats.Attitude.Pitch + "px)");
+ ChangeRotate("Ctrl_PFDDefaultPanelAttitudePitch", -PFD0.Stats.Attitude.Roll);
+ ChangeRotate("CtrlGroup_PFDDefaultPanelAttitudeRollScale", -PFD0.Stats.Attitude.Roll);
+ if(PFD0.Stats.Attitude.Roll <= 0) {
+ document.getElementById("ProgringFg_PFDDefaultPanelAttitudeRoll").style.strokeDasharray = (Math.PI * 420) * (-PFD0.Stats.Attitude.Roll / 360) + "px, " + (Math.PI * 420) * (1 + PFD0.Stats.Attitude.Roll / 360) + "px";
+ } else {
+ document.getElementById("ProgringFg_PFDDefaultPanelAttitudeRoll").style.strokeDasharray = "0, " + (Math.PI * 420) * (1 - PFD0.Stats.Attitude.Roll / 360) + "px, " + (Math.PI * 420) * (PFD0.Stats.Attitude.Roll / 360) + "px";
+ }
+ if(PFD0.Alert.Active.AttitudeWarning == "BankAngle") {
+ AddClass("ProgringFg_PFDDefaultPanelAttitudeRoll", "BankAngleWarning");
+ AddClass("PFDDefaultPanelAttitudeRollPointer", "BankAngleWarning");
+ } else {
+ RemoveClass("ProgringFg_PFDDefaultPanelAttitudeRoll", "BankAngleWarning");
+ RemoveClass("PFDDefaultPanelAttitudeRollPointer", "BankAngleWarning");
+ }
+ } else {
+ Show("Ctrl_PFDDefaultPanelAttitudeStatus");
+ ChangeText("Label_PFDDefaultPanelAttitudeStatus", Translate("AttitudeUnavailable"));
+ AddClass("Label_PFDDefaultPanelAttitudeStatus", "OrangeText");
+ }
+ } else {
+ Show("Ctrl_PFDDefaultPanelAttitudeStatus");
+ ChangeText("Label_PFDDefaultPanelAttitudeStatus", Translate("AttitudeDisabled"));
+ RemoveClass("Label_PFDDefaultPanelAttitudeStatus", "OrangeText");
+ }
+
+ // Speed
+ Fade("Ctrl_PFDDefaultPanelSpeedStatus");
+ Fade("Ctrl_PFDDefaultPanelSpeedTape");
+ Fade("Ctrl_PFDDefaultPanelSpeedAdditionalIndicators");
+ Fade("Ctrl_PFDDefaultPanelSpeedBalloon");
+ Fade("Ctrl_PFDDefaultPanelSpeedMachNumber");
+ if((PFD.Speed.Mode == "GPS" && PFD0.Status.GPS.IsSpeedAvailable == true) ||
+ (PFD.Speed.Mode == "Accel" && PFD0.Status.IsAccelAvailable == true) ||
+ (PFD.Speed.Mode == "DualChannel" && (PFD0.Status.GPS.IsSpeedAvailable == true || PFD0.Status.IsAccelAvailable == true)) ||
+ PFD.Speed.Mode == "Manual") {
+ // Show ctrls
+ Show("Ctrl_PFDDefaultPanelSpeedTape");
+ Show("Ctrl_PFDDefaultPanelSpeedAdditionalIndicators");
+ Show("Ctrl_PFDDefaultPanelSpeedBalloon");
+ if(System.Display.Anim > 0) {
+ ChangeAnim("Ctrl_PFDDefaultPanelSpeedTape", "100ms");
+ ChangeAnim("Ctrl_PFDDefaultPanelSpeedAdditionalIndicators", "100ms");
+ } else {
+ ChangeAnim("Ctrl_PFDDefaultPanelSpeedTape", "");
+ ChangeAnim("Ctrl_PFDDefaultPanelSpeedAdditionalIndicators", "");
+ }
+
+ // Tape
+ ChangeTop("CtrlGroup_PFDDefaultPanelSpeedTape", "calc(50% - 5000px + " + 5 * ConvertUnit(PFD0.Stats.Speed.TapeDisplay, "MeterPerSec", Subsystem.I18n.SpeedUnit) + "px)");
+
+ // Additional indicators
+ // Speed trend
+ if(Math.abs(ConvertUnit(PFD0.Stats.Speed.TrendDisplay, "MeterPerSec", Subsystem.I18n.SpeedUnit)) >= 3) {
+ Show("Needle_PFDDefaultPanelSpeedTrend");
+ } else {
+ Fade("Needle_PFDDefaultPanelSpeedTrend");
+ }
+ ChangeTop("Needle_PFDDefaultPanelSpeedTrend", "calc(50% - " + 5 * Math.abs(ConvertUnit(PFD0.Stats.Speed.TrendDisplay, "MeterPerSec", Subsystem.I18n.SpeedUnit)) + "px)");
+ ChangeHeight("Needle_PFDDefaultPanelSpeedTrend", 10 * Math.abs(ConvertUnit(PFD0.Stats.Speed.TrendDisplay, "MeterPerSec", Subsystem.I18n.SpeedUnit)) + "px");
+ if(PFD0.Stats.Speed.TrendDisplay >= 0) {
+ RemoveClass("Needle_PFDDefaultPanelSpeedTrend", "Decreasing");
+ } else {
+ AddClass("Needle_PFDDefaultPanelSpeedTrend", "Decreasing");
+ }
+
+ // Other speeds
+ ChangeTop("CtrlGroup_PFDDefaultPanelOtherSpeeds", "calc(50% - 5000px + " + 5 * ConvertUnit(PFD0.Stats.Speed.TapeDisplay, "MeterPerSec", Subsystem.I18n.SpeedUnit) + "px)");
+ // Speed limits
+ switch(PFD.FlightMode.FlightMode) {
+ case "DepartureGround":
+ case "ArrivalGround":
+ Hide("Ctrl_PFDDefaultPanelSpeedLimitMin");
+ break;
+ case "TakeOff":
+ case "Cruise":
+ case "Land":
+ case "EmergencyReturn":
+ Show("Ctrl_PFDDefaultPanelSpeedLimitMin");
+ ChangeHeight("Ctrl_PFDDefaultPanelSpeedLimitMin", 5 * ConvertUnit(PFD.Speed.SpeedLimit.Min, "MeterPerSec", Subsystem.I18n.SpeedUnit) + "px");
+ break;
+ default:
+ AlertSystemError("The value of PFD.FlightMode.FlightMode \"" + PFD.FlightMode.FlightMode + "\" in function RefreshPFDPanel is invalid.");
+ break;
+ }
+ ChangeHeight("Ctrl_PFDDefaultPanelSpeedLimitMax", 5 * (1000 - ConvertUnit(CalcMaxSpeedLimit(PFD.Speed.SpeedLimit.MaxOnFlapsUp, PFD.Speed.SpeedLimit.MaxOnFlapsFull, PFD.Flaps), "MeterPerSec", Subsystem.I18n.SpeedUnit)) + "px");
+
+ // Balloon
+ ChangeTop("RollingDigit_PFDDefaultPanelSpeed1", -45 * (9 - PFD0.Stats.Speed.BalloonDisplay[1]) + "px");
+ ChangeTop("RollingDigit_PFDDefaultPanelSpeed2", -45 * (10 - PFD0.Stats.Speed.BalloonDisplay[2]) + "px");
+ switch(true) {
+ case ConvertUnit(PFD0.Stats.Speed.TapeDisplay, "MeterPerSec", Subsystem.I18n.SpeedUnit) < 1:
+ ChangeTop("RollingDigit_PFDDefaultPanelSpeed3", 15 - 30 * (18 - PFD0.Stats.Speed.BalloonDisplay[3]) + "px");
+ break;
+ case ConvertUnit(PFD0.Stats.Speed.TapeDisplay, "MeterPerSec", Subsystem.I18n.SpeedUnit) > 998:
+ ChangeTop("RollingDigit_PFDDefaultPanelSpeed3", 15 - 30 * (9 - PFD0.Stats.Speed.BalloonDisplay[3]) + "px");
+ break;
+ default:
+ ChangeTop("RollingDigit_PFDDefaultPanelSpeed3", 15 - 30 * (14 - PFD0.Stats.Speed.BalloonDisplay[3]) + "px");
+ break;
+ }
+ if(PFD0.Alert.Active.SpeedWarning != "") {
+ AddClass("Ctrl_PFDDefaultPanelSpeedBalloonBalloon", "Warning");
+ } else {
+ RemoveClass("Ctrl_PFDDefaultPanelSpeedBalloonBalloon", "Warning");
+ }
+
+ // Mach number
+ if(PFD0.Stats.Speed.MachNumber >= 0.5) {
+ Show("Ctrl_PFDDefaultPanelSpeedMachNumber");
+ ChangeText("Label_PFDDefaultPanelSpeedMachNumber", PFD0.Stats.Speed.MachNumber.toFixed(3).replace("0.", "."));
+ }
+ } else {
+ Show("Ctrl_PFDDefaultPanelSpeedStatus");
+ ChangeText("Label_PFDDefaultPanelSpeedStatus", Translate("SpeedUnavailable"));
+ AddClass("Label_PFDDefaultPanelSpeedStatus", "OrangeText");
+ }
+
+ // Altitude
+ Fade("Ctrl_PFDDefaultPanelAltitudeStatus");
+ Fade("Ctrl_PFDDefaultPanelAltitudeTape");
+ Fade("Ctrl_PFDDefaultPanelAltitudeAdditionalIndicators");
+ Fade("Ctrl_PFDDefaultPanelAltitudeBalloon");
+ Fade("Ctrl_PFDDefaultPanelAltitudeMetric");
+ if((PFD.Altitude.Mode == "GPS" && PFD0.Status.GPS.IsAltitudeAvailable == true) ||
+ (PFD.Altitude.Mode == "Accel" && PFD0.Status.IsAccelAvailable == true) ||
+ (PFD.Altitude.Mode == "DualChannel" && (PFD0.Status.GPS.IsAltitudeAvailable == true || PFD0.Status.IsAccelAvailable == true)) ||
+ PFD.Altitude.Mode == "Manual") {
+ // Show ctrls
+ Show("Ctrl_PFDDefaultPanelAltitudeTape");
+ Show("Ctrl_PFDDefaultPanelAltitudeAdditionalIndicators");
+ Show("Ctrl_PFDDefaultPanelAltitudeBalloon");
+ if(System.Display.Anim > 0) {
+ ChangeAnim("Ctrl_PFDDefaultPanelAltitudeTape", "100ms");
+ ChangeAnim("Ctrl_PFDDefaultPanelAltitudeAdditionalIndicators", "100ms");
+ } else {
+ ChangeAnim("Ctrl_PFDDefaultPanelAltitudeTape", "");
+ ChangeAnim("Ctrl_PFDDefaultPanelAltitudeAdditionalIndicators", "");
+ }
+
+ // Tape
+ ChangeTop("CtrlGroup_PFDDefaultPanelAltitudeTape", "calc(50% - 37500px + " + 0.75 * ConvertUnit(PFD0.Stats.Altitude.TapeDisplay, "Meter", Subsystem.I18n.AltitudeUnit) + "px)");
+
+ // Additional indicators
+ // Altitude trend
+ if(Math.abs(ConvertUnit(PFD0.Stats.Altitude.TrendDisplay, "Meter", Subsystem.I18n.AltitudeUnit)) >= 20) {
+ Show("Needle_PFDDefaultPanelAltitudeTrend");
+ } else {
+ Fade("Needle_PFDDefaultPanelAltitudeTrend");
+ }
+ ChangeTop("Needle_PFDDefaultPanelAltitudeTrend", "calc(50% - " + 0.75 * Math.abs(ConvertUnit(PFD0.Stats.Altitude.TrendDisplay, "Meter", Subsystem.I18n.AltitudeUnit)) + "px)");
+ ChangeHeight("Needle_PFDDefaultPanelAltitudeTrend", 1.5 * Math.abs(ConvertUnit(PFD0.Stats.Altitude.TrendDisplay, "Meter", Subsystem.I18n.AltitudeUnit)) + "px");
+ if(PFD0.Stats.Altitude.TrendDisplay >= 0) {
+ RemoveClass("Needle_PFDDefaultPanelAltitudeTrend", "Decreasing");
+ } else {
+ AddClass("Needle_PFDDefaultPanelAltitudeTrend", "Decreasing");
+ }
+
+ // Other altitudes
+ ChangeTop("CtrlGroup_PFDDefaultPanelOtherAltitudes", "calc(50% - 37500px + " + 0.75 * ConvertUnit(PFD0.Stats.Altitude.TapeDisplay, "Meter", Subsystem.I18n.AltitudeUnit) + "px)");
+ // Decision altitude
+ switch(PFD.FlightMode.FlightMode) {
+ case "DepartureGround":
+ case "TakeOff":
+ case "Cruise":
+ Fade("Ctrl_PFDDefaultPanelDecisionAltitude");
+ break;
+ case "Land":
+ case "ArrivalGround":
+ Show("Ctrl_PFDDefaultPanelDecisionAltitude");
+ ChangeBottom("Ctrl_PFDDefaultPanelDecisionAltitude", 0.75 * (ConvertUnit(PFD.Altitude.AirportElevation.Arrival + PFD.Altitude.DecisionHeight, "Meter", Subsystem.I18n.AltitudeUnit) + 2000) - 35 + "px");
+ if(PFD0.Status.IsDecisionAltitudeActive == true) {
+ AddClass("Ctrl_PFDDefaultPanelDecisionAltitude", "Active");
+ } else {
+ RemoveClass("Ctrl_PFDDefaultPanelDecisionAltitude", "Active");
+ }
+ break;
+ case "EmergencyReturn":
+ Show("Ctrl_PFDDefaultPanelDecisionAltitude");
+ ChangeBottom("Ctrl_PFDDefaultPanelDecisionAltitude", 0.75 * (ConvertUnit(PFD.Altitude.AirportElevation.Departure + PFD.Altitude.DecisionHeight, "Meter", Subsystem.I18n.AltitudeUnit) + 2000) - 35 + "px");
+ if(PFD0.Status.IsDecisionAltitudeActive == true) {
+ AddClass("Ctrl_PFDDefaultPanelDecisionAltitude", "Active");
+ } else {
+ RemoveClass("Ctrl_PFDDefaultPanelDecisionAltitude", "Active");
+ }
+ break;
+ default:
+ AlertSystemError("The value of PFD.FlightMode.FlightMode \"" + PFD.FlightMode.FlightMode + "\" in function RefreshPFDPanel is invalid.");
+ break;
+ }
+
+ // Ground altitude
+ switch(PFD.FlightMode.FlightMode) {
+ case "DepartureGround":
+ case "TakeOff":
+ case "EmergencyReturn":
+ ChangeBottom("Ctrl_PFDDefaultPanelGroundAltitude", 0.75 * (ConvertUnit(PFD.Altitude.AirportElevation.Departure, "Meter", Subsystem.I18n.AltitudeUnit) + 2000) - 40 + "px");
+ break;
+ case "Cruise":
+ case "Land":
+ case "ArrivalGround":
+ ChangeBottom("Ctrl_PFDDefaultPanelGroundAltitude", 0.75 * (ConvertUnit(PFD.Altitude.AirportElevation.Arrival, "Meter", Subsystem.I18n.AltitudeUnit) + 2000) - 40 + "px");
+ break;
+ default:
+ AlertSystemError("The value of PFD.FlightMode.FlightMode \"" + PFD.FlightMode.FlightMode + "\" in function RefreshPFDPanel is invalid.");
+ break;
+ }
+
+ // Balloon
+ if(PFD0.Stats.Altitude.TapeDisplay >= 0) {
+ ChangeTop("RollingDigit_PFDDefaultPanelAltitude1", -45 * (5 - PFD0.Stats.Altitude.BalloonDisplay[1]) + "px");
+ } else {
+ ChangeTop("RollingDigit_PFDDefaultPanelAltitude1", "-270px");
+ }
+ ChangeTop("RollingDigit_PFDDefaultPanelAltitude2", -45 * (10 - PFD0.Stats.Altitude.BalloonDisplay[2]) + "px");
+ ChangeTop("RollingDigit_PFDDefaultPanelAltitude3", -45 * (10 - PFD0.Stats.Altitude.BalloonDisplay[3]) + "px");
+ switch(true) {
+ case ConvertUnit(PFD0.Stats.Altitude.TapeDisplay, "Meter", Subsystem.I18n.AltitudeUnit) < -1980:
+ ChangeTop("RollingDigit_PFDDefaultPanelAltitude4", 17.5 - 25 * (21 - PFD0.Stats.Altitude.BalloonDisplay[4] / 20) + "px");
+ break;
+ case ConvertUnit(PFD0.Stats.Altitude.TapeDisplay, "Meter", Subsystem.I18n.AltitudeUnit) >= -1980 &&
+ ConvertUnit(PFD0.Stats.Altitude.TapeDisplay, "Meter", Subsystem.I18n.AltitudeUnit) <= -20:
+ ChangeTop("RollingDigit_PFDDefaultPanelAltitude4", 17.5 - 25 * (17 - PFD0.Stats.Altitude.BalloonDisplay[4] / 20) + "px");
+ break;
+ case ConvertUnit(PFD0.Stats.Altitude.TapeDisplay, "Meter", Subsystem.I18n.AltitudeUnit) > -20 &&
+ ConvertUnit(PFD0.Stats.Altitude.TapeDisplay, "Meter", Subsystem.I18n.AltitudeUnit) < 20:
+ ChangeTop("RollingDigit_PFDDefaultPanelAltitude4", 17.5 - 25 * (13 - PFD0.Stats.Altitude.BalloonDisplay[4] / 20) + "px");
+ break;
+ case ConvertUnit(PFD0.Stats.Altitude.TapeDisplay, "Meter", Subsystem.I18n.AltitudeUnit) >= 20 &&
+ ConvertUnit(PFD0.Stats.Altitude.TapeDisplay, "Meter", Subsystem.I18n.AltitudeUnit) <= 49980:
+ ChangeTop("RollingDigit_PFDDefaultPanelAltitude4", 17.5 - 25 * (9 - PFD0.Stats.Altitude.BalloonDisplay[4] / 20) + "px");
+ break;
+ case ConvertUnit(PFD0.Stats.Altitude.TapeDisplay, "Meter", Subsystem.I18n.AltitudeUnit) > 49980:
+ ChangeTop("RollingDigit_PFDDefaultPanelAltitude4", 17.5 - 25 * (5 - PFD0.Stats.Altitude.BalloonDisplay[4] / 20) + "px");
+ break;
+ default:
+ AlertSystemError("The value of ConvertUnit(PFD0.Stats.Altitude.TapeDisplay, \"Meter\", Subsystem.I18n.AltitudeUnit) \"" + ConvertUnit(PFD0.Stats.Altitude.TapeDisplay, "Meter", Subsystem.I18n.AltitudeUnit) + "\" in function RefreshPFDPanel is invalid.");
+ break;
+ }
+ if(PFD0.Alert.Active.AltitudeWarning != "") {
+ AddClass("Ctrl_PFDDefaultPanelAltitudeBalloonBalloon", "Warning");
+ } else {
+ RemoveClass("Ctrl_PFDDefaultPanelAltitudeBalloonBalloon", "Warning");
+ }
+
+ // Metric
+ switch(Subsystem.I18n.AltitudeUnit) {
+ case "Meter":
+ case "Feet":
+ break;
+ case "FeetButShowMeterBeside":
+ Show("Ctrl_PFDDefaultPanelAltitudeMetric");
+ ChangeText("Label_PFDDefaultPanelAltitudeMetric", PFD0.Stats.Altitude.TapeDisplay.toFixed(0) + "" + Translate("MetricAltitude") + "");
+ break;
+ default:
+ AlertSystemError("The value of Subsystem.I18n.AltitudeUnit \"" + Subsystem.I18n.AltitudeUnit + "\" in function RefreshPFDPanel is invalid.");
+ break;
+ }
+ } else {
+ Show("Ctrl_PFDDefaultPanelAltitudeStatus");
+ ChangeText("Label_PFDDefaultPanelAltitudeStatus", Translate("AltitudeUnavailable"));
+ AddClass("Label_PFDDefaultPanelAltitudeStatus", "OrangeText");
+ }
+
+ // Vertical speed
+ Fade("Ctrl_PFDDefaultPanelVerticalSpeedStatus");
+ Fade("Ctrl_PFDDefaultPanelVerticalSpeedTape");
+ Fade("Ctrl_PFDDefaultPanelVerticalSpeedNeedle");
+ Fade("Ctrl_PFDDefaultPanelVerticalSpeedBalloon");
+ if((PFD.Altitude.Mode == "GPS" && PFD0.Status.GPS.IsAltitudeAvailable == true) ||
+ (PFD.Altitude.Mode == "Accel" && PFD0.Status.IsAccelAvailable == true) ||
+ (PFD.Altitude.Mode == "DualChannel" && (PFD0.Status.GPS.IsAltitudeAvailable == true || PFD0.Status.IsAccelAvailable == true)) ||
+ PFD.Altitude.Mode == "Manual") {
+ // Show ctrls
+ Show("Ctrl_PFDDefaultPanelVerticalSpeedTape");
+ Show("Ctrl_PFDDefaultPanelVerticalSpeedNeedle");
+ if(System.Display.Anim > 0) {
+ ChangeAnim("Ctrl_PFDDefaultPanelVerticalSpeedNeedle", "100ms");
+ } else {
+ ChangeAnim("Ctrl_PFDDefaultPanelVerticalSpeedNeedle", "");
+ }
+
+ // Needle
+ // Calc needle angle
+ let ConvertedVerticalSpeed = ConvertUnit(PFD0.Stats.Speed.Vertical, "MeterPerSec", Subsystem.I18n.VerticalSpeedUnit),
+ VerticalPixels = 0, NeedleAngle = 0;
+ switch(Subsystem.I18n.VerticalSpeedUnit) {
+ case "MeterPerSec":
+ switch(true) {
+ case ConvertedVerticalSpeed <= -6:
+ VerticalPixels = -180;
+ break;
+ case ConvertedVerticalSpeed > -6 && ConvertedVerticalSpeed <= -2:
+ VerticalPixels = -120 + 60 * ((ConvertedVerticalSpeed + 2) / 4);
+ break;
+ case ConvertedVerticalSpeed > -2 && ConvertedVerticalSpeed < 2:
+ VerticalPixels = 120 * (ConvertedVerticalSpeed / 2);
+ break;
+ case ConvertedVerticalSpeed >= 2 && ConvertedVerticalSpeed < 6:
+ VerticalPixels = 120 + 60 * ((ConvertedVerticalSpeed - 2) / 4);
+ break;
+ case ConvertedVerticalSpeed >= 6:
+ VerticalPixels = 180;
+ break;
+ default:
+ AlertSystemError("The value of ConvertedVerticalSpeed \"" + ConvertedVerticalSpeed + "\" in function RefreshPFDPanel is invalid.");
+ break;
+ }
+ break;
+ case "FeetPerMin":
+ switch(true) {
+ case ConvertedVerticalSpeed <= -6000:
+ VerticalPixels = -180;
+ break;
+ case ConvertedVerticalSpeed > -6000 && ConvertedVerticalSpeed <= -2000:
+ VerticalPixels = -120 + 60 * ((ConvertedVerticalSpeed + 2000) / 4000);
+ break;
+ case ConvertedVerticalSpeed > -2000 && ConvertedVerticalSpeed < 2000:
+ VerticalPixels = 120 * (ConvertedVerticalSpeed / 2000);
+ break;
+ case ConvertedVerticalSpeed >= 2000 && ConvertedVerticalSpeed < 6000:
+ VerticalPixels = 120 + 60 * ((ConvertedVerticalSpeed - 2000) / 4000);
+ break;
+ case ConvertedVerticalSpeed >= 6000:
+ VerticalPixels = 180;
+ break;
+ default:
+ AlertSystemError("The value of ConvertedVerticalSpeed \"" + ConvertedVerticalSpeed + "\" in function RefreshPFDPanel is invalid.");
+ break;
+ }
+ break;
+ default:
+ AlertSystemError("The value of Subsystem.I18n.VerticalSpeedUnit \"" + Subsystem.I18n.VerticalSpeedUnit + "\" in function RefreshPFDPanel is invalid.");
+ break;
+ }
+ NeedleAngle = Math.atan(VerticalPixels / 100) / (Math.PI / 180);
+
+ // Refresh needle
+ let NeedleLength = 100 / Math.cos(NeedleAngle * (Math.PI / 180));
+ ChangeRotate("Needle_PFDDefaultPanelVerticalSpeed", -90 + NeedleAngle);
+ ChangeTop("Needle_PFDDefaultPanelVerticalSpeed", "calc(50% - " + NeedleLength + "px)");
+ ChangeHeight("Needle_PFDDefaultPanelVerticalSpeed", NeedleLength * 2 + "px");
+
+ // Balloon
+ if((Subsystem.I18n.VerticalSpeedUnit == "MeterPerSec" && Math.abs(ConvertedVerticalSpeed) >= 1) ||
+ (Subsystem.I18n.VerticalSpeedUnit == "FeetPerMin" && Math.abs(ConvertedVerticalSpeed) >= 400)) {
+ Show("Ctrl_PFDDefaultPanelVerticalSpeedBalloon");
+ let VerticalSpeedDisplay = 0;
+ switch(Subsystem.I18n.VerticalSpeedUnit) {
+ case "MeterPerSec":
+ VerticalSpeedDisplay = Math.trunc(ConvertedVerticalSpeed / 0.2) * 0.2;
+ if(VerticalSpeedDisplay < -50) {
+ VerticalSpeedDisplay = -50;
+ }
+ if(VerticalSpeedDisplay > 50) {
+ VerticalSpeedDisplay = 50;
+ }
+ if(VerticalSpeedDisplay > 0) {
+ ChangeText("Label_PFDDefaultPanelVerticalSpeedBalloon", "+" + VerticalSpeedDisplay.toFixed(1));
+ } else {
+ ChangeText("Label_PFDDefaultPanelVerticalSpeedBalloon", VerticalSpeedDisplay.toFixed(1));
+ }
+ break;
+ case "FeetPerMin":
+ VerticalSpeedDisplay = Math.trunc(ConvertedVerticalSpeed / 50) * 50;
+ if(VerticalSpeedDisplay < -9999) {
+ VerticalSpeedDisplay = -9999;
+ }
+ if(VerticalSpeedDisplay > 9999) {
+ VerticalSpeedDisplay = 9999;
+ }
+ if(VerticalSpeedDisplay > 0) {
+ ChangeText("Label_PFDDefaultPanelVerticalSpeedBalloon", "+" + VerticalSpeedDisplay);
+ } else {
+ ChangeText("Label_PFDDefaultPanelVerticalSpeedBalloon", VerticalSpeedDisplay);
+ }
+ break;
+ default:
+ AlertSystemError("The value of Subsystem.I18n.VerticalSpeedUnit \"" + Subsystem.I18n.VerticalSpeedUnit + "\" in function RefreshPFDPanel is invalid.");
+ break;
+ }
+ }
+ } else {
+ Show("Ctrl_PFDDefaultPanelVerticalSpeedStatus");
+ ChangeText("Label_PFDDefaultPanelVerticalSpeedStatus", Translate("VerticalSpeedUnavailable"));
+ AddClass("Label_PFDDefaultPanelVerticalSpeedStatus", "OrangeText");
+ }
+
+ // Heading
+ Fade("Ctrl_PFDDefaultPanelHeadingStatus");
+ Fade("Ctrl_PFDDefaultPanelHeadingTape");
+ Fade("Ctrl_PFDDefaultPanelHeadingBalloon");
+ if(PFD0.Status.GPS.IsHeadingAvailable == true) {
+ Show("Ctrl_PFDDefaultPanelHeadingTape");
+ Show("Ctrl_PFDDefaultPanelHeadingBalloon");
+ if(System.Display.Anim > 0) {
+ ChangeAnim("Ctrl_PFDDefaultPanelHeadingTape", "100ms");
+ } else {
+ ChangeAnim("Ctrl_PFDDefaultPanelHeadingTape", "");
+ }
+ ChangeRotate("CtrlGroup_PFDDefaultPanelHeadingTape", -PFD0.Stats.Heading.Display);
+ ChangeText("Label_PFDDefaultPanelHeadingBalloon", PFD0.Stats.Heading.Display.toFixed(0).padStart(3, "0"));
+ } else {
+ Show("Ctrl_PFDDefaultPanelHeadingStatus");
+ ChangeText("Label_PFDDefaultPanelHeadingStatus", Translate("HeadingUnavailable"));
+ AddClass("Label_PFDDefaultPanelHeadingStatus", "OrangeText");
+ }
+
+ // DME
+ if(PFD.DME.IsEnabled == true && PFD0.Status.GPS.IsPositionAvailable == true) {
+ Show("Ctnr_PFDDefaultPanelDME");
+ if(PFD0.Stats.DME.Distance < 10000000) { // Max 10000 kilometers.
+ ChangeText("Label_PFDDefaultPanelDMEDistance", ConvertUnit(PFD0.Stats.DME.Distance, "Meter", Subsystem.I18n.DistanceUnit).toFixed(1));
+ if(PFD0.Stats.Speed.GSDisplay > 0 && PFD0.Stats.DME.ETA < 360000000) { // Max 100 hours.
+ ChangeText("Label_PFDDefaultPanelDMEETA",
+ Math.trunc(PFD0.Stats.DME.ETA / 3600000) + "" + Translate("Hour") + "" +
+ Math.trunc(PFD0.Stats.DME.ETA % 3600000 / 60000).toString().padStart(2, "0") + "" + Translate("Minute") + "");
+ } else {
+ ChangeText("Label_PFDDefaultPanelDMEETA", "--" + Translate("Hour") + "--" + Translate("Minute") + "");
+ }
+ } else {
+ ChangeText("Label_PFDDefaultPanelDMEDistance", Translate("DistanceTooFar"));
+ ChangeText("Label_PFDDefaultPanelDMEETA", "--" + Translate("Hour") + "--" + Translate("Minute") + "");
+ }
+ } else {
+ Fade("Ctnr_PFDDefaultPanelDME");
+ }
+
+ // Radio altitude
+ if((
+ (PFD.Altitude.Mode == "GPS" && PFD0.Status.GPS.IsAltitudeAvailable == true) ||
+ (PFD.Altitude.Mode == "Accel" && PFD0.Status.IsAccelAvailable == true) ||
+ (PFD.Altitude.Mode == "DualChannel" && (PFD0.Status.GPS.IsAltitudeAvailable == true || PFD0.Status.IsAccelAvailable == true)) ||
+ PFD.Altitude.Mode == "Manual"
+ ) &&
+ PFD0.Stats.Altitude.RadioDisplay <= 762) {
+ Show("Ctnr_PFDDefaultPanelRadioAltitude");
+ let ConvertedRadioAltitude = ConvertUnit(PFD0.Stats.Altitude.RadioDisplay, "Meter", Subsystem.I18n.AltitudeUnit),
+ ConvertedRadioAltitudeDisplay = 0;
+ switch(true) {
+ case Math.abs(ConvertedRadioAltitude) >= 500:
+ switch(Subsystem.I18n.AltitudeUnit) {
+ case "Meter":
+ ConvertedRadioAltitudeDisplay = Math.round(ConvertedRadioAltitude / 10) * 10;
+ break;
+ case "Feet":
+ case "FeetButShowMeterBeside":
+ ConvertedRadioAltitudeDisplay = Math.round(ConvertedRadioAltitude / 20) * 20;
+ break;
+ default:
+ AlertSystemError("The value of Subsystem.I18n.AltitudeUnit \"" + Subsystem.I18n.AltitudeUnit + "\" in function RefreshPFDPanel is invalid.");
+ break;
+ }
+ break;
+ case Math.abs(ConvertedRadioAltitude) >= 100 && Math.abs(ConvertedRadioAltitude) < 500:
+ switch(Subsystem.I18n.AltitudeUnit) {
+ case "Meter":
+ ConvertedRadioAltitudeDisplay = Math.round(ConvertedRadioAltitude / 5) * 5;
+ break;
+ case "Feet":
+ case "FeetButShowMeterBeside":
+ ConvertedRadioAltitudeDisplay = Math.round(ConvertedRadioAltitude / 10) * 10;
+ break;
+ default:
+ AlertSystemError("The value of Subsystem.I18n.AltitudeUnit \"" + Subsystem.I18n.AltitudeUnit + "\" in function RefreshPFDPanel is invalid.");
+ break;
+ }
+ break;
+ case Math.abs(ConvertedRadioAltitude) < 100:
+ switch(Subsystem.I18n.AltitudeUnit) {
+ case "Meter":
+ ConvertedRadioAltitudeDisplay = Math.round(ConvertedRadioAltitude);
+ break;
+ case "Feet":
+ case "FeetButShowMeterBeside":
+ ConvertedRadioAltitudeDisplay = Math.round(ConvertedRadioAltitude / 2) * 2;
+ break;
+ default:
+ AlertSystemError("The value of Subsystem.I18n.AltitudeUnit \"" + Subsystem.I18n.AltitudeUnit + "\" in function RefreshPFDPanel is invalid.");
+ break;
+ }
+ break;
+ default:
+ AlertSystemError("The value of Math.abs(ConvertedRadioAltitude) \"" + Math.abs(ConvertedRadioAltitude) + "\" in function RefreshPFDPanel is invalid.");
+ break;
+ }
+ ChangeText("ProgringText_PFDDefaultPanelRadioAltitude", ConvertedRadioAltitudeDisplay);
+ Fade("CtrlGroup_PFDDefaultPanelRadioAltitudeDialScale");
+ Fade("Progring_PFDDefaultPanelRadioAltitude");
+ if(Math.abs(ConvertedRadioAltitudeDisplay) < 1000) {
+ Show("CtrlGroup_PFDDefaultPanelRadioAltitudeDialScale");
+ if(ConvertedRadioAltitude >= 0) {
+ Show("Progring_PFDDefaultPanelRadioAltitude");
+ document.getElementById("ProgringFg_PFDDefaultPanelRadioAltitude").style.strokeDasharray = (Math.PI * 82) * (ConvertedRadioAltitude / 1000) + "px, " + (Math.PI * 82) * (1 - ConvertedRadioAltitude / 1000) + "px";
+ if(System.Display.Anim > 0) {
+ ChangeAnim("ProgringFg_PFDDefaultPanelRadioAltitude", "100ms");
+ } else {
+ ChangeAnim("ProgringFg_PFDDefaultPanelRadioAltitude", "");
+ }
+ }
+ }
+ } else {
+ Fade("Ctnr_PFDDefaultPanelRadioAltitude");
+ }
+
+ // Decision altitude
+ if((PFD.Altitude.Mode == "GPS" && PFD0.Status.GPS.IsAltitudeAvailable == true) ||
+ (PFD.Altitude.Mode == "Accel" && PFD0.Status.IsAccelAvailable == true) ||
+ (PFD.Altitude.Mode == "DualChannel" && (PFD0.Status.GPS.IsAltitudeAvailable == true || PFD0.Status.IsAccelAvailable == true)) ||
+ PFD.Altitude.Mode == "Manual") {
+ switch(PFD.FlightMode.FlightMode) {
+ case "DepartureGround":
+ case "TakeOff":
+ case "Cruise":
+ Fade("Ctnr_PFDDefaultPanelDecisionAltitude");
+ break;
+ case "Land":
+ case "ArrivalGround":
+ Show("Ctnr_PFDDefaultPanelDecisionAltitude");
+ ChangeText("Label_PFDDefaultPanelDecisionAltitudeValue", Math.trunc(ConvertUnit(PFD.Altitude.AirportElevation.Arrival + PFD.Altitude.DecisionHeight, "Meter", Subsystem.I18n.AltitudeUnit)));
+ if(PFD0.Status.IsDecisionAltitudeActive == true) {
+ AddClass("Ctnr_PFDDefaultPanelDecisionAltitude", "Active");
+ if(PFD0.Stats.ClockTime - PFD0.Stats.Altitude.DecisionTimestamp < 3000) {
+ AddClass("Ctnr_PFDDefaultPanelDecisionAltitude", "Caution");
+ } else {
+ RemoveClass("Ctnr_PFDDefaultPanelDecisionAltitude", "Caution");
+ }
+ } else {
+ RemoveClass("Ctnr_PFDDefaultPanelDecisionAltitude", "Active");
+ }
+ break;
+ case "EmergencyReturn":
+ Show("Ctnr_PFDDefaultPanelDecisionAltitude");
+ ChangeText("Label_PFDDefaultPanelDecisionAltitudeValue", Math.trunc(ConvertUnit(PFD.Altitude.AirportElevation.Departure + PFD.Altitude.DecisionHeight, "Meter", Subsystem.I18n.AltitudeUnit)));
+ if(PFD0.Status.IsDecisionAltitudeActive == true) {
+ AddClass("Ctnr_PFDDefaultPanelDecisionAltitude", "Active");
+ if(PFD0.Stats.ClockTime - PFD0.Stats.Altitude.DecisionTimestamp < 3000) {
+ AddClass("Ctnr_PFDDefaultPanelDecisionAltitude", "Caution");
+ } else {
+ RemoveClass("Ctnr_PFDDefaultPanelDecisionAltitude", "Caution");
+ }
+ } else {
+ RemoveClass("Ctnr_PFDDefaultPanelDecisionAltitude", "Active");
+ }
+ break;
+ default:
+ AlertSystemError("The value of PFD.FlightMode.FlightMode \"" + PFD.FlightMode.FlightMode + "\" in function RefreshPFDPanel is invalid.");
+ break;
+ }
+ } else {
+ Fade("Ctnr_PFDDefaultPanelDecisionAltitude");
+ }
+
+ // Warning
+ Fade("Ctnr_PFDDefaultPanelWarning");
+ if(PFD0.Alert.Active.AttitudeWarning != "") {
+ Show("Ctnr_PFDDefaultPanelWarning");
+ ChangeText("Label_PFDDefaultPanelWarning", Translate(PFD0.Alert.Active.AttitudeWarning));
+ }
+ if(PFD0.Alert.Active.SpeedWarning != "") {
+ Show("Ctnr_PFDDefaultPanelWarning");
+ ChangeText("Label_PFDDefaultPanelWarning", Translate(PFD0.Alert.Active.SpeedWarning));
+ }
+ if(PFD0.Alert.Active.AltitudeWarning != "") {
+ Show("Ctnr_PFDDefaultPanelWarning");
+ ChangeText("Label_PFDDefaultPanelWarning", Translate(PFD0.Alert.Active.AltitudeWarning));
+ }
+ }
+ function RefreshBoeingPanel() {
+ // ???
+ }
+ function RefreshAirbusPanel() {
+ // ???
+ }
+ function RefreshHUDPanel() {
+ // ???
+ }
+ function RefreshBocchi737Panel() {
+ // ???
+ }
+ function RefreshAnalogGaugesPanel() {
+ // ???
+ }
+ function RefreshAutomobileSpeedometerPanel() {
+ // ???
+ }
+
+ function RefreshPFDAudio() {
+ switch(Subsystem.Audio.Scheme) {
+ case "Boeing":
+ RefreshBoeingAudio();
+ break;
+ case "Airbus":
+ case "Bocchi737":
+ AlertSystemError("An audio scheme which is still under construction was selected.");
+ break;
+ default:
+ AlertSystemError("The value of Subsystem.Audio.Scheme \"" + Subsystem.Audio.Scheme + "\" in function RefreshPFDAudio is invalid.");
+ break;
+ }
+ }
+ // Sub-functions
+ function RefreshBoeingAudio() {
+ // Attitude
+ if(PFD0.Alert.Active.AttitudeWarning != PFD0.Alert.NowPlaying.AttitudeWarning) {
+ switch(PFD0.Alert.Active.AttitudeWarning) {
+ case "":
+ StopAudio("Audio_AttitudeAlert");
+ break;
+ case "BankAngle":
+ ChangeAudioLoop("Audio_AttitudeAlert", true);
+ PlayAudio("Audio_AttitudeAlert", "audio/Common_" + PFD0.Alert.Active.AttitudeWarning + ".mp3");
+ break;
+ default:
+ AlertSystemError("The value of PFD0.Alert.Active.AttitudeWarning \"" + PFD0.Alert.Active.AttitudeWarning + "\" in function RefreshPFDAudio is invalid.");
+ break;
+ }
+ PFD0.Alert.NowPlaying.AttitudeWarning = PFD0.Alert.Active.AttitudeWarning;
+ }
+
+ // Speed
+ if(PFD0.Alert.Active.SpeedWarning != PFD0.Alert.NowPlaying.SpeedWarning) {
+ switch(PFD0.Alert.Active.SpeedWarning) {
+ case "":
+ StopAudio("Audio_SpeedAlert");
+ break;
+ case "AirspeedLow":
+ case "Overspeed":
+ ChangeAudioLoop("Audio_SpeedAlert", true);
+ PlayAudio("Audio_SpeedAlert", "audio/Boeing_" + PFD0.Alert.Active.SpeedWarning + ".mp3");
+ break;
+ default:
+ AlertSystemError("The value of PFD0.Alert.Active.SpeedWarning \"" + PFD0.Alert.Active.SpeedWarning + "\" in function RefreshPFDAudio is invalid.");
+ break;
+ }
+ PFD0.Alert.NowPlaying.SpeedWarning = PFD0.Alert.Active.SpeedWarning;
+ }
+
+ // Altitude
+ if(PFD0.Alert.Active.AltitudeWarning != PFD0.Alert.NowPlaying.AltitudeWarning) {
+ switch(PFD0.Alert.Active.AltitudeWarning) {
+ case "":
+ StopAudio("Audio_AltitudeAlert");
+ break;
+ case "DontSink":
+ case "GlideSlope":
+ case "SinkRate":
+ case "PullUp":
+ ChangeAudioLoop("Audio_AltitudeAlert", true);
+ PlayAudio("Audio_AltitudeAlert", "audio/Common_" + PFD0.Alert.Active.AltitudeWarning + ".mp3");
+ break;
+ default:
+ AlertSystemError("The value of PFD0.Alert.Active.AltitudeWarning \"" + PFD0.Alert.Active.AltitudeWarning + "\" in function RefreshPFDAudio is invalid.");
+ break;
+ }
+ PFD0.Alert.NowPlaying.AltitudeWarning = PFD0.Alert.Active.AltitudeWarning;
+ }
+ if(PFD0.Alert.Active.AltitudeCallout != PFD0.Alert.NowPlaying.AltitudeCallout && PFD0.Alert.Active.AltitudeWarning == "") {
+ switch(PFD0.Alert.Active.AltitudeCallout) {
+ case "":
+ case "HundredAbove":
+ case "Retard":
+ break;
+ case "2500":
+ case "1000":
+ case "500":
+ case "400":
+ case "300":
+ case "200":
+ case "100":
+ case "50":
+ case "40":
+ case "30":
+ case "20":
+ case "10":
+ case "ApproachingMinimums":
+ case "Minimums":
+ ChangeAudioLoop("Audio_AltitudeAlert", false);
+ PlayAudio("Audio_AltitudeAlert", "audio/Boeing_" + PFD0.Alert.Active.AltitudeCallout + ".mp3");
+ break;
+ default:
+ AlertSystemError("The value of PFD0.Alert.Active.AltitudeCallout \"" + PFD0.Alert.Active.AltitudeCallout + "\" in function RefreshPFDAudio is invalid.");
+ break;
+ }
+ PFD0.Alert.NowPlaying.AltitudeCallout = PFD0.Alert.Active.AltitudeCallout;
+ }
+ }
+ function RefreshAirbusAudio() {
+ // ???
+ }
+ function RefreshBocchi737Audio() {
+ // ???
+ }
+
+ function RefreshTechInfo() {
+ // GPS
+ if(PFD0.RawData.GPS.Position.Lat != null) {
+ ChangeText("Label_PFDTechInfoLat", PFD0.RawData.GPS.Position.Lat.toFixed(5));
+ } else {
+ ChangeText("Label_PFDTechInfoLat", "N/A");
+ }
+ if(PFD0.RawData.GPS.Position.Lon != null) {
+ ChangeText("Label_PFDTechInfoLon", PFD0.RawData.GPS.Position.Lon.toFixed(5));
+ } else {
+ ChangeText("Label_PFDTechInfoLon", "N/A");
+ }
+ if(PFD0.RawData.GPS.Position.Accuracy != null) {
+ ChangeText("Label_PFDTechInfoPositionAccuracy", PFD0.RawData.GPS.Position.Accuracy.toFixed(2) + "米");
+ } else {
+ ChangeText("Label_PFDTechInfoPositionAccuracy", "N/A");
+ }
+ if(PFD0.RawData.GPS.Speed != null) {
+ ChangeText("Label_PFDTechInfoGPSSpeed", PFD0.RawData.GPS.Speed.toFixed(2) + "米/秒");
+ } else {
+ ChangeText("Label_PFDTechInfoGPSSpeed", "N/A");
+ }
+ if(PFD0.RawData.GPS.Altitude.Altitude != null) {
+ ChangeText("Label_PFDTechInfoGPSAltitude", PFD0.RawData.GPS.Altitude.Altitude.toFixed(2) + "米");
+ } else {
+ ChangeText("Label_PFDTechInfoGPSAltitude", "N/A");
+ }
+ if(PFD0.RawData.GPS.Altitude.Accuracy != null) {
+ ChangeText("Label_PFDTechInfoAltitudeAccuracy", PFD0.RawData.GPS.Altitude.Accuracy.toFixed(2) + "米");
+ } else {
+ ChangeText("Label_PFDTechInfoAltitudeAccuracy", "N/A");
+ }
+ if(PFD0.RawData.GPS.Heading != null) {
+ ChangeText("Label_PFDTechInfoHeading", PFD0.RawData.GPS.Heading.toFixed(2) + "度");
+ } else {
+ ChangeText("Label_PFDTechInfoHeading", "N/A");
+ }
+ ChangeText("Label_PFDTechInfoGPSTimestamp", PFD0.RawData.GPS.Timestamp + " (+" + (PFD0.Stats.ClockTime - PFD0.RawData.GPS.Timestamp) + ")");
+
+ // Accel
+ if(PFD0.RawData.Accel.Accel.Absolute.X != null) {
+ ChangeText("Label_PFDTechInfoAbsoluteXAxis", PFD0.RawData.Accel.Accel.Absolute.X.toFixed(2) + "m/s²");
+ } else {
+ ChangeText("Label_PFDTechInfoAbsoluteXAxis", "N/A");
+ }
+ if(PFD0.RawData.Accel.Accel.Absolute.Y != null) {
+ ChangeText("Label_PFDTechInfoAbsoluteYAxis", PFD0.RawData.Accel.Accel.Absolute.Y.toFixed(2) + "m/s²");
+ } else {
+ ChangeText("Label_PFDTechInfoAbsoluteYAxis", "N/A");
+ }
+ if(PFD0.RawData.Accel.Accel.Absolute.Z != null) {
+ ChangeText("Label_PFDTechInfoAbsoluteZAxis", PFD0.RawData.Accel.Accel.Absolute.Z.toFixed(2) + "m/s²");
+ } else {
+ ChangeText("Label_PFDTechInfoAbsoluteZAxis", "N/A");
+ }
+ if(PFD0.RawData.Accel.Accel.AbsoluteWithGravity.X != null) {
+ ChangeText("Label_PFDTechInfoAbsoluteXAxisWithGravity", PFD0.RawData.Accel.Accel.AbsoluteWithGravity.X.toFixed(2) + "m/s²");
+ } else {
+ ChangeText("Label_PFDTechInfoAbsoluteXAxisWithGravity", "N/A");
+ }
+ if(PFD0.RawData.Accel.Accel.AbsoluteWithGravity.Y != null) {
+ ChangeText("Label_PFDTechInfoAbsoluteYAxisWithGravity", PFD0.RawData.Accel.Accel.AbsoluteWithGravity.Y.toFixed(2) + "m/s²");
+ } else {
+ ChangeText("Label_PFDTechInfoAbsoluteYAxisWithGravity", "N/A");
+ }
+ if(PFD0.RawData.Accel.Accel.AbsoluteWithGravity.Z != null) {
+ ChangeText("Label_PFDTechInfoAbsoluteZAxisWithGravity", PFD0.RawData.Accel.Accel.AbsoluteWithGravity.Z.toFixed(2) + "m/s²");
+ } else {
+ ChangeText("Label_PFDTechInfoAbsoluteZAxisWithGravity", "N/A");
+ }
+ ChangeText("Label_PFDTechInfoScreenOrientation", screen.orientation.type);
+ ChangeText("Label_PFDTechInfoRelativeForward", PFD0.RawData.Accel.Accel.Relative.Forward.toFixed(2) + "m/s²");
+ ChangeText("Label_PFDTechInfoRelativeRight", PFD0.RawData.Accel.Accel.Relative.Right.toFixed(2) + "m/s²");
+ ChangeText("Label_PFDTechInfoRelativeUpward", PFD0.RawData.Accel.Accel.Relative.Upward.toFixed(2) + "m/s²");
+ ChangeText("Label_PFDTechInfoRelativeForwardWithGravity", PFD0.RawData.Accel.Accel.RelativeWithGravity.Forward.toFixed(2) + "m/s²");
+ ChangeText("Label_PFDTechInfoRelativeRightWithGravity", PFD0.RawData.Accel.Accel.RelativeWithGravity.Right.toFixed(2) + "m/s²");
+ ChangeText("Label_PFDTechInfoRelativeUpwardWithGravity", PFD0.RawData.Accel.Accel.RelativeWithGravity.Upward.toFixed(2) + "m/s²");
+ ChangeText("Label_PFDTechInfoAlignedForward", PFD0.RawData.Accel.Accel.Aligned.Forward.toFixed(2) + "m/s²");
+ ChangeText("Label_PFDTechInfoAlignedRight", PFD0.RawData.Accel.Accel.Aligned.Right.toFixed(2) + "m/s²");
+ ChangeText("Label_PFDTechInfoAlignedUpward", PFD0.RawData.Accel.Accel.Aligned.Upward.toFixed(2) + "m/s²");
+ ChangeText("Label_PFDTechInfoAccelPitch", PFD0.RawData.Accel.Attitude.Original.Pitch.toFixed(2) + "度");
+ ChangeText("Label_PFDTechInfoAccelRoll", PFD0.RawData.Accel.Attitude.Original.Roll.toFixed(2) + "度");
+ ChangeText("Label_PFDTechInfoAlignedPitch", PFD0.RawData.Accel.Attitude.Aligned.Pitch.toFixed(2) + "度");
+ ChangeText("Label_PFDTechInfoAlignedRoll", PFD0.RawData.Accel.Attitude.Aligned.Roll.toFixed(2) + "度");
+ ChangeText("Label_PFDTechInfoSpeedVectorForward", PFD0.RawData.Accel.Speed.Vector.Forward.toFixed(2) + "米/秒");
+ ChangeText("Label_PFDTechInfoSpeedVectorRight", PFD0.RawData.Accel.Speed.Vector.Right.toFixed(2) + "米/秒");
+ ChangeText("Label_PFDTechInfoSpeedVectorUpward", PFD0.RawData.Accel.Speed.Vector.Upward.toFixed(2) + "米/秒");
+ let NeedleAngle = 0, NeedleLength = 0;
+ NeedleAngle = Math.atan2(PFD0.RawData.Accel.Speed.Vector.Forward, PFD0.RawData.Accel.Speed.Vector.Right) / (Math.PI / 180);
+ NeedleLength = Math.sqrt(Math.pow(PFD0.RawData.Accel.Speed.Vector.Right, 2) + Math.pow(PFD0.RawData.Accel.Speed.Vector.Forward, 2)) * 5;
+ ChangeTop("Needle_PFDTechInfoSpeedVectorGraph", "calc(50% - " + NeedleLength + "px)");
+ ChangeRotate("Needle_PFDTechInfoSpeedVectorGraph", 90 - NeedleAngle);
+ ChangeHeight("Needle_PFDTechInfoSpeedVectorGraph", NeedleLength * 2 + "px");
+ ChangeText("Label_PFDTechInfoAccelSpeed", PFD0.RawData.Accel.Speed.Speed.toFixed(2) + "米/秒");
+ ChangeText("Label_PFDTechInfoAccelAltitude", PFD0.RawData.Accel.Altitude.toFixed(2) + "米");
+ if(PFD0.RawData.Accel.Interval != null) {
+ ChangeText("Label_PFDTechInfoAccelInterval", PFD0.RawData.Accel.Interval + "毫秒");
+ } else {
+ ChangeText("Label_PFDTechInfoAccelInterval", "N/A");
+ }
+ ChangeText("Label_PFDTechInfoAccelTimestamp", PFD0.RawData.Accel.Timestamp + " (+" + (PFD0.Stats.ClockTime - PFD0.RawData.Accel.Timestamp) + ")");
+
+ // Manual
+ ChangeText("Label_PFDTechInfoManualPitch", PFD0.RawData.Manual.Attitude.Pitch.toFixed(2) + "度");
+ ChangeText("Label_PFDTechInfoManualRoll", PFD0.RawData.Manual.Attitude.Roll.toFixed(2) + "度");
+ ChangeText("Label_PFDTechInfoManualSpeed", PFD0.RawData.Manual.Speed.toFixed(2) + "米/秒");
+ ChangeText("Label_PFDTechInfoManualAltitude", PFD0.RawData.Manual.Altitude.toFixed(2) + "米");
+ }
+
+ function RefreshPFD() {
+ // Call
+ ClockPFD();
+
+ // PFD
+ // Panel
+ switch(Subsystem.Display.PFDStyle) {
+ case "Default":
+ if(PFD.Flaps > 0) {
+ ChangeText("Label_PFDDefaultPanelFlapsValue", PFD.Flaps + "%");
+ } else {
+ ChangeText("Label_PFDDefaultPanelFlapsValue", Translate("FlapsUp"));
+ }
+ ChangeProgbar("ProgbarFg_PFDDefaultPanelFlaps", "Vertical", PFD.Flaps);
+ if(PFD.Attitude.IsEnabled == true) {
+ Show("Ctrl_PFDDefaultPanelAttitudeMode");
+ ChangeText("Label_PFDDefaultPanelAttitudeModeValue", Translate(PFD.Attitude.Mode));
+ } else {
+ Hide("Ctrl_PFDDefaultPanelAttitudeMode");
+ }
+ ChangeText("Label_PFDDefaultPanelSpeedModeValue", Translate(PFD.Speed.Mode));
+ ChangeText("Label_PFDDefaultPanelAltitudeModeValue", Translate(PFD.Altitude.Mode));
+ break;
+ case "Boeing":
+ case "Airbus":
+ case "HUD":
+ case "Bocchi737":
+ case "AnalogGauges":
+ case "AutomobileSpeedometer":
+ AlertSystemError("A PFD style which is still under construction was selected.");
+ break;
+ default:
+ AlertSystemError("The value of Subsystem.Display.PFDStyle \"" + Subsystem.Display.PFDStyle + "\" in function RefreshPFD is invalid.");
+ break;
+ }
+
+ // Menu
+ // Ctrl
+ if(PFD.Attitude.Mode == "Manual" || PFD.Speed.Mode == "Manual" || PFD.Altitude.Mode == "Manual") {
+ Show("Ctrl_PFDManualManeuver");
+ if(PFD.Attitude.Mode == "Manual") {
+ ChangeDisabled("Button_PFDPitchDown", false);
+ ChangeDisabled("Button_PFDPitchUp", false);
+ ChangeDisabled("Button_PFDRollLeft", false);
+ ChangeDisabled("Button_PFDRollRight", false);
+ } else {
+ ChangeDisabled("Button_PFDPitchDown", true);
+ ChangeDisabled("Button_PFDPitchUp", true);
+ ChangeDisabled("Button_PFDRollLeft", true);
+ ChangeDisabled("Button_PFDRollRight", true);
+ }
+ if(PFD.Speed.Mode == "Manual") {
+ ChangeDisabled("Button_PFDSpeedUp", false);
+ ChangeDisabled("Button_PFDSpeedDown", false);
+ } else {
+ ChangeDisabled("Button_PFDSpeedUp", true);
+ ChangeDisabled("Button_PFDSpeedDown", true);
+ }
+ if(PFD.Altitude.Mode == "Manual") {
+ ChangeDisabled("Button_PFDAltitudeUp", false);
+ ChangeDisabled("Button_PFDAltitudeDown", false);
+ } else {
+ ChangeDisabled("Button_PFDAltitudeUp", true);
+ ChangeDisabled("Button_PFDAltitudeDown", true);
+ }
+ } else {
+ Hide("Ctrl_PFDManualManeuver");
+ }
+ if(PFD.Flaps > 0) {
+ ChangeText("Label_PFDFlaps", PFD.Flaps + "%");
+ } else {
+ ChangeText("Label_PFDFlaps", Translate("FlapsUp"));
+ }
+ ChangeValue("Slider_PFDFlaps", PFD.Flaps);
+
+ // Options
+ ChangeChecked("Checkbox_PFDOptionsEnableAttitudeIndicator", PFD.Attitude.IsEnabled);
+ if(PFD.Attitude.IsEnabled == true) {
+ Show("Ctrl_PFDOptionsAttitudeMode");
+ } else {
+ Hide("Ctrl_PFDOptionsAttitudeMode");
+ }
+ ChangeValue("Combobox_PFDOptionsAttitudeMode", PFD.Attitude.Mode);
+ ChangeValue("Combobox_PFDOptionsSpeedMode", PFD.Speed.Mode);
+ ChangeValue("Combobox_PFDOptionsAltitudeMode", PFD.Altitude.Mode);
+ ChangeChecked("Checkbox_PFDOptionsEnableDME", PFD.DME.IsEnabled);
+ ChangeValue("Combobox_PFDOptionsFlightMode", PFD.FlightMode.FlightMode);
+ ChangeChecked("Checkbox_PFDOptionsFlipPFDVertically", Subsystem.Display.FlipPFDVertically);
+ ChangeChecked("Checkbox_PFDOptionsKeepScreenOn", Subsystem.Display.KeepScreenOn);
+
+ // Settings
+ // Attitude
+ ChangeChecked("Checkbox_SettingsEnableAttitudeIndicator", PFD.Attitude.IsEnabled);
+ if(PFD.Attitude.IsEnabled == true) {
+ Show("Ctrl_SettingsAttitudeMode");
+ Show("Label_SettingsAttitudeOffset");
+ Show("Label_SettingsAttitudeOffsetInfo");
+ Show("Ctrl_SettingsAttitudeOffsetPitch");
+ Show("Ctrl_SettingsAttitudeOffsetRoll");
+ ChangeValue("Combobox_SettingsAttitudeMode", PFD.Attitude.Mode);
+ ChangeValue("Textbox_SettingsAttitudeOffsetPitch", PFD.Attitude.Offset.Pitch);
+ ChangeValue("Textbox_SettingsAttitudeOffsetRoll", PFD.Attitude.Offset.Roll);
+ } else {
+ Hide("Ctrl_SettingsAttitudeMode");
+ Hide("Label_SettingsAttitudeOffset");
+ Hide("Label_SettingsAttitudeOffsetInfo");
+ Hide("Ctrl_SettingsAttitudeOffsetPitch");
+ Hide("Ctrl_SettingsAttitudeOffsetRoll");
+ }
+
+ // Speed
+ ChangeValue("Combobox_SettingsSpeedMode", PFD.Speed.Mode);
+ ChangeValue("Combobox_SettingsIASAlgorithm", PFD.Speed.IASAlgorithm);
+ ChangeValue("Textbox_SettingsAirportTemperatureDeparture", ConvertUnit(PFD.Speed.AirportTemperature.Departure, "Kelvin", Subsystem.I18n.TemperatureUnit).toFixed(0));
+ ChangeValue("Textbox_SettingsAirportTemperatureArrival", ConvertUnit(PFD.Speed.AirportTemperature.Arrival, "Kelvin", Subsystem.I18n.TemperatureUnit).toFixed(0));
+ if(PFD.Speed.AirportTemperature.Departure != PFD.Speed.AirportTemperature.Arrival) {
+ ChangeDisabled("Button_SettingsAirportTemperatureSwap", false);
+ } else {
+ ChangeDisabled("Button_SettingsAirportTemperatureSwap", true);
+ }
+ switch(PFD.Speed.IASAlgorithm) {
+ case "SimpleAlgorithm":
+ Hide("Ctrl_SettingsRelativeHumidity");
+ Hide("Ctrl_SettingsRelativeHumiditySwap");
+ Hide("Ctrl_SettingsQNH");
+ Hide("Ctrl_SettingsQNHSwap");
+ break;
+ case "AdvancedAlgorithmA":
+ case "AdvancedAlgorithmB":
+ Show("Ctrl_SettingsRelativeHumidity");
+ Show("Ctrl_SettingsRelativeHumiditySwap");
+ Show("Ctrl_SettingsQNH");
+ Show("Ctrl_SettingsQNHSwap");
+ ChangeValue("Textbox_SettingsRelativeHumidityDeparture", PFD.Speed.RelativeHumidity.Departure);
+ ChangeValue("Textbox_SettingsRelativeHumidityArrival", PFD.Speed.RelativeHumidity.Arrival);
+ if(PFD.Speed.RelativeHumidity.Departure != PFD.Speed.RelativeHumidity.Arrival) {
+ ChangeDisabled("Button_SettingsRelativeHumiditySwap", false);
+ } else {
+ ChangeDisabled("Button_SettingsRelativeHumiditySwap", true);
+ }
+ ChangeValue("Textbox_SettingsQNHDeparture", ConvertUnit(PFD.Speed.QNH.Departure, "Hectopascal", Subsystem.I18n.PressureUnit).toFixed(2));
+ ChangeValue("Textbox_SettingsQNHArrival", ConvertUnit(PFD.Speed.QNH.Arrival, "Hectopascal", Subsystem.I18n.PressureUnit).toFixed(2));
+ if(PFD.Speed.QNH.Departure != PFD.Speed.QNH.Arrival) {
+ ChangeDisabled("Button_SettingsQNHSwap", false);
+ } else {
+ ChangeDisabled("Button_SettingsQNHSwap", true);
+ }
+ break;
+ default:
+ AlertSystemError("The value of PFD.Speed.IASAlgorithm \"" + PFD.Speed.IASAlgorithm + "\" in function RefreshPFD is invalid.");
+ break;
+ }
+ ChangeValue("Textbox_SettingsWindDirection", PFD.Speed.Wind.Direction);
+ ChangeValue("Textbox_SettingsWindSpeed", ConvertUnit(PFD.Speed.Wind.Speed, "MeterPerSec", Subsystem.I18n.SpeedUnit).toFixed(0));
+ if(PFD.Speed.Wind.Direction > 0 || PFD.Speed.Wind.Speed > 0) {
+ ChangeDisabled("Button_SettingsWindReset", false);
+ } else {
+ ChangeDisabled("Button_SettingsWindReset", true);
+ }
+ switch(true) {
+ case PFD.Speed.SpeedLimit.Min == 0 && PFD.Speed.SpeedLimit.MaxOnFlapsUp == 277.5 && PFD.Speed.SpeedLimit.MaxOnFlapsFull == 277.5:
+ ChangeValue("Combobox_SettingsSpeedLimitPreset", "NoSpeedLimits");
+ break;
+ case PFD.Speed.SpeedLimit.Min == 61.728 && PFD.Speed.SpeedLimit.MaxOnFlapsUp == 174.896 && PFD.Speed.SpeedLimit.MaxOnFlapsFull == 83.3328:
+ ChangeValue("Combobox_SettingsSpeedLimitPreset", "Boeing737-800");
+ break;
+ case PFD.Speed.SpeedLimit.Min == 61.728 && PFD.Speed.SpeedLimit.MaxOnFlapsUp == 180.04 && PFD.Speed.SpeedLimit.MaxOnFlapsFull == 91.0488:
+ ChangeValue("Combobox_SettingsSpeedLimitPreset", "AirbusA320");
+ break;
+ case PFD.Speed.SpeedLimit.Min == 0 && PFD.Speed.SpeedLimit.MaxOnFlapsUp == 33.33333 && PFD.Speed.SpeedLimit.MaxOnFlapsFull == 33.33333:
+ ChangeValue("Combobox_SettingsSpeedLimitPreset", "GroundVehicle");
+ break;
+ default:
+ ChangeValue("Combobox_SettingsSpeedLimitPreset", "");
+ break;
+ }
+ ChangeValue("Textbox_SettingsSpeedLimitMin", ConvertUnit(PFD.Speed.SpeedLimit.Min, "MeterPerSec", Subsystem.I18n.SpeedUnit).toFixed(0));
+ ChangeValue("Textbox_SettingsSpeedLimitMaxOnFlapsUp", ConvertUnit(PFD.Speed.SpeedLimit.MaxOnFlapsUp, "MeterPerSec", Subsystem.I18n.SpeedUnit).toFixed(0));
+ ChangeValue("Textbox_SettingsSpeedLimitMaxOnFlapsFull", ConvertUnit(PFD.Speed.SpeedLimit.MaxOnFlapsFull, "MeterPerSec", Subsystem.I18n.SpeedUnit).toFixed(0));
+
+ // Altitude
+ ChangeValue("Combobox_SettingsAltitudeMode", PFD.Altitude.Mode);
+ ChangeValue("Textbox_SettingsAirportElevationDeparture", ConvertUnit(PFD.Altitude.AirportElevation.Departure, "Meter", Subsystem.I18n.AltitudeUnit).toFixed(0));
+ ChangeValue("Textbox_SettingsAirportElevationArrival", ConvertUnit(PFD.Altitude.AirportElevation.Arrival, "Meter", Subsystem.I18n.AltitudeUnit).toFixed(0));
+ if(PFD.Altitude.AirportElevation.Departure != PFD.Altitude.AirportElevation.Arrival) {
+ ChangeDisabled("Button_SettingsAirportElevationSwap", false);
+ } else {
+ ChangeDisabled("Button_SettingsAirportElevationSwap", true);
+ }
+ ChangeValue("Textbox_SettingsDecisionHeight", ConvertUnit(PFD.Altitude.DecisionHeight, "Meter", Subsystem.I18n.AltitudeUnit).toFixed(0));
+
+ // DME
+ ChangeChecked("Checkbox_SettingsEnableDME", PFD.DME.IsEnabled);
+ if(PFD.DME.IsEnabled == true) {
+ Show("Label_SettingsAirportCoordinates");
+ Show("Label_SettingsAirportCoordinatesInfo");
+ Show("Ctrl_SettingsAirportCoordinatesDeparture");
+ Show("Ctrl_SettingsAirportCoordinatesArrival");
+ Show("Ctrl_SettingsAirportCoordinatesSwap");
+ ChangeValue("Textbox_SettingsAirportCoordinatesDepartureLat", PFD.DME.AirportCoordinates.Departure.Lat.toFixed(5));
+ ChangeValue("Textbox_SettingsAirportCoordinatesDepartureLon", PFD.DME.AirportCoordinates.Departure.Lon.toFixed(5));
+ ChangeValue("Textbox_SettingsAirportCoordinatesArrivalLat", PFD.DME.AirportCoordinates.Arrival.Lat.toFixed(5));
+ ChangeValue("Textbox_SettingsAirportCoordinatesArrivalLon", PFD.DME.AirportCoordinates.Arrival.Lon.toFixed(5));
+ if(PFD.DME.AirportCoordinates.Departure.Lat != PFD.DME.AirportCoordinates.Arrival.Lat || PFD.DME.AirportCoordinates.Departure.Lon != PFD.DME.AirportCoordinates.Arrival.Lon) {
+ ChangeDisabled("Button_SettingsAirportCoordinatesSwap", false);
+ } else {
+ ChangeDisabled("Button_SettingsAirportCoordinatesSwap", true);
+ }
+ } else {
+ Hide("Label_SettingsAirportCoordinates");
+ Hide("Label_SettingsAirportCoordinatesInfo");
+ Hide("Ctrl_SettingsAirportCoordinatesDeparture");
+ Hide("Ctrl_SettingsAirportCoordinatesArrival");
+ Hide("Ctrl_SettingsAirportCoordinatesSwap");
+ }
+
+ // Flight mode
+ ChangeValue("Combobox_SettingsFlightMode", PFD.FlightMode.FlightMode);
+ ChangeChecked("Checkbox_SettingsAutoSwitchFlightModeAndSwapAirportData", PFD.FlightMode.AutoSwitchFlightModeAndSwapAirportData);
+
+ // Save user data
+ localStorage.setItem("GPSPFD_PFD", JSON.stringify(PFD));
+ }
+ function RefreshGPSData(GeolocationAPIData) { // https://www.freecodecamp.org/news/how-to-use-the-javascript-geolocation-api/
+ // GPS data
+ PFD0.RawData.GPS = {
+ Position: {
+ Lat: GeolocationAPIData.coords.latitude,
+ Lon: GeolocationAPIData.coords.longitude,
+ Accuracy: GeolocationAPIData.coords.accuracy
+ },
+ Speed: GeolocationAPIData.coords.speed,
+ Altitude: {
+ Altitude: GeolocationAPIData.coords.altitude,
+ Accuracy: GeolocationAPIData.coords.altitudeAccuracy
+ },
+ Heading: GeolocationAPIData.coords.heading,
+ Timestamp: GeolocationAPIData.timestamp
+ };
+
+ // Replace accel data
+ switch(PFD.Speed.Mode) {
+ case "GPS":
+ case "Accel":
+ case "Manual":
+ break;
+ case "DualChannel":
+ if(PFD0.RawData.GPS.Speed != null) {
+ let ProportionVertor = 0;
+ if(PFD0.RawData.Accel.Speed.Speed > 0) {
+ ProportionVertor = {
+ Forward: PFD0.RawData.Accel.Speed.Vector.Forward / PFD0.RawData.Accel.Speed.Speed,
+ Right: PFD0.RawData.Accel.Speed.Vector.Right / PFD0.RawData.Accel.Speed.Speed,
+ Upward: PFD0.RawData.Accel.Speed.Vector.Upward / PFD0.RawData.Accel.Speed.Speed
+ };
+ } else {
+ ProportionVertor = {
+ Forward: 0, Right: 0, Upward: 0
+ };
+ }
+ PFD0.RawData.Accel.Speed.Speed = PFD0.RawData.GPS.Speed;
+ PFD0.RawData.Accel.Speed.Vector = {
+ Forward: PFD0.RawData.Accel.Speed.Speed * ProportionVertor.Forward,
+ Right: PFD0.RawData.Accel.Speed.Speed * ProportionVertor.Right,
+ Upward: PFD0.RawData.Accel.Speed.Speed * ProportionVertor.Upward
+ };
+ }
+ break;
+ default:
+ AlertSystemError("The value of PFD.Speed.Mode \"" + PFD.Speed.Mode + "\" in function RefreshGPSData is invalid.");
+ break;
+ }
+ switch(PFD.Altitude.Mode) {
+ case "GPS":
+ case "Accel":
+ case "Manual":
+ break;
+ case "DualChannel":
+ if(PFD0.RawData.GPS.Altitude.Altitude != null) {
+ PFD0.RawData.Accel.Altitude = PFD0.RawData.GPS.Altitude.Altitude;
+ }
+ break;
+ default:
+ AlertSystemError("The value of PFD.Altitude.Mode \"" + PFD.Altitude.Mode + "\" in function RefreshGPSData is invalid.");
+ break;
+ }
+ }
+ function RefreshAccelData(DeviceMotionAPIData) { // https://medium.com/@kamresh485/understanding-the-device-motion-event-api-0ce5b3e252f1
+ // Absolute accel
+ PFD0.RawData.Accel.Accel.Absolute = {
+ X: DeviceMotionAPIData.acceleration.x,
+ Y: DeviceMotionAPIData.acceleration.y,
+ Z: DeviceMotionAPIData.acceleration.z
+ };
+ PFD0.RawData.Accel.Accel.AbsoluteWithGravity = {
+ X: DeviceMotionAPIData.accelerationIncludingGravity.x,
+ Y: DeviceMotionAPIData.accelerationIncludingGravity.y,
+ Z: DeviceMotionAPIData.accelerationIncludingGravity.z
+ };
+
+ // Interval
+ PFD0.RawData.Accel.Interval = DeviceMotionAPIData.interval;
+
+ // Relative accel
+ switch(screen.orientation.type) {
+ case "landscape-primary":
+ PFD0.RawData.Accel.Accel.Relative = {
+ Forward: PFD0.RawData.Accel.Accel.Absolute.Z,
+ Right: PFD0.RawData.Accel.Accel.Absolute.Y,
+ Upward: -PFD0.RawData.Accel.Accel.Absolute.X
+ };
+ PFD0.RawData.Accel.Accel.RelativeWithGravity = {
+ Forward: PFD0.RawData.Accel.Accel.AbsoluteWithGravity.Z,
+ Right: PFD0.RawData.Accel.Accel.AbsoluteWithGravity.Y,
+ Upward: -PFD0.RawData.Accel.Accel.AbsoluteWithGravity.X
+ };
+ break;
+ case "landscape-secondary":
+ PFD0.RawData.Accel.Accel.Relative = {
+ Forward: PFD0.RawData.Accel.Accel.Absolute.Z,
+ Right: -PFD0.RawData.Accel.Accel.Absolute.Y,
+ Upward: PFD0.RawData.Accel.Accel.Absolute.X
+ };
+ PFD0.RawData.Accel.Accel.RelativeWithGravity = {
+ Forward: PFD0.RawData.Accel.Accel.AbsoluteWithGravity.Z,
+ Right: -PFD0.RawData.Accel.Accel.AbsoluteWithGravity.Y,
+ Upward: PFD0.RawData.Accel.Accel.AbsoluteWithGravity.X
+ };
+ break;
+ case "portrait-primary":
+ PFD0.RawData.Accel.Accel.Relative = {
+ Forward: PFD0.RawData.Accel.Accel.Absolute.Z,
+ Right: -PFD0.RawData.Accel.Accel.Absolute.X,
+ Upward: -PFD0.RawData.Accel.Accel.Absolute.Y
+ };
+ PFD0.RawData.Accel.Accel.RelativeWithGravity = {
+ Forward: PFD0.RawData.Accel.Accel.AbsoluteWithGravity.Z,
+ Right: -PFD0.RawData.Accel.Accel.AbsoluteWithGravity.X,
+ Upward: -PFD0.RawData.Accel.Accel.AbsoluteWithGravity.Y
+ };
+ break;
+ case "portrait-secondary":
+ PFD0.RawData.Accel.Accel.Relative = {
+ Forward: PFD0.RawData.Accel.Accel.Absolute.Z,
+ Right: PFD0.RawData.Accel.Accel.Absolute.X,
+ Upward: PFD0.RawData.Accel.Accel.Absolute.Y
+ };
+ PFD0.RawData.Accel.Accel.RelativeWithGravity = {
+ Forward: PFD0.RawData.Accel.Accel.AbsoluteWithGravity.Z,
+ Right: PFD0.RawData.Accel.Accel.AbsoluteWithGravity.X,
+ Upward: PFD0.RawData.Accel.Accel.AbsoluteWithGravity.Y
+ };
+ break;
+ default:
+ AlertSystemError("The value of screen.orientation.type \"" + screen.orientation.type + "\" in function RefreshAccelData is invalid.");
+ break;
+ }
+
+ // Attitude
+ PFD0.RawData.Accel.Attitude.Original = CalcAttitude(PFD0.RawData.Accel.Accel.Relative, PFD0.RawData.Accel.Accel.RelativeWithGravity);
+ PFD0.RawData.Accel.Attitude.Aligned = {
+ Pitch: PFD0.RawData.Accel.Attitude.Original.Pitch + PFD.Attitude.Offset.Pitch,
+ Roll: PFD0.RawData.Accel.Attitude.Original.Roll + PFD.Attitude.Offset.Roll
+ };
+
+ // Aligned accel
+ // Convert to opposite and align orientation
+ PFD0.RawData.Accel.Accel.Aligned = {
+ Forward: -PFD0.RawData.Accel.Accel.Relative.Forward * Math.cos(Math.abs(PFD0.RawData.Accel.Attitude.Original.Pitch * (Math.PI / 180))),
+ Right: -PFD0.RawData.Accel.Accel.Relative.Right * Math.cos(Math.abs(PFD0.RawData.Accel.Attitude.Original.Roll * (Math.PI / 180))),
+ Upward: -PFD0.RawData.Accel.Accel.Relative.Upward * Math.cos(Math.abs(PFD0.RawData.Accel.Attitude.Original.Roll * (Math.PI / 180))) * Math.cos(Math.abs(PFD0.RawData.Accel.Attitude.Original.Pitch * (Math.PI / 180)))
+ };
+
+ // Reduce sensitivity to prevent incorrect speed inflation
+ if(Math.abs(PFD0.RawData.Accel.Accel.Aligned.Forward) < 1) {
+ PFD0.RawData.Accel.Accel.Aligned.Forward = 0;
+ }
+ if(Math.abs(PFD0.RawData.Accel.Accel.Aligned.Right) < 1) {
+ PFD0.RawData.Accel.Accel.Aligned.Right = 0;
+ }
+ if(Math.abs(PFD0.RawData.Accel.Accel.Aligned.Upward) < 1) {
+ PFD0.RawData.Accel.Accel.Aligned.Upward = 0;
+ }
+
+ // Speed and altitude
+ PFD0.RawData.Accel.Speed.Vector = {
+ Forward: PFD0.RawData.Accel.Speed.Vector.Forward + PFD0.RawData.Accel.Accel.Aligned.Forward * (PFD0.RawData.Accel.Interval / 1000),
+ Right: PFD0.RawData.Accel.Speed.Vector.Right + PFD0.RawData.Accel.Accel.Aligned.Right * (PFD0.RawData.Accel.Interval / 1000),
+ Upward: PFD0.RawData.Accel.Speed.Vector.Upward + PFD0.RawData.Accel.Accel.Aligned.Upward * (PFD0.RawData.Accel.Interval / 1000)
+ }
+ PFD0.RawData.Accel.Speed.Speed = Math.sqrt(Math.pow(PFD0.RawData.Accel.Speed.Vector.Forward, 2) + Math.pow(PFD0.RawData.Accel.Speed.Vector.Right, 2) + Math.pow(PFD0.RawData.Accel.Speed.Vector.Upward, 2));
+ PFD0.RawData.Accel.Altitude += PFD0.RawData.Accel.Speed.Vector.Upward * (PFD0.RawData.Accel.Interval / 1000);
+
+ // Timestamp
+ PFD0.RawData.Accel.Timestamp = Date.now();
+ }
+
+// Cmds
+ // PFD
+ // Menu
+ function ForceHidePFDMenu() {
+ setTimeout(function() { // Because the close button is inside the window, clicking the close button also triggers showing the window. So a delay should be set here.
+ HideToCorner("Window_PFDMenu");
+ }, 40);
+ }
+ function TogglePFDMenuCollapse(Name) {
+ if(IsClassContained("CtrlGroup_PFD" + Name, "Hidden") == true) {
+ Show("CtrlGroup_PFD" + Name);
+ } else {
+ Hide("CtrlGroup_PFD" + Name);
+ }
+ }
+ // Ctrl
+ function PitchDown() {
+ PFD0.RawData.Manual.Attitude.Pitch -= 0.5;
+ if(PFD0.RawData.Manual.Attitude.Pitch < -90) {
+ PFD0.RawData.Manual.Attitude.Pitch = -90;
+ }
+ if(PFD0.RawData.Manual.Attitude.Pitch > 90) {
+ PFD0.RawData.Manual.Attitude.Pitch = 90;
+ }
+ RefreshPFD();
+ }
+ function PitchUp() {
+ PFD0.RawData.Manual.Attitude.Pitch += 0.5;
+ if(PFD0.RawData.Manual.Attitude.Pitch < -90) {
+ PFD0.RawData.Manual.Attitude.Pitch = -90;
+ }
+ if(PFD0.RawData.Manual.Attitude.Pitch > 90) {
+ PFD0.RawData.Manual.Attitude.Pitch = 90;
+ }
+ RefreshPFD();
+ }
+ function RollLeft() {
+ PFD0.RawData.Manual.Attitude.Roll -= 0.5;
+ if(PFD0.RawData.Manual.Attitude.Roll < -180) {
+ PFD0.RawData.Manual.Attitude.Roll += 360;
+ }
+ if(PFD0.RawData.Manual.Attitude.Roll > 180) {
+ PFD0.RawData.Manual.Attitude.Roll -= 360;
+ }
+ RefreshPFD();
+ }
+ function RollRight() {
+ PFD0.RawData.Manual.Attitude.Roll += 0.5;
+ if(PFD0.RawData.Manual.Attitude.Roll < -180) {
+ PFD0.RawData.Manual.Attitude.Roll += 360;
+ }
+ if(PFD0.RawData.Manual.Attitude.Roll > 180) {
+ PFD0.RawData.Manual.Attitude.Roll -= 360;
+ }
+ RefreshPFD();
+ }
+ function SpeedUp() {
+ PFD0.RawData.Manual.Speed += 0.5;
+ if(PFD0.RawData.Manual.Speed < 0) {
+ PFD0.RawData.Manual.Speed = 0;
+ }
+ if(PFD0.RawData.Manual.Speed > 277.5) {
+ PFD0.RawData.Manual.Speed = 277.5;
+ }
+ RefreshPFD();
+ }
+ function SpeedDown() {
+ PFD0.RawData.Manual.Speed -= 0.5;
+ if(PFD0.RawData.Manual.Speed < 0) {
+ PFD0.RawData.Manual.Speed = 0;
+ }
+ if(PFD0.RawData.Manual.Speed > 277.5) {
+ PFD0.RawData.Manual.Speed = 277.5;
+ }
+ RefreshPFD();
+ }
+ function AltitudeUp() {
+ PFD0.RawData.Manual.Altitude += 2;
+ if(PFD0.RawData.Manual.Altitude < -609.6) {
+ PFD0.RawData.Manual.Altitude = -609.6;
+ }
+ if(PFD0.RawData.Manual.Altitude > 15240) {
+ PFD0.RawData.Manual.Altitude = 15240;
+ }
+ RefreshPFD();
+ }
+ function AltitudeDown() {
+ PFD0.RawData.Manual.Altitude -= 2;
+ if(PFD0.RawData.Manual.Altitude < -609.6) {
+ PFD0.RawData.Manual.Altitude = -609.6;
+ }
+ if(PFD0.RawData.Manual.Altitude > 15240) {
+ PFD0.RawData.Manual.Altitude = 15240;
+ }
+ RefreshPFD();
+ }
+ function SetFlaps() {
+ PFD.Flaps = ReadValue("Slider_PFDFlaps");
+ RefreshPFD();
+ }
+ function ResetAccelSpeed() {
+ PFD0.RawData.Accel.Speed.Vector = {
+ Forward: 0, Right: 0, Upward: 0
+ };
+ RefreshPFD();
+ }
+
+ // Options
+ function SetEnableAttitudeIndicatorAtPFDOptions() {
+ PFD.Attitude.IsEnabled = IsChecked("Checkbox_PFDOptionsEnableAttitudeIndicator");
+ RefreshPFD();
+ }
+ function SetAttitudeModeAtPFDOptions() {
+ PFD.Attitude.Mode = ReadValue("Combobox_PFDOptionsAttitudeMode");
+ RefreshPFD();
+ }
+ function SetSpeedModeAtPFDOptions() {
+ PFD.Speed.Mode = ReadValue("Combobox_PFDOptionsSpeedMode");
+ RefreshPFD();
+ }
+ function SetAltitudeModeAtPFDOptions() {
+ PFD.Altitude.Mode = ReadValue("Combobox_PFDOptionsAltitudeMode");
+ RefreshPFD();
+ }
+ function SetEnableDMEAtPFDOptions() {
+ PFD.DME.IsEnabled = IsChecked("Checkbox_PFDOptionsEnableDME");
+ RefreshPFD();
+ }
+ function SetFlightModeAtPFDOptions() {
+ PFD.FlightMode.FlightMode = ReadValue("Combobox_PFDOptionsFlightMode");
+ PFD0.Stats.FlightModeTimestamp = Date.now();
+ RefreshPFD();
+ }
+ function SetFlipPFDVerticallyAtPFDOptions() {
+ Subsystem.Display.FlipPFDVertically = IsChecked("Checkbox_PFDOptionsFlipPFDVertically");
+ RefreshSubsystem();
+ }
+ function SetKeepScreenOnAtPFDOptions() {
+ Subsystem.Display.KeepScreenOn = IsChecked("Checkbox_PFDOptionsKeepScreenOn");
+ RefreshSubsystem();
+ }
+
+ // Settings
+ // Attitude
+ function SetEnableAttitudeIndicator() {
+ PFD.Attitude.IsEnabled = IsChecked("Checkbox_SettingsEnableAttitudeIndicator");
+ RefreshPFD();
+ }
+ function SetAttitudeMode() {
+ PFD.Attitude.Mode = ReadValue("Combobox_SettingsAttitudeMode");
+ RefreshPFD();
+ }
+ function SetAttitudeOffsetPitch() {
+ PFD.Attitude.Offset.Pitch = Math.trunc(ReadValue("Textbox_SettingsAttitudeOffsetPitch"));
+ if(PFD.Attitude.Offset.Pitch < -90) {
+ PFD.Attitude.Offset.Pitch = -90;
+ }
+ if(PFD.Attitude.Offset.Pitch > 90) {
+ PFD.Attitude.Offset.Pitch = 90;
+ }
+ RefreshPFD();
+ }
+ function SetAttitudeOffsetRoll() {
+ PFD.Attitude.Offset.Roll = Math.trunc(ReadValue("Textbox_SettingsAttitudeOffsetRoll"));
+ if(PFD.Attitude.Offset.Roll < -90) {
+ PFD.Attitude.Offset.Roll = -90;
+ }
+ if(PFD.Attitude.Offset.Roll > 90) {
+ PFD.Attitude.Offset.Roll = 90;
+ }
+ RefreshPFD();
+ }
+
+ // Speed
+ function SetSpeedMode() {
+ PFD.Speed.Mode = ReadValue("Combobox_SettingsSpeedMode");
+ RefreshPFD();
+ }
+ function SetIASAlgorithm() {
+ PFD.Speed.IASAlgorithm = ReadValue("Combobox_SettingsIASAlgorithm");
+ RefreshPFD();
+ }
+ function SetAirportTemperatureDeparture() {
+ PFD.Speed.AirportTemperature.Departure = ConvertUnit(Math.trunc(ReadValue("Textbox_SettingsAirportTemperatureDeparture")), Subsystem.I18n.TemperatureUnit, "Kelvin");
+ if(PFD.Speed.AirportTemperature.Departure < 223.15) {
+ PFD.Speed.AirportTemperature.Departure = 223.15;
+ }
+ if(PFD.Speed.AirportTemperature.Departure > 323.15) {
+ PFD.Speed.AirportTemperature.Departure = 323.15;
+ }
+ RefreshPFD();
+ }
+ function SetAirportTemperatureArrival() {
+ PFD.Speed.AirportTemperature.Arrival = ConvertUnit(Math.trunc(ReadValue("Textbox_SettingsAirportTemperatureArrival")), Subsystem.I18n.TemperatureUnit, "Kelvin");
+ if(PFD.Speed.AirportTemperature.Arrival < 223.15) {
+ PFD.Speed.AirportTemperature.Arrival = 223.15;
+ }
+ if(PFD.Speed.AirportTemperature.Arrival > 323.15) {
+ PFD.Speed.AirportTemperature.Arrival = 323.15;
+ }
+ RefreshPFD();
+ }
+ function SwapAirportTemperatures() {
+ let Swapper = PFD.Speed.AirportTemperature.Departure;
+ PFD.Speed.AirportTemperature.Departure = PFD.Speed.AirportTemperature.Arrival;
+ PFD.Speed.AirportTemperature.Arrival = Swapper;
+ RefreshPFD();
+ }
+ function SetRelativeHumidityDeparture() {
+ PFD.Speed.RelativeHumidity.Departure = Math.trunc(ReadValue("Textbox_SettingsRelativeHumidityDeparture"));
+ if(PFD.Speed.RelativeHumidity.Departure < 0) {
+ PFD.Speed.RelativeHumidity.Departure = 0;
+ }
+ if(PFD.Speed.RelativeHumidity.Departure > 100) {
+ PFD.Speed.RelativeHumidity.Departure = 100;
+ }
+ RefreshPFD();
+ }
+ function SetRelativeHumidityArrival() {
+ PFD.Speed.RelativeHumidity.Arrival = Math.trunc(ReadValue("Textbox_SettingsRelativeHumidityArrival"));
+ if(PFD.Speed.RelativeHumidity.Arrival < 0) {
+ PFD.Speed.RelativeHumidity.Arrival = 0;
+ }
+ if(PFD.Speed.RelativeHumidity.Arrival > 100) {
+ PFD.Speed.RelativeHumidity.Arrival = 100;
+ }
+ RefreshPFD();
+ }
+ function SwapRelativeHumidity() {
+ let Swapper = PFD.Speed.RelativeHumidity.Departure;
+ PFD.Speed.RelativeHumidity.Departure = PFD.Speed.RelativeHumidity.Arrival;
+ PFD.Speed.RelativeHumidity.Arrival = Swapper;
+ RefreshPFD();
+ }
+ function SetQNHDeparture() {
+ PFD.Speed.QNH.Departure = ConvertUnit(Math.trunc(ReadValue("Textbox_SettingsQNHDeparture") * 100) / 100, Subsystem.I18n.PressureUnit, "Hectopascal");
+ if(PFD.Speed.QNH.Departure < 900) {
+ PFD.Speed.QNH.Departure = 900;
+ }
+ if(PFD.Speed.QNH.Departure > 1100) {
+ PFD.Speed.QNH.Departure = 1100;
+ }
+ RefreshPFD();
+ }
+ function SetQNHArrival() {
+ PFD.Speed.QNH.Arrival = ConvertUnit(Math.trunc(ReadValue("Textbox_SettingsQNHArrival") * 100) / 100, Subsystem.I18n.PressureUnit, "Hectopascal");
+ if(PFD.Speed.QNH.Arrival < 900) {
+ PFD.Speed.QNH.Arrival = 900;
+ }
+ if(PFD.Speed.QNH.Arrival > 1100) {
+ PFD.Speed.QNH.Arrival = 1100;
+ }
+ RefreshPFD();
+ }
+ function SwapQNHs() {
+ let Swapper = PFD.Speed.QNH.Departure;
+ PFD.Speed.QNH.Departure = PFD.Speed.QNH.Arrival;
+ PFD.Speed.QNH.Arrival = Swapper;
+ RefreshPFD();
+ }
+ function SetWindDirection() {
+ PFD.Speed.Wind.Direction = Math.trunc(ReadValue("Textbox_SettingsWindDirection"));
+ if(PFD.Speed.Wind.Direction < 0) {
+ PFD.Speed.Wind.Direction = 0;
+ }
+ if(PFD.Speed.Wind.Direction > 359) {
+ PFD.Speed.Wind.Direction = 359;
+ }
+ RefreshPFD();
+ }
+ function SetWindSpeed() {
+ PFD.Speed.Wind.Speed = ConvertUnit(Math.trunc(ReadValue("Textbox_SettingsWindSpeed")), Subsystem.I18n.SpeedUnit, "MeterPerSec");
+ if(PFD.Speed.Wind.Speed < 0) {
+ PFD.Speed.Wind.Speed = 0;
+ }
+ if(PFD.Speed.Wind.Speed > 277.5) {
+ PFD.Speed.Wind.Speed = 277.5;
+ }
+ RefreshPFD();
+ }
+ function ResetWind() {
+ PFD.Speed.Wind = {
+ Direction: 0, Speed: 0
+ };
+ RefreshPFD();
+ }
+ function SetSpeedLimitPreset() {
+ switch(ReadValue("Combobox_SettingsSpeedLimitPreset")) {
+ case "NoSpeedLimits":
+ PFD.Speed.SpeedLimit = {
+ Min: 0, MaxOnFlapsUp: 277.5, MaxOnFlapsFull: 277.5
+ };
+ break;
+ case "Boeing737-800":
+ PFD.Speed.SpeedLimit = {
+ Min: 61.728, MaxOnFlapsUp: 174.896, MaxOnFlapsFull: 83.3328
+ };
+ break;
+ case "AirbusA320":
+ PFD.Speed.SpeedLimit = {
+ Min: 61.728, MaxOnFlapsUp: 180.04, MaxOnFlapsFull: 91.0488
+ };
+ break;
+ case "GroundVehicle":
+ PFD.Speed.SpeedLimit = {
+ Min: 0, MaxOnFlapsUp: 33.33333, MaxOnFlapsFull: 33.33333
+ };
+ break;
+ default:
+ AlertSystemError("The value of ReadValue(\"Combobox_SettingsSpeedLimitPreset\") \"" + ReadValue("Combobox_SettingsSpeedLimitPreset") + "\" in function SetSpeedLimitPreset is invalid.");
+ break;
+ }
+ RefreshPFD();
+ }
+ function SetSpeedLimitMin() {
+ PFD.Speed.SpeedLimit.Min = ConvertUnit(Math.trunc(ReadValue("Textbox_SettingsSpeedLimitMin")), Subsystem.I18n.SpeedUnit, "MeterPerSec");
+ if(PFD.Speed.SpeedLimit.Min < 0) {
+ PFD.Speed.SpeedLimit.Min = 0;
+ }
+ if(PFD.Speed.SpeedLimit.Min > 272.5) {
+ PFD.Speed.SpeedLimit.Min = 272.5;
+ }
+ if(PFD.Speed.SpeedLimit.Min > PFD.Speed.SpeedLimit.MaxOnFlapsFull - 5) {
+ PFD.Speed.SpeedLimit.MaxOnFlapsFull = PFD.Speed.SpeedLimit.Min + 5;
+ if(PFD.Speed.SpeedLimit.MaxOnFlapsFull > PFD.Speed.SpeedLimit.MaxOnFlapsUp) {
+ PFD.Speed.SpeedLimit.MaxOnFlapsUp = PFD.Speed.SpeedLimit.MaxOnFlapsFull;
+ }
+ }
+ RefreshPFD();
+ }
+ function SetSpeedLimitMaxOnFlapsUp() {
+ PFD.Speed.SpeedLimit.MaxOnFlapsUp = ConvertUnit(Math.trunc(ReadValue("Textbox_SettingsSpeedLimitMaxOnFlapsUp")), Subsystem.I18n.SpeedUnit, "MeterPerSec");
+ if(PFD.Speed.SpeedLimit.MaxOnFlapsUp < 5) {
+ PFD.Speed.SpeedLimit.MaxOnFlapsUp = 5;
+ }
+ if(PFD.Speed.SpeedLimit.MaxOnFlapsUp > 277.5) {
+ PFD.Speed.SpeedLimit.MaxOnFlapsUp = 277.5;
+ }
+ if(PFD.Speed.SpeedLimit.MaxOnFlapsUp < PFD.Speed.SpeedLimit.MaxOnFlapsFull) {
+ PFD.Speed.SpeedLimit.MaxOnFlapsFull = PFD.Speed.SpeedLimit.MaxOnFlapsUp;
+ if(PFD.Speed.SpeedLimit.MaxOnFlapsFull < PFD.Speed.SpeedLimit.Min + 5) {
+ PFD.Speed.SpeedLimit.Min = PFD.Speed.SpeedLimit.MaxOnFlapsFull - 5;
+ }
+ }
+ RefreshPFD();
+ }
+ function SetSpeedLimitMaxOnFlapsFull() {
+ PFD.Speed.SpeedLimit.MaxOnFlapsFull = ConvertUnit(Math.trunc(ReadValue("Textbox_SettingsSpeedLimitMaxOnFlapsFull")), Subsystem.I18n.SpeedUnit, "MeterPerSec");
+ if(PFD.Speed.SpeedLimit.MaxOnFlapsFull < 5) {
+ PFD.Speed.SpeedLimit.MaxOnFlapsFull = 5;
+ }
+ if(PFD.Speed.SpeedLimit.MaxOnFlapsFull > 277.5) {
+ PFD.Speed.SpeedLimit.MaxOnFlapsFull = 277.5;
+ }
+ if(PFD.Speed.SpeedLimit.MaxOnFlapsFull < PFD.Speed.SpeedLimit.Min + 5) {
+ PFD.Speed.SpeedLimit.Min = PFD.Speed.SpeedLimit.MaxOnFlapsFull - 5;
+ }
+ if(PFD.Speed.SpeedLimit.MaxOnFlapsFull > PFD.Speed.SpeedLimit.MaxOnFlapsUp) {
+ PFD.Speed.SpeedLimit.MaxOnFlapsUp = PFD.Speed.SpeedLimit.MaxOnFlapsFull;
+ }
+ RefreshPFD();
+ }
+
+ // Altitude
+ function SetAltitudeMode() {
+ PFD.Altitude.Mode = ReadValue("Combobox_SettingsAltitudeMode");
+ RefreshPFD();
+ }
+ function SetAirportElevationDeparture() {
+ PFD.Altitude.AirportElevation.Departure = ConvertUnit(Math.trunc(ReadValue("Textbox_SettingsAirportElevationDeparture")), Subsystem.I18n.AltitudeUnit, "Meter");
+ if(PFD.Altitude.AirportElevation.Departure < -500) {
+ PFD.Altitude.AirportElevation.Departure = 500;
+ }
+ if(PFD.Altitude.AirportElevation.Departure > 5000) {
+ PFD.Altitude.AirportElevation.Departure = 5000;
+ }
+ RefreshPFD();
+ }
+ function SetAirportElevationArrival() {
+ PFD.Altitude.AirportElevation.Arrival = ConvertUnit(Math.trunc(ReadValue("Textbox_SettingsAirportElevationArrival")), Subsystem.I18n.AltitudeUnit, "Meter");
+ if(PFD.Altitude.AirportElevation.Arrival < -500) {
+ PFD.Altitude.AirportElevation.Arrival = 500;
+ }
+ if(PFD.Altitude.AirportElevation.Arrival > 5000) {
+ PFD.Altitude.AirportElevation.Arrival = 5000;
+ }
+ RefreshPFD();
+ }
+ function SwapAirportElevations() {
+ let Swapper = PFD.Altitude.AirportElevation.Departure;
+ PFD.Altitude.AirportElevation.Departure = PFD.Altitude.AirportElevation.Arrival;
+ PFD.Altitude.AirportElevation.Arrival = Swapper;
+ RefreshPFD();
+ }
+ function SetDecisionHeight() {
+ PFD.Altitude.DecisionHeight = ConvertUnit(Math.trunc(ReadValue("Textbox_SettingsDecisionHeight")), Subsystem.I18n.AltitudeUnit, "Meter");
+ if(PFD.Altitude.DecisionHeight < 15) {
+ PFD.Altitude.DecisionHeight = 15;
+ }
+ if(PFD.Altitude.DecisionHeight > 750) {
+ PFD.Altitude.DecisionHeight = 750;
+ }
+ RefreshPFD();
+ }
+
+ // DME
+ function SetEnableDME() {
+ PFD.DME.IsEnabled = IsChecked("Checkbox_SettingsEnableDME");
+ RefreshPFD();
+ }
+ function SetAirportCoordinatesDeparture() {
+ PFD.DME.AirportCoordinates.Departure = {
+ Lat: Math.trunc(ReadValue("Textbox_SettingsAirportCoordinatesDepartureLat") * 100000) / 100000,
+ Lon: Math.trunc(ReadValue("Textbox_SettingsAirportCoordinatesDepartureLon") * 100000) / 100000
+ };
+ if(PFD.DME.AirportCoordinates.Departure.Lat < -90) {
+ PFD.DME.AirportCoordinates.Departure.Lat = -90;
+ }
+ if(PFD.DME.AirportCoordinates.Departure.Lat > 90) {
+ PFD.DME.AirportCoordinates.Departure.Lat = 90;
+ }
+ if(PFD.DME.AirportCoordinates.Departure.Lon < -180) {
+ PFD.DME.AirportCoordinates.Departure.Lon = -180;
+ }
+ if(PFD.DME.AirportCoordinates.Departure.Lon > 180) {
+ PFD.DME.AirportCoordinates.Departure.Lon = 180;
+ }
+ RefreshPFD();
+ }
+ function SetAirportCoordinatesArrival() {
+ PFD.DME.AirportCoordinates.Arrival = {
+ Lat: Math.trunc(ReadValue("Textbox_SettingsAirportCoordinatesArrivalLat") * 100000) / 100000,
+ Lon: Math.trunc(ReadValue("Textbox_SettingsAirportCoordinatesArrivalLon") * 100000) / 100000
+ };
+ if(PFD.DME.AirportCoordinates.Arrival.Lat < -90) {
+ PFD.DME.AirportCoordinates.Arrival.Lat = -90;
+ }
+ if(PFD.DME.AirportCoordinates.Arrival.Lat > 90) {
+ PFD.DME.AirportCoordinates.Arrival.Lat = 90;
+ }
+ if(PFD.DME.AirportCoordinates.Arrival.Lon < -180) {
+ PFD.DME.AirportCoordinates.Arrival.Lon = -180;
+ }
+ if(PFD.DME.AirportCoordinates.Arrival.Lon > 180) {
+ PFD.DME.AirportCoordinates.Arrival.Lon = 180;
+ }
+ RefreshPFD();
+ }
+ function SwapAirportCoordinates() {
+ let Swapper = structuredClone(PFD.DME.AirportCoordinates.Departure);
+ PFD.DME.AirportCoordinates.Departure = structuredClone(PFD.DME.AirportCoordinates.Arrival);
+ PFD.DME.AirportCoordinates.Arrival = structuredClone(Swapper);
+ RefreshPFD();
+ }
+
+ // Flight mode
+ function SetFlightMode() {
+ PFD.FlightMode.FlightMode = ReadValue("Combobox_SettingsFlightMode");
+ PFD0.Stats.FlightModeTimestamp = Date.now();
+ RefreshPFD();
+ }
+ function SetAutoSwitchFlightModeAndSwapAirportData() {
+ PFD.FlightMode.AutoSwitchFlightModeAndSwapAirportData = IsChecked("Checkbox_SettingsAutoSwitchFlightModeAndSwapAirportData");
+ RefreshPFD();
+ }
+
+ // Display
+ function SetPFDStyle() {
+ Subsystem.Display.PFDStyle = ReadValue("Combobox_SettingsPFDStyle");
+ RefreshSubsystem();
+ }
+ function SetFlipPFDVertically() {
+ Subsystem.Display.FlipPFDVertically = IsChecked("Checkbox_SettingsFlipPFDVertically");
+ RefreshSubsystem();
+ RefreshPFD();
+ }
+ function SetKeepScreenOn() {
+ Subsystem.Display.KeepScreenOn = IsChecked("Checkbox_SettingsKeepScreenOn");
+ RefreshSubsystem();
+ }
+
+ // Audio
+ function SetAudioScheme() {
+ Subsystem.Audio.Scheme = ReadValue("Combobox_SettingsAudioScheme");
+ RefreshSubsystem();
+ }
+ function SetAttitudeAlertVolume() {
+ Subsystem.Audio.AttitudeAlertVolume = ReadValue("Slider_SettingsAttitudeAlertVolume");
+ RefreshSubsystem();
+ }
+ function PreviewAttitudeAlertVolume() {
+ ChangeAudioLoop("Audio_AttitudeAlert", false);
+ PlayAudio("Audio_AttitudeAlert", "../audio/Beep.mp3");
+ PFD0.Alert.NowPlaying.AttitudeWarning = "";
+ }
+ function SetSpeedAlertVolume() {
+ Subsystem.Audio.SpeedAlertVolume = ReadValue("Slider_SettingsSpeedAlertVolume");
+ RefreshSubsystem();
+ }
+ function PreviewSpeedAlertVolume() {
+ ChangeAudioLoop("Audio_SpeedAlert", false);
+ PlayAudio("Audio_SpeedAlert", "../audio/Beep.mp3");
+ PFD0.Alert.NowPlaying.SpeedWarning = "";
+ }
+ function SetAltitudeAlertVolume() {
+ Subsystem.Audio.AltitudeAlertVolume = ReadValue("Slider_SettingsAltitudeAlertVolume");
+ RefreshSubsystem();
+ }
+ function PreviewAltitudeAlertVolume() {
+ ChangeAudioLoop("Audio_AltitudeAlert", false);
+ PlayAudio("Audio_AltitudeAlert", "../audio/Beep.mp3");
+ PFD0.Alert.NowPlaying.AltitudeCallout = "";
+ PFD0.Alert.NowPlaying.AltitudeWarning = "";
+ }
+
+ // I18n
+ function SetAlwaysUseEnglishTerminologyOnPFD() {
+ Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD = IsChecked("Checkbox_SettingsAlwaysUseEnglishTerminologyOnPFD");
+ RefreshSubsystem();
+ RefreshPFD();
+ }
+ function SetMeasurementUnitsPreset() {
+ switch(ReadValue("Combobox_SettingsMeasurementUnitsPreset")) {
+ case "AllMetric":
+ Subsystem.I18n.SpeedUnit = "KilometerPerHour";
+ Subsystem.I18n.DistanceUnit = "Kilometer";
+ Subsystem.I18n.AltitudeUnit = "Meter";
+ Subsystem.I18n.VerticalSpeedUnit = "MeterPerSec";
+ Subsystem.I18n.PressureUnit = "Hectopascal";
+ Subsystem.I18n.TemperatureUnit = "Celsius";
+ break;
+ case "CivilAviation":
+ Subsystem.I18n.SpeedUnit = "Knot";
+ Subsystem.I18n.DistanceUnit = "NauticalMile";
+ Subsystem.I18n.AltitudeUnit = "Feet";
+ Subsystem.I18n.VerticalSpeedUnit = "FeetPerMin";
+ Subsystem.I18n.PressureUnit = "Hectopascal";
+ Subsystem.I18n.TemperatureUnit = "Celsius";
+ break;
+ default:
+ AlertSystemError("The value of ReadValue(\"Combobox_SettingsMeasurementUnitsPreset\") \"" + ReadValue("Combobox_SettingsMeasurementUnitsPreset") + "\" in function SetMeasurementUnitsPreset is invalid.");
+ break;
+ }
+ RefreshSubsystem();
+ RefreshPFD();
+ }
+ function SetSpeedUnit() {
+ Subsystem.I18n.SpeedUnit = ReadValue("Combobox_SettingsSpeedUnit");
+ RefreshSubsystem();
+ RefreshPFD();
+ }
+ function SetDistanceUnit() {
+ Subsystem.I18n.DistanceUnit = ReadValue("Combobox_SettingsDistanceUnit");
+ RefreshSubsystem();
+ RefreshPFD();
+ }
+ function SetAltitudeUnit() {
+ Subsystem.I18n.AltitudeUnit = ReadValue("Combobox_SettingsAltitudeUnit");
+ RefreshSubsystem();
+ RefreshPFD();
+ }
+ function SetVerticalSpeedUnit() {
+ Subsystem.I18n.VerticalSpeedUnit = ReadValue("Combobox_SettingsVerticalSpeedUnit");
+ RefreshSubsystem();
+ RefreshPFD();
+ }
+ function SetTemperatureUnit() {
+ Subsystem.I18n.TemperatureUnit = ReadValue("Combobox_SettingsTemperatureUnit");
+ RefreshSubsystem();
+ RefreshPFD();
+ }
+ function SetPressureUnit() {
+ Subsystem.I18n.PressureUnit = ReadValue("Combobox_SettingsPressureUnit");
+ RefreshSubsystem();
+ RefreshPFD();
+ }
+
+ // Misc
+ function ResetAllDontShowAgainDialogs() {
+ System.DontShowAgain = [0];
+ RefreshSystem();
+ ShowToast("已重置");
+ }
+
+ // User data
+ function ImportUserData() {
+ if(ReadValue("Textbox_SettingsUserDataImport") != "") {
+ if(ReadValue("Textbox_SettingsUserDataImport").startsWith("{\"System\":{\"Display\":{\"Theme\":") == true) {
+ let Objects = JSON.parse(ReadValue("Textbox_SettingsUserDataImport"));
+ Object.keys(Objects).forEach(function(ObjectName) {
+ localStorage.setItem(ObjectName, JSON.stringify(Objects[ObjectName]));
+ });
+ RefreshWebpage();
+ } else {
+ ShowDialog("System_JSONStringInvalid",
+ "Error",
+ "您键入的 JSON 字符串不合法。",
+ "", "", "", "确定");
+ RefreshSystem();
+ }
+ }
+ }
+ function ExportUserData() {
+ navigator.clipboard.writeText("{" +
+ "\"System\":" + JSON.stringify(System) + "," +
+ "\"GPSPFD_Subsystem\":" + JSON.stringify(Subsystem) + "," +
+ "\"GPSPFD_PFD\":" + JSON.stringify(PFD) +
+ "}");
+ ShowDialog("System_UserDataExported",
+ "Info",
+ "已导出本网页的用户数据至剪贴板。",
+ "", "", "", "确定");
+ }
+ function ConfirmClearUserData() {
+ ShowDialog("System_ConfirmClearUserData",
+ "Caution",
+ "您确认要清空用户数据?",
+ "", "", "清空", "取消");
+ }
+
+ // Dialog
+ function AnswerDialog(Selector) {
+ let DialogEvent = Interaction.Dialog[Interaction.Dialog.length - 1].Event;
+ ShowDialog("Previous");
+ switch(DialogEvent) {
+ case "System_LanguageUnsupported":
+ case "System_MajorUpdateDetected":
+ case "System_PWANewVersionReady":
+ case "System_RefreshingWebpage":
+ case "System_JSONStringInvalid":
+ case "System_UserDataExported":
+ switch(Selector) {
+ case 3:
+ break;
+ default:
+ AlertSystemError("The value of Selector \"" + Selector + "\" in function AnswerDialog is invalid.");
+ break;
+ }
+ break;
+ case "System_Welcome":
+ switch(Selector) {
+ case 2:
+ if(IsChecked("Checkbox_DialogCheckboxOption") == true) {
+ System.DontShowAgain[System.DontShowAgain.length] = "GPSPFD_System_Welcome";
+ RefreshSystem();
+ }
+ ScrollIntoView("Item_HelpReadBeforeUse");
+ ShowIAmHere("Item_HelpReadBeforeUse");
+ break;
+ case 3:
+ if(IsChecked("Checkbox_DialogCheckboxOption") == true) {
+ System.DontShowAgain[System.DontShowAgain.length] = "GPSPFD_System_Welcome";
+ RefreshSystem();
+ }
+ break;
+ default:
+ AlertSystemError("The value of Selector \"" + Selector + "\" in function AnswerDialog is invalid.");
+ break;
+ }
+ break;
+ case "System_ConfirmGoToTutorial":
+ switch(Selector) {
+ case 2:
+ ScrollIntoView("Item_HelpTutorial");
+ ShowIAmHere("Item_HelpTutorial");
+ break;
+ case 3:
+ break;
+ default:
+ AlertSystemError("The value of Selector \"" + Selector + "\" in function AnswerDialog is invalid.");
+ break;
+ }
+ break;
+ case "System_ConfirmClearUserData":
+ switch(Selector) {
+ case 2:
+ localStorage.clear();
+ RefreshWebpage();
+ break;
+ case 3:
+ break;
+ default:
+ AlertSystemError("The value of Selector \"" + Selector + "\" in function AnswerDialog is invalid.");
+ break;
+ }
+ break;
+ case "System_Error":
+ switch(Selector) {
+ case 1:
+ ScrollIntoView("Item_SettingsUserData");
+ ShowIAmHere("Item_SettingsUserData");
+ break;
+ case 2:
+ Object.keys(Automation).forEach(function(AutomationName) {
+ clearInterval(Automation[AutomationName]);
+ });
+ break;
+ case 3:
+ break;
+ default:
+ AlertSystemError("The value of Selector \"" + Selector + "\" in function AnswerDialog is invalid.");
+ break;
+ }
+ break;
+ default:
+ AlertSystemError("The value of DialogEvent \"" + DialogEvent + "\" in function AnswerDialog is invalid.");
+ break;
+ }
+ }
+
+// Listeners
+ // On click (mouse left button, Enter key or Space key)
+ document.addEventListener("click", function() {
+ setTimeout(function() {
+ HideToCorner("Window_PFDMenu");
+ }, 0);
+ });
+
+ // On keyboard
+ document.addEventListener("keydown", function(Hotkey) {
+ if(Hotkey.key == "Escape") {
+ HideToCorner("Window_PFDMenu");
+ }
+ if(Hotkey.key == "F1") {
+ ShowDialog("System_ConfirmGoToTutorial",
+ "Question",
+ "您按下了 F1 键。是否前往教程?",
+ "", "", "前往", "取消");
+ if(System.Display.HotkeyIndicators == "ShowOnAnyKeyPress" || System.Display.HotkeyIndicators == "AlwaysShow") {
+ ShowHotkeyIndicators();
+ }
+ }
+ if((document.activeElement.tagName.toLowerCase() != "input" && document.activeElement.tagName.toLowerCase() != "textarea")) { // Prevent hotkey activation when inputing text etc.
+ switch(Hotkey.key.toUpperCase()) {
+ case "M":
+ Click("Button_PFDMenu");
+ if(System.Display.HotkeyIndicators == "ShowOnAnyKeyPress" || System.Display.HotkeyIndicators == "AlwaysShow") {
+ ShowHotkeyIndicators();
+ }
+ break;
+ case "R":
+ PFD.Flaps = PFD.Flaps - 5;
+ if(PFD.Flaps < 0) {
+ PFD.Flaps = 0;
+ }
+ if(PFD.Flaps > 100) {
+ PFD.Flaps = 100;
+ }
+ RefreshPFD();
+ if(System.Display.HotkeyIndicators == "ShowOnAnyKeyPress" || System.Display.HotkeyIndicators == "AlwaysShow") {
+ ShowHotkeyIndicators();
+ }
+ break;
+ case "F":
+ PFD.Flaps = PFD.Flaps + 5;
+ if(PFD.Flaps < 0) {
+ PFD.Flaps = 0;
+ }
+ if(PFD.Flaps > 100) {
+ PFD.Flaps = 100;
+ }
+ RefreshPFD();
+ if(System.Display.HotkeyIndicators == "ShowOnAnyKeyPress" || System.Display.HotkeyIndicators == "AlwaysShow") {
+ ShowHotkeyIndicators();
+ }
+ break;
+ case "W":
+ if(PFD.Attitude.Mode == "Manual") {
+ PitchDown();
+ }
+ if(System.Display.HotkeyIndicators == "ShowOnAnyKeyPress" || System.Display.HotkeyIndicators == "AlwaysShow") {
+ ShowHotkeyIndicators();
+ }
+ break;
+ case "S":
+ if(PFD.Attitude.Mode == "Manual") {
+ PitchUp();
+ }
+ if(System.Display.HotkeyIndicators == "ShowOnAnyKeyPress" || System.Display.HotkeyIndicators == "AlwaysShow") {
+ ShowHotkeyIndicators();
+ }
+ break;
+ case "A":
+ if(PFD.Attitude.Mode == "Manual") {
+ RollLeft();
+ }
+ if(System.Display.HotkeyIndicators == "ShowOnAnyKeyPress" || System.Display.HotkeyIndicators == "AlwaysShow") {
+ ShowHotkeyIndicators();
+ }
+ break;
+ case "D":
+ if(PFD.Attitude.Mode == "Manual") {
+ RollRight();
+ }
+ if(System.Display.HotkeyIndicators == "ShowOnAnyKeyPress" || System.Display.HotkeyIndicators == "AlwaysShow") {
+ ShowHotkeyIndicators();
+ }
+ break;
+ case "U":
+ if(PFD.Speed.Mode == "Manual") {
+ SpeedUp();
+ }
+ if(System.Display.HotkeyIndicators == "ShowOnAnyKeyPress" || System.Display.HotkeyIndicators == "AlwaysShow") {
+ ShowHotkeyIndicators();
+ }
+ break;
+ case "J":
+ if(PFD.Speed.Mode == "Manual") {
+ SpeedDown();
+ }
+ if(System.Display.HotkeyIndicators == "ShowOnAnyKeyPress" || System.Display.HotkeyIndicators == "AlwaysShow") {
+ ShowHotkeyIndicators();
+ }
+ break;
+ case "O":
+ if(PFD.Altitude.Mode == "Manual") {
+ AltitudeUp();
+ }
+ if(System.Display.HotkeyIndicators == "ShowOnAnyKeyPress" || System.Display.HotkeyIndicators == "AlwaysShow") {
+ ShowHotkeyIndicators();
+ }
+ break;
+ case "L":
+ if(PFD.Altitude.Mode == "Manual") {
+ AltitudeDown();
+ }
+ if(System.Display.HotkeyIndicators == "ShowOnAnyKeyPress" || System.Display.HotkeyIndicators == "AlwaysShow") {
+ ShowHotkeyIndicators();
+ }
+ break;
+ default:
+ if((System.Display.HotkeyIndicators == "ShowOnWrongKeyPress" && IsWrongKeyNegligible(Hotkey.key) == false && Hotkey.key != "F1") ||
+ System.Display.HotkeyIndicators == "ShowOnAnyKeyPress" || System.Display.HotkeyIndicators == "AlwaysShow") {
+ ShowHotkeyIndicators();
+ }
+ break;
+ }
+ }
+ });
+
+ // On resizing window
+ window.addEventListener("resize", ClockPFD);
+
+ // Geolocation API
+ navigator.geolocation.watchPosition(RefreshGPSData);
+
+ // Device motion API
+ window.addEventListener("devicemotion", RefreshAccelData);
+
+// Automations
+Automation.ClockPFD = setInterval(ClockPFD, 20);
+
+// Features
+ // Converters
+ function ConvertUnit(Value, InputUnit, OutputUnit) {
+ if(InputUnit != OutputUnit) {
+ switch(InputUnit) {
+ case "Meter":
+ switch(OutputUnit) {
+ case "Kilometer":
+ Value /= 1000;
+ break;
+ case "Feet":
+ case "FeetButShowMeterBeside":
+ Value *= 3.28084;
+ break;
+ case "Mile":
+ Value /= 1609.344;
+ break;
+ case "NauticalMile":
+ Value /= 1852;
+ break;
+ default:
+ AlertSystemError("The value of OutputUnit \"" + OutputUnit + "\" in function ConvertUnit is invalid.");
+ break;
+ }
+ break;
+ case "Kilometer":
+ switch(OutputUnit) {
+ case "Meter":
+ Value *= 1000;
+ break;
+ case "Feet":
+ case "FeetButShowMeterBeside":
+ Value *= 3280.83990;
+ break;
+ case "Mile":
+ Value /= 1.60934;
+ break;
+ case "NauticalMile":
+ Value /= 1.852;
+ break;
+ default:
+ AlertSystemError("The value of OutputUnit \"" + OutputUnit + "\" in function ConvertUnit is invalid.");
+ break;
+ }
+ break;
+ case "Feet":
+ case "FeetButShowMeterBeside":
+ switch(OutputUnit) {
+ case "Meter":
+ Value /= 3.28084;
+ break;
+ case "Kilometer":
+ Value /= 3280.83990;
+ break;
+ case "Mile":
+ Value /= 5280;
+ break;
+ case "NauticalMile":
+ Value /= 6076.11549;
+ break;
+ default:
+ AlertSystemError("The value of OutputUnit \"" + OutputUnit + "\" in function ConvertUnit is invalid.");
+ break;
+ }
+ break;
+ case "Mile":
+ switch(OutputUnit) {
+ case "Meter":
+ Value *= 1609.344;
+ break;
+ case "Kilometer":
+ Value *= 1.60934;
+ break;
+ case "Feet":
+ case "FeetButShowMeterBeside":
+ Value *= 5280;
+ break;
+ case "NauticalMile":
+ Value /= 1.15078;
+ break;
+ default:
+ AlertSystemError("The value of OutputUnit \"" + OutputUnit + "\" in function ConvertUnit is invalid.");
+ break;
+ }
+ break;
+ case "NauticalMile":
+ switch(OutputUnit) {
+ case "Meter":
+ Value *= 1852;
+ break;
+ case "Kilometer":
+ Value *= 1.852;
+ break;
+ case "Feet":
+ case "FeetButShowMeterBeside":
+ Value *= 6076.11549;
+ break;
+ case "Mile":
+ Value *= 1.15078;
+ break;
+ default:
+ AlertSystemError("The value of OutputUnit \"" + OutputUnit + "\" in function ConvertUnit is invalid.");
+ break;
+ }
+ break;
+ case "MeterPerSec":
+ switch(OutputUnit) {
+ case "KilometerPerHour":
+ Value *= 3.6;
+ break;
+ case "FeetPerMin":
+ Value *= 196.85039;
+ break;
+ case "MilePerHour":
+ Value *= 2.23694;
+ break;
+ case "Knot":
+ Value *= 1.94384;
+ break;
+ default:
+ AlertSystemError("The value of OutputUnit \"" + OutputUnit + "\" in function ConvertUnit is invalid.");
+ break;
+ }
+ break;
+ case "KilometerPerHour":
+ switch(OutputUnit) {
+ case "MeterPerSec":
+ Value /= 3.6;
+ break;
+ case "FeetPerMin":
+ Value *= 54.68066;
+ break;
+ case "MilePerHour":
+ Value /= 1.60934;
+ break;
+ case "Knot":
+ Value /= 1.852;
+ break;
+ default:
+ AlertSystemError("The value of OutputUnit \"" + OutputUnit + "\" in function ConvertUnit is invalid.");
+ break;
+ }
+ break;
+ case "FeetPerMin":
+ switch(OutputUnit) {
+ case "MeterPerSec":
+ Value /= 196.85039;
+ break;
+ case "KilometerPerHour":
+ Value /= 54.68066;
+ break;
+ case "MilePerHour":
+ Value /= 88;
+ break;
+ case "Knot":
+ Value /= 101.26859;
+ break;
+ default:
+ AlertSystemError("The value of OutputUnit \"" + OutputUnit + "\" in function ConvertUnit is invalid.");
+ break;
+ }
+ break;
+ case "MilePerHour":
+ switch(OutputUnit) {
+ case "MeterPerSec":
+ Value /= 2.23694;
+ break;
+ case "KilometerPerHour":
+ Value *= 1.60934;
+ break;
+ case "FeetPerMin":
+ Value *= 88;
+ break;
+ case "Knot":
+ Value /= 1.15078;
+ break;
+ default:
+ AlertSystemError("The value of OutputUnit \"" + OutputUnit + "\" in function ConvertUnit is invalid.");
+ break;
+ }
+ break;
+ case "Knot":
+ switch(OutputUnit) {
+ case "MeterPerSec":
+ Value /= 1.94384;
+ break;
+ case "KilometerPerHour":
+ Value *= 1.852;
+ break;
+ case "FeetPerMin":
+ Value *= 101.26859;
+ break;
+ case "MilePerHour":
+ Value *= 1.15078;
+ break;
+ default:
+ AlertSystemError("The value of OutputUnit \"" + OutputUnit + "\" in function ConvertUnit is invalid.");
+ break;
+ }
+ break;
+ case "Kelvin":
+ switch(OutputUnit) {
+ case "Celsius":
+ Value -= 273.15;
+ break;
+ case "Fahrenheit":
+ Value = (Value - 273.15) * 1.8 + 32;
+ break;
+ default:
+ AlertSystemError("The value of OutputUnit \"" + OutputUnit + "\" in function ConvertUnit is invalid.");
+ break;
+ }
+ break;
+ case "Celsius":
+ switch(OutputUnit) {
+ case "Kelvin":
+ Value += 273.15;
+ break;
+ case "Fahrenheit":
+ Value = Value * 1.8 + 32;
+ break;
+ default:
+ AlertSystemError("The value of OutputUnit \"" + OutputUnit + "\" in function ConvertUnit is invalid.");
+ break;
+ }
+ break;
+ case "Fahrenheit":
+ switch(OutputUnit) {
+ case "Kelvin":
+ Value = (Value - 32) / 1.8 + 273.15;
+ break;
+ case "Celsius":
+ Value = (Value - 32) / 1.8;
+ break;
+ default:
+ AlertSystemError("The value of OutputUnit \"" + OutputUnit + "\" in function ConvertUnit is invalid.");
+ break;
+ }
+ break;
+ case "Hectopascal":
+ switch(OutputUnit) {
+ case "InchOfMercury":
+ Value /= 33.86389;
+ break;
+ default:
+ AlertSystemError("The value of OutputUnit \"" + OutputUnit + "\" in function ConvertUnit is invalid.");
+ break;
+ }
+ break;
+ case "InchOfMercury":
+ switch(OutputUnit) {
+ case "Hectopascal":
+ Value *= 33.86389;
+ break;
+ default:
+ AlertSystemError("The value of OutputUnit \"" + OutputUnit + "\" in function ConvertUnit is invalid.");
+ break;
+ }
+ break;
+ default:
+ AlertSystemError("The value of InputUnit \"" + InputUnit + "\" in function ConvertUnit is invalid.");
+ break;
+ }
+ }
+ return Math.round(Value * 100000) / 100000;
+ }
+ function Translate(Value) {
+ switch(Value) {
+ case "Unknown":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "未知";
+ } else {
+ return "UNKNOWN";
+ }
+ case "Normal":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "正常";
+ } else {
+ return "NORM";
+ }
+ case "SignalWeak":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "信号弱";
+ } else {
+ return "WEAK";
+ }
+ case "Unavailable":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "不可用";
+ } else {
+ return "UNAVAIL";
+ }
+ case "NoWind":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "无风";
+ } else {
+ return "---°/---";
+ }
+ case "FlapsUp":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "收起";
+ } else {
+ return "UP";
+ }
+ case "GPS":
+ return "GPS";
+ case "Accel":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "加速计";
+ } else {
+ return "ACCEL";
+ }
+ case "DualChannel":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "双通道";
+ } else {
+ return "DUAL CH";
+ }
+ case "Manual":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "手动";
+ } else {
+ return "MAN";
+ }
+ case "DepartureGround":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "出发地面";
+ } else {
+ return "DEP GND";
+ }
+ case "TakeOff":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "起飞";
+ } else {
+ return "T/O";
+ }
+ case "Cruise":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "巡航";
+ } else {
+ return "CRZ";
+ }
+ case "Land":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "降落";
+ } else {
+ return "LAND";
+ }
+ case "ArrivalGround":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "到达地面";
+ } else {
+ return "ARR GND";
+ }
+ case "EmergencyReturn":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "紧急返航";
+ } else {
+ return "EMG RTN";
+ }
+ case "AttitudeUnavailable":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "姿态仪不可用";
+ } else {
+ return "ATT";
+ }
+ case "AttitudeDisabled":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "姿态仪已禁用";
+ } else {
+ return "ATT OFF";
+ }
+ case "SpeedUnavailable":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "空速表不可用";
+ } else {
+ return "SPD";
+ }
+ case "AltitudeUnavailable":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "高度表不可用";
+ } else {
+ return "ALT";
+ }
+ case "MetricAltitude":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "米";
+ } else {
+ return "M";
+ }
+ case "VerticalSpeedUnavailable":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "垂直速度表不可用";
+ } else {
+ return "V/S";
+ }
+ case "HeadingUnavailable":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "朝向指示器不可用";
+ } else {
+ return "HDG";
+ }
+ case "DistanceTooFar":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "距离过远";
+ } else {
+ return "---";
+ }
+ case "Hour":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "时";
+ } else {
+ return "H";
+ }
+ case "Minute":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "分";
+ } else {
+ return "M";
+ }
+ case "BankAngle":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "倾角过大";
+ } else {
+ return "BANK ANGLE";
+ }
+ case "AirspeedLow":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "空速过低";
+ } else {
+ return "SPD LOW";
+ }
+ case "Overspeed":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "超速";
+ } else {
+ return "OVERSPEED";
+ }
+ case "DontSink":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "不要下降";
+ } else {
+ return "DON'T SINK";
+ }
+ case "GlideSlope":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "偏离下滑道";
+ } else {
+ return "G/S";
+ }
+ case "SinkRate":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "下降率过大";
+ } else {
+ return "SINK RATE";
+ }
+ case "PullUp":
+ if(Subsystem.I18n.AlwaysUseEnglishTerminologyOnPFD == false) {
+ return "拉杆!";
+ } else {
+ return "PULL UP!";
+ }
+ case "KilometerPerHour":
+ return "公里/小时";
+ case "MilePerHour":
+ return "英里/小时";
+ case "Knot":
+ return "节";
+ case "Kilometer":
+ return "公里";
+ case "Mile":
+ return "英里";
+ case "NauticalMile":
+ return "海里";
+ case "Meter":
+ return "米";
+ case "Feet":
+ case "FeetButShowMeterBeside":
+ return "英尺";
+ case "Celsius":
+ return "℃";
+ case "Fahrenheit":
+ return "℉";
+ case "Hectopascal":
+ return "百帕";
+ case "InchOfMercury":
+ return "英寸
汞柱";
+ default:
+ AlertSystemError("The value of Value \"" + Value + "\" in function Translate is invalid.");
+ break;
+ }
+ }
+
+ // Maths
+ function CalcAttitude(AccelVector, AccelVectorWithGravity) { // https://youtube.com/watch?v=p7tjtLkIlFo
+ let Gravity = 9.80665,
+ Calc = (AccelVectorWithGravity.Forward - AccelVector.Forward) / Gravity;
+ if(Calc < -1) {
+ Calc = -1;
+ }
+ if(Calc > 1) {
+ Calc = 1;
+ }
+ return {
+ Pitch: -Math.asin(Calc) / (Math.PI / 180),
+ Roll: Math.atan2(AccelVectorWithGravity.Right - AccelVector.Right, AccelVector.Upward - AccelVectorWithGravity.Upward) / (Math.PI / 180)
+ };
+ }
+ function CalcTAS(GS, WindRelativeHeading, WindSpeed, VerticalSpeed) {
+ let HorizontalTAS = 0, TAS = 0;
+ if(WindRelativeHeading != null) {
+ HorizontalTAS = GS + WindSpeed * Math.cos(WindRelativeHeading * (Math.PI / 180));
+ } else {
+ HorizontalTAS = GS;
+ }
+ TAS = Math.sqrt(Math.pow(HorizontalTAS, 2) + Math.pow(VerticalSpeed, 2));
+ return TAS;
+ }
+ function CalcOutsideAirTemperature(Altitude, AirportAltitude, AirportTemperature) { // Air temperature drops 2 Celsius for each 1000 feet. (https://aviation.stackexchange.com/a/44763)
+ return AirportTemperature - 2 * ((Altitude - AirportAltitude) / 304.8);
+ }
+ function CalcOutsideAirPressure(Altitude, QNH, OutsideAirTemperature) { // https://www.omnicalculator.com/physics/air-pressure-at-altitude
+ return QNH * Math.pow(Math.E, (-9.80665 * 0.0289644 * Altitude) / (8.31432 * OutsideAirTemperature));
+ }
+ function CalcOutsideAirDensity(OutsideAirTemperature, OutsideAirPressure, RelativeHumidity) { // https://www.omnicalculator.com/physics/air-density
+ let WaterVaporPressure = 0, DryAirPressure = 0, CelsiusTemperature = 0, Calc = 0, Calc2 = 0;
+ CelsiusTemperature = ConvertUnit(OutsideAirTemperature, "Kelvin", "Celsius");
+ WaterVaporPressure = 6.1078 * Math.pow(10, (7.5 * CelsiusTemperature) / (CelsiusTemperature + 237.3)) * (RelativeHumidity / 100);
+ DryAirPressure = OutsideAirPressure - WaterVaporPressure;
+ Calc = (DryAirPressure * 100) / (287.058 * OutsideAirTemperature);
+ Calc2 = (WaterVaporPressure * 100) / (461.495 * OutsideAirTemperature);
+ return Calc + Calc2;
+ }
+ function CalcIAS(Algorithm, TAS, Altitude, AirportAltitude, AirportTemperature, RelativeHumidity, QNH, IsAttitudeConsidered, RelativePitch) { // https://aerotoolbox.com/airspeed-conversions/, https://aviation.stackexchange.com/a/25811
+ let OutsideAirTemperature = 0, OutsideAirPressure = 0, OutsideAirDensity = 0, IAS = 0;
+ switch(Algorithm) {
+ case "SimpleAlgorithm":
+ IAS = TAS / (1 + 0.02 * (Altitude / 304.8));
+ break;
+ case "AdvancedAlgorithmA":
+ OutsideAirTemperature = CalcOutsideAirTemperature(Altitude, AirportAltitude, AirportTemperature);
+ OutsideAirPressure = CalcOutsideAirPressure(Altitude, QNH, OutsideAirTemperature);
+ OutsideAirDensity = CalcOutsideAirDensity(OutsideAirTemperature, OutsideAirPressure, RelativeHumidity);
+ IAS = TAS * Math.sqrt(OutsideAirDensity / 1.225);
+ break;
+ case "AdvancedAlgorithmB":
+ OutsideAirTemperature = CalcOutsideAirTemperature(Altitude, AirportAltitude, AirportTemperature);
+ OutsideAirPressure = CalcOutsideAirPressure(Altitude, QNH, OutsideAirTemperature);
+ OutsideAirDensity = CalcOutsideAirDensity(OutsideAirTemperature, OutsideAirPressure, RelativeHumidity);
+ IAS = 340.3 * Math.sqrt(5 * (Math.pow(OutsideAirDensity / 2 * Math.pow(TAS, 2) / 101325 + 1, 2 / 7) - 1));
+ break;
+ default:
+ AlertSystemError("The value of Algorithm \"" + Algorithm + "\" in function CalcIAS is invalid.");
+ break;
+ }
+ if(IsAttitudeConsidered == true) {
+ return IAS * Math.cos(RelativePitch * (Math.PI / 180));
+ } else {
+ return IAS;
+ }
+ }
+ function CalcMachNumber(TAS, Altitude, AirportAltitude, AirportTemperature) {
+ let OutsideAirTemperature = 0, SoundSpeed = 0;
+ OutsideAirTemperature = CalcOutsideAirTemperature(Altitude, AirportAltitude, AirportTemperature);
+ SoundSpeed = 331.15 + 0.61 * ConvertUnit(OutsideAirTemperature, "Kelvin", "Celsius");
+ return TAS / SoundSpeed;
+ }
+ function CalcMaxSpeedLimit(MaxSpeedOnFlapsUp, MaxSpeedOnFlapsFull, FlapsPercentage) {
+ return MaxSpeedOnFlapsUp - (MaxSpeedOnFlapsUp - MaxSpeedOnFlapsFull) * (FlapsPercentage / 100);
+ }
+ function CalcDistance(Lat1, Lon1, Lat2, Lon2) { // Haversine formula (https://stackoverflow.com/a/27943)
+ let EarthRadius = 6371008.8, LatDiffInRad = 0, LonDiffInRad = 0, Calc = 0, Distance = 0;
+ LatDiffInRad = (Lat2 - Lat1) * (Math.PI / 180);
+ LonDiffInRad = (Lon2 - Lon1) * (Math.PI / 180);
+ Calc = Math.sin(LatDiffInRad / 2) * Math.sin(LatDiffInRad / 2) + Math.cos(Lat1 * (Math.PI / 180)) * Math.cos(Lat2 * (Math.PI / 180)) * Math.sin(LonDiffInRad / 2) * Math.sin(LonDiffInRad / 2);
+ Distance = 2 * EarthRadius * Math.atan2(Math.sqrt(Calc), Math.sqrt(1 - Calc));
+ return Distance;
+ }
+
+ // Alerts
+ function IsExcessiveBankAngle() {
+ if(PFD0.Stats.Altitude.RadioDisplay >= 9.144) { // https://skybrary.aero/articles/bank-angle-awareness
+ let Threshold = 10 + 25 * ((PFD0.Stats.Altitude.RadioDisplay - 9.144) / 36.576);
+ if(Threshold > 35) {
+ Threshold = 35;
+ }
+ return Math.abs(PFD0.Stats.Attitude.Roll) >= Threshold;
+ } else {
+ return false;
+ }
+ }
+ function IsAirspeedLow() {
+ switch(PFD.FlightMode.FlightMode) {
+ case "DepartureGround":
+ case "ArrivalGround":
+ return false;
+ case "TakeOff":
+ case "Cruise":
+ case "Land":
+ case "EmergencyReturn":
+ return PFD0.Stats.Speed.TapeDisplay <= PFD.Speed.SpeedLimit.Min;
+ default:
+ AlertSystemError("The value of PFD.FlightMode.FlightMode \"" + PFD.FlightMode.FlightMode + "\" in function IsAirspeedLow is invalid.");
+ break;
+ }
+ }
+ function IsOverspeed() {
+ return PFD0.Stats.Speed.TapeDisplay >= CalcMaxSpeedLimit(PFD.Speed.SpeedLimit.MaxOnFlapsUp, PFD.Speed.SpeedLimit.MaxOnFlapsFull, PFD.Flaps);
+ }
+ function IsDontSink() {
+ switch(PFD.FlightMode.FlightMode) {
+ case "DepartureGround":
+ case "Cruise":
+ case "Land":
+ case "ArrivalGround":
+ case "EmergencyReturn":
+ return false;
+ case "TakeOff":
+ return PFD0.Stats.Speed.Vertical <= -0.508;
+ default:
+ AlertSystemError("The value of PFD.FlightMode.FlightMode \"" + PFD.FlightMode.FlightMode + "\" in function IsDontSink is invalid.");
+ break;
+ }
+ }
+ function IsExcessivelyBelowGlideSlope() {
+ switch(PFD.FlightMode.FlightMode) {
+ case "DepartureGround":
+ case "TakeOff":
+ case "Cruise":
+ case "ArrivalGround":
+ return false;
+ case "Land":
+ case "EmergencyReturn":
+ return PFD0.Stats.Altitude.RadioDisplay <= PFD0.Stats.DME.Distance * Math.tan(3 * (Math.PI / 180)) - 60.96;
+ default:
+ AlertSystemError("The value of PFD.FlightMode.FlightMode \"" + PFD.FlightMode.FlightMode + "\" in function IsExcessivelyBelowGlideSlope is invalid.");
+ break;
+ }
+ }
+ function IsSinkRate() { // https://commons.wikimedia.org/wiki/File:FAA_excessive_sink_rate_graph.svg
+ let HeightRange = {
+ Min: 0, Max: 0
+ };
+ switch(true) {
+ case PFD0.Stats.Speed.Vertical > -6:
+ return false;
+ case PFD0.Stats.Speed.Vertical <= -6 && PFD0.Stats.Speed.Vertical > -8:
+ HeightRange.Min = 60 - 40 * ((-PFD0.Stats.Speed.Vertical - 6) / 2);
+ HeightRange.Max = 60 + 90 * ((-PFD0.Stats.Speed.Vertical - 6) / 2);
+ return PFD0.Stats.Altitude.RadioDisplay >= HeightRange.Min && PFD0.Stats.Altitude.RadioDisplay <= HeightRange.Max;
+ case PFD0.Stats.Speed.Vertical <= -8 && PFD0.Stats.Speed.Vertical > -30:
+ HeightRange.Min = 20 - 10 * ((-PFD0.Stats.Speed.Vertical - 8) / 22);
+ HeightRange.Max = 150 + 850 * ((-PFD0.Stats.Speed.Vertical - 8) / 22);
+ return PFD0.Stats.Altitude.RadioDisplay >= HeightRange.Min && PFD0.Stats.Altitude.RadioDisplay <= HeightRange.Max;
+ case PFD0.Stats.Speed.Vertical <= -30 && PFD0.Stats.Speed.Vertical > -50:
+ HeightRange.Min = 10 - 10 * ((-PFD0.Stats.Speed.Vertical - 30) / 20);
+ HeightRange.Max = 1000 + 500 * ((-PFD0.Stats.Speed.Vertical - 30) / 20);
+ return PFD0.Stats.Altitude.RadioDisplay >= HeightRange.Min && PFD0.Stats.Altitude.RadioDisplay <= HeightRange.Max;
+ case PFD0.Stats.Speed.Vertical <= -50:
+ HeightRange.Max = 1500 + 500 * ((-PFD0.Stats.Speed.Vertical - 50) / 20);
+ return PFD0.Stats.Altitude.RadioDisplay <= HeightRange.Max;
+ }
+ }
+ function IsSinkRatePullUp() {
+ let HeightRange = {
+ Min: 0, Max: 0
+ };
+ switch(true) {
+ case PFD0.Stats.Speed.Vertical > -8:
+ return false;
+ case PFD0.Stats.Speed.Vertical <= -8 && PFD0.Stats.Speed.Vertical > -30:
+ HeightRange.Min = 20 - 10 * ((-PFD0.Stats.Speed.Vertical - 8) / 22);
+ HeightRange.Max = 60 + 740 * ((-PFD0.Stats.Speed.Vertical - 8) / 22);
+ return PFD0.Stats.Altitude.RadioDisplay >= HeightRange.Min && PFD0.Stats.Altitude.RadioDisplay <= HeightRange.Max;
+ case PFD0.Stats.Speed.Vertical <= -30 && PFD0.Stats.Speed.Vertical > -50:
+ HeightRange.Min = 10 - 10 * ((-PFD0.Stats.Speed.Vertical - 30) / 20);
+ HeightRange.Max = 800 + 400 * ((-PFD0.Stats.Speed.Vertical - 30) / 20);
+ return PFD0.Stats.Altitude.RadioDisplay >= HeightRange.Min && PFD0.Stats.Altitude.RadioDisplay <= HeightRange.Max;
+ case PFD0.Stats.Speed.Vertical <= -50:
+ HeightRange.Max = 1200 + 400 * ((-PFD0.Stats.Speed.Vertical - 50) / 20);
+ return PFD0.Stats.Altitude.RadioDisplay <= HeightRange.Max;
+ }
+ }
+
+// Error handling
+function AlertSystemError(Message) {
+ console.error("● 系统错误\n" +
+ Message);
+ ShowDialog("System_Error",
+ "Error",
+ "抱歉,发生了系统错误。您可尝试清空用户数据来修复错误,或向我提供反馈。若无法关闭对话框,请点击「强制停止」。
" +
+ "
" +
+ "错误信息:" + Message,
+ "", "了解更多", "强制停止", "关闭");
+}
diff --git a/PROJECT/GPS-PFD/styles/style.css b/PROJECT/GPS-PFD/styles/style.css
new file mode 100644
index 0000000..6346ae2
--- /dev/null
+++ b/PROJECT/GPS-PFD/styles/style.css
@@ -0,0 +1,1036 @@
+/* For SamToki.github.io/GPS-PFD */
+/* Released under GNU GPL v3 open source license. */
+/* © 2025 SAM TOKI STUDIO */
+
+/* Area specific */
+ /* Header */
+ #Ctrl_Nav {
+ left: calc(50% - 180px);
+ width: 360px;
+ }
+
+ /* Main */
+ /* PFD */
+ #PFDViewport {
+ display: flex; justify-content: center; align-items: center;
+ }
+ /* Panels */
+ .PFDPanel {
+ position: relative;
+ flex-shrink: 0;
+ }
+ /* Default panel */
+ #Ctnr_PFDDefaultPanel {
+ width: 1000px; height: 800px;
+ }
+ /* Attitude */
+ #Ctnr_PFDDefaultPanelAttitude {
+ position: absolute; top: 175px; left: 275px;
+ width: 450px; height: 450px;
+ }
+ #Ctnr_PFDDefaultPanelAttitude > .CtrlGroup {
+ width: 100%; height: 100%;
+ }
+ /* Status */
+ .StatusBox {
+ background-color: #FFFFFF80;
+ }
+ #Ctrl_PFDDefaultPanelAttitudeStatus {
+ position: absolute; top: calc(50% - 40px); left: calc(50% - 100px);
+ width: 200px; height: 80px; padding: 5px;
+ text-align: center;
+ display: flex; justify-content: center; align-items: center;
+ }
+
+ /* Bg (Artificial horizon) */
+ #Ctrl_PFDDefaultPanelAttitudeBg {
+ position: absolute; top: calc(50% - 2000px); left: calc(50% - 2000px);
+ width: 4000px; height: 4000px;
+ }
+ #PFDDefaultPanelAttitudeSky {
+ width: 100%; height: 50%;
+ background-color: #A0E0FFC0;
+ }
+ #PFDDefaultPanelAttitudeTerrain {
+ width: 100%; height: 50%;
+ background-color: #FFE080C0;
+ }
+
+ /* Pitch */
+ #Ctrl_PFDDefaultPanelAttitudePitch {
+ position: absolute; top: 40px; left: 40px;
+ width: 370px; height: 370px; border-radius: 185px;
+ overflow: hidden;
+ }
+ #CtrlGroup_PFDDefaultPanelAttitudePitch {
+ position: absolute; top: calc(50% - 900px); left: 0;
+ width: 100%; height: 1800px;
+ flex-direction: column;
+ }
+ #CtrlGroup_PFDDefaultPanelAttitudePitch .Ctrl {
+ width: 100%; height: 0;
+ display: flex; justify-content: center; align-items: center;
+ }
+ #CtrlGroup_PFDDefaultPanelAttitudePitch .AttitudeScale0 {
+ width: 2000px; height: 2px; border-radius: 1px;
+ background-color: #000000;
+ }
+ #CtrlGroup_PFDDefaultPanelAttitudePitch .AttitudeScale1 {
+ width: 160px; height: 2px; border-radius: 1px; margin: 0 10px;
+ background-color: #000000;
+ }
+ #CtrlGroup_PFDDefaultPanelAttitudePitch .AttitudeScale2 {
+ width: 80px; height: 2px; border-radius: 1px;
+ background-color: #000000;
+ }
+ #CtrlGroup_PFDDefaultPanelAttitudePitch .AttitudeScale4 {
+ width: 40px; height: 2px; border-radius: 1px;
+ background-color: #000000;
+ }
+
+ /* Roll */
+ #Ctrl_PFDDefaultPanelAttitudeRoll {
+ position: absolute; top: 0; left: 0;
+ width: 100%; height: 100%;
+ overflow: hidden;
+ pointer-events: none;
+ }
+ #CtrlGroup_PFDDefaultPanelAttitudeRollScale {
+ position: absolute; top: -15px; left: -15px;
+ width: 480px; height: 480px; border-radius: 240px;
+ }
+ #CtrlGroup_PFDDefaultPanelAttitudeRollScale .Ctrl {
+ position: absolute; top: 0; left: 50%;
+ width: 0; height: 100%;
+ display: flex; flex-direction: column; align-items: center;
+ }
+ #CtrlGroup_PFDDefaultPanelAttitudeRollScale .AttitudeScale0 {
+ width: 15px; height: 15px; margin: 15px 0 0 0; clip-path: polygon(0 0, 100% 0, 50% 100%);
+ background-color: #000000;
+ }
+ #CtrlGroup_PFDDefaultPanelAttitudeRollScale .AttitudeScale1 {
+ width: 3px; height: 30px; border-radius: 1.5px;
+ background-color: #000000;
+ }
+ #CtrlGroup_PFDDefaultPanelAttitudeRollScale .AttitudeScale2 {
+ width: 2px; height: 15px; border-radius: 1px; margin: 15px 0 0 0;
+ background-color: #000000;
+ }
+ #Progring_PFDDefaultPanelAttitudeRoll {
+ position: absolute; top: 14px; left: 14px;
+ width: 422px; height: 422px;
+ }
+ #ProgringFg_PFDDefaultPanelAttitudeRoll {
+ stroke: #000000; stroke-width: 2; stroke-linecap: unset; stroke-dasharray: 0, calc((422px - 2px) * 3.1415926);
+ }
+ #PFDDefaultPanelAttitudeRollPointer {
+ position: absolute; top: 15px; left: calc(50% - 10px);
+ width: 20px; height: 20px; clip-path: polygon(50% 0, 100% 100%, 0 100%);
+ background-color: #000000;
+ pointer-events: auto;
+ }
+
+ /* Aircraft symbol */
+ #Ctrl_PFDDefaultPanelAttitudeAircraftSymbol {
+ position: absolute; top: calc(50% - 5px); left: 75px;
+ width: 300px; height: 22.5px;
+ }
+ #Ctrl_PFDDefaultPanelAttitudeAircraftSymbol .CtrlGroup {
+ width: 100%; height: 100%;
+ filter: drop-shadow(0 0 0.5px #000000) drop-shadow(0 0 0.5px #000000) drop-shadow(0 0 0.5px #000000) drop-shadow(0 0 0.5px #000000);
+ }
+ #Ctrl_PFDDefaultPanelAttitudeAircraftSymbol .Ctrl {
+ position: absolute; top: unset; left: unset;
+ background-color: #FFFFFF;
+ }
+ #Ctrl_PFDDefaultPanelAttitudeAircraftSymbol #Ctrl_PFDDefaultPanelAttitudeAircraftSymbolLeft1 {
+ top: 2.5px; left: 0;
+ width: 80px; height: 5px; border-radius: 2.5px;
+ }
+ #Ctrl_PFDDefaultPanelAttitudeAircraftSymbol #Ctrl_PFDDefaultPanelAttitudeAircraftSymbolLeft2 {
+ top: 2.5px; left: 75px;
+ width: 5px; height: 20px; border-radius: 2.5px;
+ }
+ #Ctrl_PFDDefaultPanelAttitudeAircraftSymbol #Ctrl_PFDDefaultPanelAttitudeAircraftSymbolCenter {
+ top: 0; left: calc(50% - 5px);
+ width: 10px; height: 10px; border-radius: 5px;
+ }
+ #Ctrl_PFDDefaultPanelAttitudeAircraftSymbol #Ctrl_PFDDefaultPanelAttitudeAircraftSymbolRight1 {
+ top: 2.5px; right: 0;
+ width: 80px; height: 5px; border-radius: 2.5px;
+ }
+ #Ctrl_PFDDefaultPanelAttitudeAircraftSymbol #Ctrl_PFDDefaultPanelAttitudeAircraftSymbolRight2 {
+ top: 2.5px; right: 75px;
+ width: 5px; height: 20px; border-radius: 2.5px;
+ }
+
+ /* Info bar */
+ #Ctnr_PFDDefaultPanelInfoBar {
+ position: absolute; top: 0; left: 0;
+ width: 125px; height: 800px;
+ }
+ #CtrlGroup_PFDDefaultPanelStatus {
+ position: absolute; top: 0; left: 0;
+ width: 100%; height: 140px;
+ }
+ #CtrlGroup_PFDDefaultPanelAdditionalSpeeds {
+ position: absolute; top: calc(50% - 110px); left: 0;
+ width: 100%; height: 220px;
+ }
+ #CtrlGroup_PFDDefaultPanelFlapStatus {
+ position: absolute; bottom: 0; left: 0;
+ width: 100%; height: 180px;
+ }
+ #Ctnr_PFDDefaultPanelInfoBar .Ctrl {
+ width: 100%; height: 60px; padding: 5px;
+ display: flex; flex-direction: column;
+ }
+ #Ctnr_PFDDefaultPanelInfoBar #Ctrl_PFDDefaultPanelAccelStatus {
+ height: 80px;
+ }
+ #Ctnr_PFDDefaultPanelInfoBar #Ctrl_PFDDefaultPanelWind {
+ height: 100px;
+ }
+ #CtrlGroup_PFDDefaultPanelFlapStatus .Ctrl {
+ height: 100%;
+ }
+ .InfoBarValue {
+ font-size: 1.50em;
+ }
+ #PFDDefaultPanelWindDirection {
+ width: 40px; height: 40px; margin: 5px 0 0 0;
+ display: flex; justify-content: center;
+ }
+ #Needle_PFDDefaultPanelWindDirection {
+ position: relative;
+ width: 10px; height: 100%;
+ }
+ #Needle_PFDDefaultPanelWindDirection .NeedleFg {
+ position: absolute; top: 5px; left: calc(50% - 1px);
+ width: 2px; height: calc(100% - 5px); border-radius: 1px;
+ }
+ #Needle_PFDDefaultPanelWindDirection .NeedleArrow {
+ height: 10px;
+ }
+ #CtrlGroup_PFDDefaultPanelFlapStatus .Ctrl {
+ justify-content: end;
+ }
+ #CtrlGroup_PFDDefaultPanelFlapStatus .Progbar {
+ width: 12px; height: 112px; border: 1px solid #000000; border-radius: 6px; margin: 5px 0 0 0; transform: scaleY(-1);
+ background-color: transparent;
+ }
+ #ProgbarFg_PFDDefaultPanelFlaps {
+ background-color: #000000;
+ }
+
+ /* FMA 1 */
+ #Ctnr_PFDDefaultPanelFMA1 {
+ position: absolute; top: 0; left: 275px;
+ width: 450px; height: 60px; border-radius: 5px;
+ background-color: #FFFFFF80;
+ }
+ #Ctnr_PFDDefaultPanelFMA1 .CtrlGroup {
+ width: 100%; height: 100%;
+ }
+ #Ctnr_PFDDefaultPanelFMA1 .Ctrl {
+ width: calc(100% / 3); height: 100%; padding: 5px;
+ text-align: center;
+ display: flex; flex-direction: column; justify-content: center; align-items: center;
+ }
+ .FMAValue {
+ font-size: 1.50em;
+ }
+
+ /* FMA 2 */
+ #Ctnr_PFDDefaultPanelFMA2 {
+ position: absolute; top: 120px; left: 425px;
+ width: 150px; height: 40px; border-radius: 5px; padding: 5px;
+ text-align: center;
+ display: flex; justify-content: center; align-items: center;
+ }
+ #Label_PFDDefaultPanelFlightMode {
+ font-size: 1.50em;
+ }
+
+ /* Speed */
+ #Ctnr_PFDDefaultPanelSpeed {
+ position: absolute; top: 100px; left: 140px;
+ width: 105px; height: 660px;
+ }
+ #Ctnr_PFDDefaultPanelSpeed > .CtrlGroup {
+ width: 100%; height: 100%;
+ }
+ /* Status */
+ #Ctrl_PFDDefaultPanelSpeedStatus {
+ position: absolute; top: calc((100% - 60px) / 2 - 40px); left: 0;
+ width: 100%; height: 80px; padding: 5px;
+ text-align: center;
+ display: flex; justify-content: center; align-items: center;
+ }
+
+ /* Tape */
+ .Tape {
+ position: absolute; top: 0; left: 0;
+ width: 100%; height: 100%; border-radius: 5px;
+ background-color: #FFFFFF80;
+ overflow: hidden;
+ }
+ #Ctrl_PFDDefaultPanelSpeedTape {
+ height: calc(100% - 60px);
+ }
+ .Tape .CtrlGroup {
+ position: absolute; top: unset; left: 0;
+ width: 100%;
+ flex-direction: column;
+ }
+ #CtrlGroup_PFDDefaultPanelSpeedTape {
+ top: calc(50% - 5000px);
+ height: 5000px;
+ }
+ .Tape .Ctrl {
+ width: 100%; height: 0;
+ display: flex; justify-content: space-between; align-items: center;
+ }
+ #CtrlGroup_PFDDefaultPanelSpeedTape .Ctrl {
+ flex-direction: row-reverse;
+ }
+ .TapeScale {
+ width: 20px; height: 2px; border-radius: 1px;
+ background-color: #00000080;
+ }
+ .TapeNumber {
+ width: calc(100% - 40px);
+ color: #00000080; font-size: 1.50em; text-align: end;
+ }
+
+ /* Additional indicators */
+ #Ctrl_PFDDefaultPanelSpeedAdditionalIndicators {
+ position: absolute; top: 0; left: 0;
+ width: calc(100% + 10px); height: calc(100% - 60px);
+ overflow: hidden;
+ pointer-events: none;
+ }
+ /* Speed trend */
+ #Needle_PFDDefaultPanelSpeedTrend {
+ position: absolute; top: unset; right: 20px;
+ width: 15px; height: 0;
+ }
+ #Needle_PFDDefaultPanelSpeedTrend.Decreasing {
+ transform: scaleY(-1);
+ }
+ #Needle_PFDDefaultPanelSpeedTrend .NeedleFg {
+ position: absolute; top: 5px; left: calc(50% - 1px);
+ width: 2px; height: calc(50% - 5px); border-radius: 1px;
+ pointer-events: auto;
+ }
+ #Needle_PFDDefaultPanelSpeedTrend .NeedleArrow {
+ height: 15px;
+ pointer-events: auto;
+ }
+
+ /* Other speeds */
+ #CtrlGroup_PFDDefaultPanelOtherSpeeds {
+ position: absolute; top: calc(50% - 5000px); right: 0;
+ width: 10px; height: 5000px;
+ }
+ #CtrlGroup_PFDDefaultPanelOtherSpeeds .Ctrl {
+ position: absolute; top: unset; left: 0;
+ width: 10px; height: 0;
+ background-color: #FF0000;
+ pointer-events: auto;
+ }
+ #CtrlGroup_PFDDefaultPanelOtherSpeeds #Ctrl_PFDDefaultPanelSpeedLimitMin {
+ bottom: 0;
+ }
+ #CtrlGroup_PFDDefaultPanelOtherSpeeds #Ctrl_PFDDefaultPanelSpeedLimitMax {
+ top: 0;
+ }
+
+ /* Balloon */
+ #Ctrl_PFDDefaultPanelSpeedBalloon {
+ position: absolute; top: calc((100% - 60px) / 2 - 35px); left: 0;
+ width: 90px; height: 70px;
+ filter: drop-shadow(0 0 0.5px #000000) drop-shadow(0 0 0.5px #000000) drop-shadow(0 0 0.5px #000000) drop-shadow(0 0 0.5px #000000);
+ }
+ #Ctrl_PFDDefaultPanelSpeedBalloon > .CtrlGroup {
+ width: 100%; height: 100%;
+ }
+ #Ctrl_PFDDefaultPanelSpeedBalloon > .CtrlGroup > .Ctrl {
+ position: absolute; top: 0; left: unset;
+ height: 100%;
+ background-color: #FFFFFF;
+ }
+ #Ctrl_PFDDefaultPanelSpeedBalloon .CtrlGroup .Balloon {
+ left: 0;
+ width: 70px; border-radius: 5px;
+ }
+ #Ctrl_PFDDefaultPanelSpeedBalloon .CtrlGroup .BalloonTail {
+ right: 0;
+ width: 20px; clip-path: polygon(100% 50%, 0 70%, 0 30%);
+ }
+ #Ctrl_PFDDefaultPanelSpeedBalloon .CtrlGroup .Balloon .CtrlGroup {
+ width: 100%; height: 100%; padding: 5px;
+ justify-content: end;
+ }
+ #Ctrl_PFDDefaultPanelSpeedBalloon .CtrlGroup .Balloon .Ctrl {
+ width: 20px; height: 100%;
+ }
+ .Balloon .Box {
+ background-color: transparent;
+ }
+ #RollingDigit_PFDDefaultPanelSpeed1 {
+ top: calc(-45px * 9);
+ }
+ #RollingDigit_PFDDefaultPanelSpeed2 {
+ top: calc(-45px * 10);
+ }
+ #RollingDigit_PFDDefaultPanelSpeed3 {
+ top: calc(15px - 30px * 18);
+ }
+ .RollingDigit span {
+ height: 30px; margin: 15px 0;
+ font-size: 2.25em;
+ }
+ #RollingDigit_PFDDefaultPanelSpeed3 span {
+ margin: 0;
+ }
+
+ /* Mach number */
+ #Ctrl_PFDDefaultPanelSpeedMachNumber {
+ position: absolute; bottom: 0; left: 0;
+ width: 100%; height: 60px; padding: 5px;
+ display: flex; align-items: center;
+ }
+ #Label_PFDDefaultPanelSpeedMachNumber {
+ font-size: 2.00em;
+ }
+
+ /* Altitude */
+ #Ctnr_PFDDefaultPanelAltitude {
+ position: absolute; top: 100px; left: 755px;
+ width: 140px; height: 660px;
+ }
+ #Ctnr_PFDDefaultPanelAltitude > .CtrlGroup {
+ width: 100%; height: 100%;
+ }
+ /* Status */
+ #Ctrl_PFDDefaultPanelAltitudeStatus {
+ position: absolute; top: calc((100% - 60px) / 2 - 40px); left: 0;
+ width: 100%; height: 80px; padding: 5px;
+ text-align: center;
+ display: flex; justify-content: center; align-items: center;
+ }
+
+ /* Tape */
+ #Ctrl_PFDDefaultPanelAltitudeTape {
+ height: calc(100% - 60px);
+ }
+ #CtrlGroup_PFDDefaultPanelAltitudeTape {
+ top: calc(50% - 37500px); /* 300px for 400ft. */
+ width: calc(100% - 20px); height: 39000px;
+ }
+
+ /* Additional indicators */
+ #Ctrl_PFDDefaultPanelAltitudeAdditionalIndicators {
+ position: absolute; top: 0; right: 0;
+ width: calc(100% + 20px); height: calc(100% - 60px);
+ overflow: hidden;
+ pointer-events: none;
+ }
+ /* Altitude trend */
+ #Needle_PFDDefaultPanelAltitudeTrend {
+ position: absolute; top: unset; left: 30px;
+ width: 15px; height: 0;
+ }
+ #Needle_PFDDefaultPanelAltitudeTrend.Decreasing {
+ transform: scaleY(-1);
+ }
+ #Needle_PFDDefaultPanelAltitudeTrend .NeedleFg {
+ position: absolute; top: 5px; left: calc(50% - 1px);
+ width: 2px; height: calc(50% - 5px); border-radius: 1px;
+ pointer-events: auto;
+ }
+ #Needle_PFDDefaultPanelAltitudeTrend .NeedleArrow {
+ height: 15px;
+ pointer-events: auto;
+ }
+
+ /* Other altitudes */
+ #CtrlGroup_PFDDefaultPanelOtherAltitudes {
+ position: absolute; top: calc(50% - 37500px); left: 0;
+ width: 20px; height: 39000px;
+ }
+ #CtrlGroup_PFDDefaultPanelOtherAltitudes .Ctrl {
+ position: absolute; top: unset; right: 0;
+ pointer-events: auto;
+ }
+ #CtrlGroup_PFDDefaultPanelOtherAltitudes #Ctrl_PFDDefaultPanelDecisionAltitude {
+ bottom: 1652.5px; /* Default decision height is 250ft. */
+ width: 20px; height: 70px; clip-path: polygon(100% 50%, 0 70%, 0 30%);
+ background-color: #0090D0;
+ }
+ #CtrlGroup_PFDDefaultPanelOtherAltitudes #Ctrl_PFDDefaultPanelDecisionAltitude.Active {
+ background-color: #E06000;
+ }
+ #CtrlGroup_PFDDefaultPanelOtherAltitudes #Ctrl_PFDDefaultPanelGroundAltitude {
+ bottom: 1460px; left: 20px;
+ width: 140px; height: 40px; border-top: 2px solid #00000080;
+ background: repeating-linear-gradient(45deg, transparent, transparent 4px, #00000080 5px, #00000080 6px, transparent 7px);
+ }
+
+ /* Balloon */
+ #Ctrl_PFDDefaultPanelAltitudeBalloon {
+ position: absolute; top: calc((100% - 60px) / 2 - 35px); right: 0;
+ width: 125px; height: 70px;
+ filter: drop-shadow(0 0 0.5px #000000) drop-shadow(0 0 0.5px #000000) drop-shadow(0 0 0.5px #000000) drop-shadow(0 0 0.5px #000000);
+ }
+ #Ctrl_PFDDefaultPanelAltitudeBalloon > .CtrlGroup {
+ width: 100%; height: 100%;
+ }
+ #Ctrl_PFDDefaultPanelAltitudeBalloon > .CtrlGroup > .Ctrl {
+ position: absolute; top: 0; left: unset;
+ height: 100%;
+ background-color: #FFFFFF;
+ }
+ #Ctrl_PFDDefaultPanelAltitudeBalloon .CtrlGroup .Balloon {
+ right: 0;
+ width: 105px; border-radius: 5px;
+ }
+ #Ctrl_PFDDefaultPanelAltitudeBalloon .CtrlGroup .BalloonTail {
+ left: 0;
+ width: 20px; clip-path: polygon(100% 30%, 100% 70%, 0 50%);
+ }
+ #Ctrl_PFDDefaultPanelAltitudeBalloon .CtrlGroup .Balloon .CtrlGroup {
+ width: 100%; height: 100%; padding: 5px;
+ justify-content: start;
+ }
+ #Ctrl_PFDDefaultPanelAltitudeBalloon .CtrlGroup .Balloon .Ctrl {
+ width: 20px; height: 100%;
+ }
+ #Ctrl_PFDDefaultPanelAltitudeBalloon .CtrlGroup .Balloon #Ctrl_PFDDefaultPanelAltitude4 {
+ width: 35px;
+ }
+ #RollingDigit_PFDDefaultPanelAltitude1 {
+ top: calc(-45px * 5);
+ }
+ #RollingDigit_PFDDefaultPanelAltitude2 {
+ top: calc(-45px * 10);
+ }
+ #RollingDigit_PFDDefaultPanelAltitude3 {
+ top: calc(-45px * 10);
+ }
+ #RollingDigit_PFDDefaultPanelAltitude4 {
+ top: calc(17.5px - 25px * 13);
+ }
+ #RollingDigit_PFDDefaultPanelAltitude4 span {
+ height: 25px; margin: 0;
+ font-size: 1.75em;
+ }
+
+ /* Metric */
+ #Ctrl_PFDDefaultPanelAltitudeMetric {
+ position: absolute; bottom: 0; left: 0;
+ width: 100%; height: 60px; padding: 5px;
+ text-align: end;
+ display: flex; justify-content: end; align-items: center;
+ }
+ #Label_PFDDefaultPanelAltitudeMetric {
+ font-size: 2.00em;
+ }
+
+ /* Vertical speed */
+ #Ctnr_PFDDefaultPanelVerticalSpeed {
+ position: absolute; top: 200px; left: 910px;
+ width: 90px; height: 400px;
+ }
+ #Ctnr_PFDDefaultPanelVerticalSpeed > .CtrlGroup {
+ width: 100%; height: 100%;
+ }
+ /* Status */
+ #Ctrl_PFDDefaultPanelVerticalSpeedStatus {
+ position: absolute; top: calc(50% - 40px); left: 0;
+ width: 100%; height: 80px; padding: 5px;
+ text-align: center;
+ display: flex; justify-content: center; align-items: center;
+ }
+
+ /* Tape */
+ #Ctrl_PFDDefaultPanelVerticalSpeedTape {
+ height: 100%;
+ }
+ #Ctrl_PFDDefaultPanelVerticalSpeedTape .CtrlGroup {
+ top: 20px;
+ height: calc(100% - 40px);
+ }
+ #Ctrl_PFDDefaultPanelVerticalSpeedTape .TapeScale {
+ width: 15px;
+ }
+ #Ctrl_PFDDefaultPanelVerticalSpeedTape .TapeNumber {
+ width: calc(100% - 25px);
+ text-align: start;
+ }
+
+ /* Needle */
+ #Ctrl_PFDDefaultPanelVerticalSpeedNeedle {
+ width: 100%; height: 100%;
+ overflow: hidden;
+ pointer-events: none;
+ }
+ #Needle_PFDDefaultPanelVerticalSpeed {
+ position: absolute; top: calc(50% - 100px); left: 117.5px;
+ width: 5px; height: 200px; transform: rotate(-90deg);
+ }
+ #Needle_PFDDefaultPanelVerticalSpeed .NeedleFg {
+ border-radius: 2.5px;
+ pointer-events: auto;
+ }
+
+ /* Balloon */
+ #Ctrl_PFDDefaultPanelVerticalSpeedBalloon {
+ position: absolute; top: calc(50% - 20px); left: 0;
+ width: 100%; height: 40px; border-radius: 5px; padding: 5px;
+ background-color: #FFFFFF; filter: drop-shadow(0 0 0.5px #000000) drop-shadow(0 0 0.5px #000000) drop-shadow(0 0 0.5px #000000) drop-shadow(0 0 0.5px #000000);
+ display: flex; align-items: center;
+ }
+ #Label_PFDDefaultPanelVerticalSpeedBalloon {
+ font-size: 1.75em;
+ }
+
+ /* Heading */
+ #Ctnr_PFDDefaultPanelHeading {
+ position: absolute; top: 700px; left: 200px;
+ width: 600px; height: 100px;
+ overflow: hidden;
+ }
+ #Ctnr_PFDDefaultPanelHeading > .CtrlGroup {
+ width: 100%; height: 100%;
+ }
+ /* Status */
+ #Ctrl_PFDDefaultPanelHeadingStatus {
+ position: absolute; top: calc(50% - 40px); left: calc(50% - 100px);
+ width: 200px; height: 80px; padding: 5px;
+ text-align: center;
+ display: flex; justify-content: center; align-items: center;
+ }
+
+ /* Tape */
+ #Ctrl_PFDDefaultPanelHeadingTape {
+ top: 0; left: -100px;
+ width: 800px; height: 800px; border-radius: 400px;
+ }
+ #CtrlGroup_PFDDefaultPanelHeadingTape {
+ width: 100%; height: 100%;
+ }
+ #CtrlGroup_PFDDefaultPanelHeadingTape .Ctrl {
+ position: absolute; top: 0; left: 50%;
+ width: 0; height: 100%;
+ flex-direction: column; justify-content: start;
+ }
+ #CtrlGroup_PFDDefaultPanelHeadingTape .TapeScale {
+ width: 2px; height: 15px;
+ }
+ #CtrlGroup_PFDDefaultPanelHeadingTape .TapeNumber {
+ width: unset; margin: 9px 0 0 0;
+ text-align: center;
+ }
+
+ /* Balloon */
+ #Ctrl_PFDDefaultPanelHeadingBalloon {
+ position: absolute; top: 1px; left: calc(50% - 35px);
+ width: 70px; height: 60px;
+ filter: drop-shadow(0 0 0.5px #000000) drop-shadow(0 0 0.5px #000000) drop-shadow(0 0 0.5px #000000) drop-shadow(0 0 0.5px #000000);
+ }
+ #Ctrl_PFDDefaultPanelHeadingBalloon > .CtrlGroup {
+ width: 100%; height: 100%;
+ }
+ #Ctrl_PFDDefaultPanelHeadingBalloon > .CtrlGroup > .Ctrl {
+ position: absolute; top: unset; left: 0;
+ width: 100%;
+ background-color: #FFFFFF;
+ }
+ #Ctrl_PFDDefaultPanelHeadingBalloon .CtrlGroup .Balloon {
+ top: 20px;
+ height: 40px; border-radius: 5px;
+ text-align: center;
+ display: flex; justify-content: center; align-items: center;
+ }
+ #Ctrl_PFDDefaultPanelHeadingBalloon .CtrlGroup .BalloonTail {
+ top: 0;
+ height: 20px; clip-path: polygon(50% 0, 70% 100%, 30% 100%);
+ }
+ #Label_PFDDefaultPanelHeadingBalloon {
+ font-size: 2.25em;
+ }
+
+ /* DME */
+ #Ctnr_PFDDefaultPanelDME {
+ position: absolute; top: 75px; left: 275px;
+ width: 120px; height: 85px; padding: 5px;
+ display: flex; flex-direction: column; justify-content: end;
+ }
+ #Label_PFDDefaultPanelDMEDistance, #Label_PFDDefaultPanelDMEETA {
+ font-size: 1.50em;
+ }
+
+ /* Radio altitude */
+ #Ctnr_PFDDefaultPanelRadioAltitude {
+ position: absolute; top: 75px; left: 640px;
+ width: 85px; height: 85px;
+ }
+ #Ctnr_PFDDefaultPanelRadioAltitude > .CtrlGroup,
+ #Ctnr_PFDDefaultPanelRadioAltitude > .CtrlGroup > .Ctrl,
+ #Ctnr_PFDDefaultPanelRadioAltitude .Ctrl .CtrlGroup {
+ width: 100%; height: 100%;
+ }
+ #Ctnr_PFDDefaultPanelRadioAltitude .Ctrl .CtrlGroup .Ctrl {
+ position: absolute; top: 0; left: 50%;
+ width: 0; height: 100%;
+ display: flex; flex-direction: column; justify-content: start; align-items: center;
+ }
+ #Ctnr_PFDDefaultPanelRadioAltitude .DialScale {
+ width: 3px; height: 8px; border-radius: 1.5px;
+ background-color: #000000;
+ }
+ #Ctnr_PFDDefaultPanelRadioAltitude .Progring {
+ position: absolute; top: 0; left: 0;
+ }
+ #Ctnr_PFDDefaultPanelRadioAltitude .ProgringFg {
+ stroke: #000000; stroke-width: 3; stroke-dasharray: 0, calc((85px - 3px) * 3.1415926);
+ }
+ #ProgringText_PFDDefaultPanelRadioAltitude {
+ font-size: 2.00em;
+ }
+
+ /* Decision altitude */
+ #Ctnr_PFDDefaultPanelDecisionAltitude {
+ position: absolute; top: 640px; left: 640px;
+ width: 85px; height: 60px; border-radius: 5px; padding: 5px;
+ display: flex; flex-direction: column; align-items: end;
+ }
+ #Ctnr_PFDDefaultPanelDecisionAltitude.Active {
+ background-color: #FFFFFF;
+ color: #D05000;
+ }
+ #Label_PFDDefaultPanelDecisionAltitudeValue {
+ font-size: 1.50em;
+ }
+
+ /* Warning */
+ #Ctnr_PFDDefaultPanelWarning {
+ position: absolute; top: 275px; left: 380px;
+ width: 240px; height: 50px; border-radius: 5px;
+ background-color: #FFFFFF;
+ text-align: center;
+ display: flex; justify-content: center; align-items: center;
+ }
+ #Label_PFDDefaultPanelWarning {
+ font-size: 2.00em;
+ }
+
+ /* Blink */
+ #Ctnr_PFDDefaultPanel .ModeChanged {
+ background-color: #FFFFFF; box-shadow: 0 0 0 3px #00A000;
+ color: #008000;
+ animation: Anim_PFDDefaultPanelModeChanged 1000ms infinite ease-in-out;
+ }
+ @keyframes Anim_PFDDefaultPanelModeChanged {
+ 0%, 75%, 100% {
+ box-shadow: none;
+ }
+ 25%, 50% {
+ box-shadow: 0 0 0 3px #00A000;
+ }
+ }
+ #Ctnr_PFDDefaultPanel .Caution {
+ box-shadow: 0 0 0 3px #E06000;
+ animation: Anim_PFDDefaultPanelCaution 500ms infinite ease-in-out;
+ }
+ @keyframes Anim_PFDDefaultPanelCaution {
+ 0%, 75%, 100% {
+ box-shadow: none;
+ }
+ 25%, 50% {
+ box-shadow: 0 0 0 3px #E06000;
+ }
+ }
+ #Ctnr_PFDDefaultPanel .Warning {
+ box-shadow: 0 0 0 3px #FF0000;
+ color: #E00000;
+ animation: Anim_PFDDefaultPanelWarning 500ms infinite ease-in-out;
+ }
+ @keyframes Anim_PFDDefaultPanelWarning {
+ 0%, 75%, 100% {
+ box-shadow: none;
+ }
+ 25%, 50% {
+ box-shadow: 0 0 0 3px #FF0000;
+ }
+ }
+ #Ctnr_PFDDefaultPanel .BankAngleWarning {
+ background-color: #FF0000;
+ stroke: #FF0000;
+ animation: Anim_PFDDefaultPanelBankAngleWarning 500ms infinite ease-in-out;
+ }
+ @keyframes Anim_PFDDefaultPanelBankAngleWarning {
+ 0%, 75%, 100% {
+ background-color: #000000;
+ stroke: #000000;
+ }
+ 25%, 50% {
+ background-color: #FF0000;
+ stroke: #FF0000;
+ }
+ }
+
+ /* Menu */
+ #Ctnr_PFDMenu {
+ position: absolute; top: 0; right: 0;
+ }
+ #Ctnr_PFDMenu > .CtrlGroup {
+ width: 35px; height: 35px;
+ }
+ #Ctnr_PFDMenu > .CtrlGroup > .Ctrl {
+ width: 100%;
+ }
+ #Window_PFDMenu {
+ position: absolute; top: 0; right: 0;
+ width: 320px; height: 600px; max-height: calc(100vh - 90px);
+ overflow: scroll;
+ }
+ #Html:has(#Topbar.Hidden) #Window_PFDMenu {
+ max-height: calc(100vh - 30px);
+ }
+ /* Sections */
+ #CtrlGroup_PFDMenuCloseButton {
+ position: sticky; top: 0; left: 0; z-index: 1;
+ width: 320px;
+ }
+ #CtrlGroup_PFDMenuContents {
+ width: 320px; padding: 15px 0;
+ }
+ #CtrlGroup_PFDMenuCloseButton .Ctrl {
+ position: absolute; top: 15px; right: 15px;
+ width: 35px;
+ }
+ .PFDMenuSection {
+ width: 100%; height: unset; padding: 0 15px;
+ }
+ .PFDMenuSection.Hidden {
+ padding: 0 15px !important;
+ }
+ .PFDMenuTitle .Ctrl {
+ width: calc(100% - 50px); height: unset; min-height: 35px;
+ }
+ .PFDMenuTitle h3 {
+ padding: 0;
+ font-size: 1.25em;
+ }
+ .Icon.CollapseCaret {
+ margin: 0 0 0 5px;
+ }
+ .PFDMenuSection:has(.PFDMenuContent.Hidden) .Icon.CollapseCaret {
+ transform: rotate(-90deg);
+ }
+ .PFDMenuContent {
+ border-bottom: 1px solid #00000040; padding: 10px 0 15px 0; margin: 0 0 15px 0;
+ }
+ .PFDMenuContent > .Ctrl {
+ width: 100%; height: unset; margin: 0 0 10px 0;
+ }
+
+ /* Area specific */
+ /* Ctrl */
+ /* Fullscreen & Reset accel speed */
+ #Ctrl_PFDToggleFullscreen, #Ctrl_PFDResetAccelSpeed {
+ height: 35px;
+ }
+
+ /* Manual maneuver */
+ #Ctrl_PFDManualManeuver > .CtrlGroup {
+ width: 255px;
+ }
+ #Ctrl_PFDManualManeuver > .CtrlGroup > .Ctrl {
+ height: 110px; margin: 0 0 10px 0;
+ }
+ #Ctrl_PFDAttitudeManeuver {
+ width: 125px;
+ }
+ #Ctrl_PFDSpeedManeuver, #Ctrl_PFDAltitudeManeuver {
+ width: 35px;
+ }
+ #Ctrl_PFDManualManeuver > .CtrlGroup > .Ctrl > span {
+ width: 100%; height: 30px;
+ text-align: center;
+ display: flex; justify-content: center;
+ }
+ #Ctrl_PFDManualManeuver .Ctrl .CtrlGroup {
+ position: relative;
+ width: 100%; height: 80px;
+ }
+ #CtrlGroup_PFDAttitudeManeuver .Ctrl {
+ position: absolute; top: unset; left: unset;
+ width: 35px;
+ }
+ #Ctrl_PFDManualManeuver .Ctrl #Ctrl_PFDPitchDown {
+ top: 0; left: 45px;
+ }
+ #Ctrl_PFDManualManeuver .Ctrl #Ctrl_PFDPitchUp {
+ top: 45px; left: 45px;
+ }
+ #Ctrl_PFDManualManeuver .Ctrl #Ctrl_PFDRollLeft {
+ top: 22.5px; left: 0;
+ }
+ #Ctrl_PFDManualManeuver .Ctrl #Ctrl_PFDRollRight {
+ top: 22.5px; left: 90px;
+ }
+ #CtrlGroup_PFDSpeedManeuver, #CtrlGroup_PFDAltitudeManeuver {
+ flex-direction: column;
+ }
+ #CtrlGroup_PFDSpeedManeuver .Ctrl, #CtrlGroup_PFDAltitudeManeuver .Ctrl {
+ width: 35px;
+ }
+
+ /* Flaps */
+ #Ctrl_PFDFlaps > .CtrlGroup {
+ width: 190px;
+ }
+ #Ctrl_PFDFlapsSlider {
+ width: 50px; height: 280px;
+ }
+ #Ctrl_PFDFlapsSlider label {
+ flex-direction: column;
+ }
+ #Ctrl_PFDFlapsSlider label > * {
+ margin: 5px 0 0 0;
+ }
+ #Ctrl_PFDFlapsSlider .HotkeyIndicator {
+ top: calc(50% - 27.5px);
+ height: 55px;
+ }
+ #Label_PFDFlaps {
+ width: 100%; height: 20px;
+ text-align: center;
+ display: flex; justify-content: center; align-items: center;
+ }
+ #Slider_PFDFlaps {
+ width: 100%; height: 240px;
+ writing-mode: vertical-lr;
+ }
+ #Ctrl_PFDFlapsReference {
+ width: 120px; height: 270px;
+ display: flex; flex-wrap: wrap;
+ }
+ #Ctrl_PFDFlapsReference > span {
+ width: 50%; height: 20px; margin: 5px 0;
+ display: flex; align-items: center;
+ }
+ #Ctrl_PFDFlapsReference .CtrlGroup {
+ position: relative;
+ width: 50%; height: 220px; margin: 10px 0;
+ }
+ #Ctrl_PFDFlapsReference .Ctrl {
+ position: absolute; top: unset; left: 0;
+ width: 100%; height: 0;
+ display: flex; justify-content: space-between; align-items: center;
+ }
+ .FlapsScale {
+ width: 15px; height: 1px; border-radius: 0.5px;
+ background-color: #000000;
+ }
+ .FlapsLabel {
+ width: calc(100% - 20px);
+ }
+ #Ctrl_PFDFlapsReference #Ctrl_PFDFlapsReferenceB738Scale1To5 {
+ top: 50%;
+ }
+ #Ctrl_PFDFlapsReference #Ctrl_PFDFlapsReferenceB738Scale10 {
+ top: 73%;
+ }
+ #Ctrl_PFDFlapsReference #Ctrl_PFDFlapsReferenceB738Scale15 {
+ top: 78%;
+ }
+ #Ctrl_PFDFlapsReference #Ctrl_PFDFlapsReferenceB738Scale25 {
+ top: 84%;
+ }
+ #Ctrl_PFDFlapsReference #Ctrl_PFDFlapsReferenceB738Scale30 {
+ top: 92%;
+ }
+ #Ctrl_PFDFlapsReference #Ctrl_PFDFlapsReferenceB738Scale40 {
+ top: 100%;
+ }
+ #Ctrl_PFDFlapsReference #Ctrl_PFDFlapsReferenceA320Scale1 {
+ top: 69%;
+ }
+ #Ctrl_PFDFlapsReference #Ctrl_PFDFlapsReferenceA320Scale1PlusF {
+ top: 78%;
+ }
+ #Ctrl_PFDFlapsReference #Ctrl_PFDFlapsReferenceA320Scale2 {
+ top: 86%;
+ }
+ #Ctrl_PFDFlapsReference #Ctrl_PFDFlapsReferenceA320Scale3 {
+ top: 95%;
+ }
+ #Ctrl_PFDFlapsReference #Ctrl_PFDFlapsReferenceA320ScaleFULL {
+ top: 100%;
+ }
+
+ /* Options */
+ #CtrlGroup_PFDOptions .Ctrl {
+ height: 35px; margin: 0 0 10px 0;
+ }
+ #CtrlGroup_PFDOptions .Combobox {
+ width: 160px;
+ }
+
+ /* Tech info */
+ #PFDTechInfoSpeedVectorGraph {
+ position: relative;
+ width: 100px; height: 100px; border-radius: 5px;
+ background-color: #00000020;
+ overflow: hidden;
+ }
+ #Needle_PFDTechInfoSpeedVectorGraph {
+ position: absolute; top: unset; left: calc(50% - 2.5px);
+ width: 5px; height: 0;
+ }
+ #Needle_PFDTechInfoSpeedVectorGraph .NeedleFg {
+ position: absolute; top: 5px; left: calc(50% - 0.5px);
+ width: 1px; height: calc(50% - 5px); border-radius: 0.5px;
+ }
+ #Needle_PFDTechInfoSpeedVectorGraph .NeedleArrow {
+ height: 10px;
+ }
+
+ /* Settings */
+ #Settings .BeforeSmallButtonCtrl {
+ width: calc(100% - 45px);
+ }
+ #Settings .SmallButtonCtrl {
+ width: 35px;
+ }
+
+/* Responsive web design */
+@media (max-width: 880px) {
+ /* Area specific */
+ /* Header */
+ #DropctrlGroup_Nav {
+ height: 107px;
+ }
+}
+@media (min-width: 830px) { /* Optimization for long fieldsets exceeding 600px height. */
+ /* Area specific */
+ /* Main */
+ /* Settings */
+ #Item_SettingsSpeed {
+ width: 800px;
+ }
+ #Item_SettingsSpeed .Ctrl { /* .WidthDividedBy2 */
+ width: calc((100% - 10px) / 2);
+ }
+ #Item_SettingsSpeed .BeforeSmallButtonCtrl {
+ width: calc((100% - 10px) / 2 - 45px);
+ }
+ #Item_SettingsSpeed .SmallButtonCtrl {
+ width: 35px;
+ }
+}
diff --git a/PROJECT/GPS-PFD/styles/style_Dark.css b/PROJECT/GPS-PFD/styles/style_Dark.css
new file mode 100644
index 0000000..5f83bb0
--- /dev/null
+++ b/PROJECT/GPS-PFD/styles/style_Dark.css
@@ -0,0 +1,217 @@
+/* For SamToki.github.io/GPS-PFD */
+/* Released under GNU GPL v3 open source license. */
+/* © 2025 SAM TOKI STUDIO */
+
+/* This is a theme variant, so only colors along with few things are configured. */
+
+/* Area specific */
+ /* Main */
+ /* PFD */
+ /* Panels */
+ /* Default panel */
+ /* Attitude */
+ /* Status */
+ .StatusBox {
+ background-color: #20202080;
+ }
+
+ /* Bg (Artificial horizon) */
+ #PFDDefaultPanelAttitudeSky {
+ background-color: #0080C0C0;
+ }
+ #PFDDefaultPanelAttitudeTerrain {
+ background-color: #806000C0;
+ }
+
+ /* Pitch */
+ #CtrlGroup_PFDDefaultPanelAttitudePitch .AttitudeScale0,
+ #CtrlGroup_PFDDefaultPanelAttitudePitch .AttitudeScale1,
+ #CtrlGroup_PFDDefaultPanelAttitudePitch .AttitudeScale2,
+ #CtrlGroup_PFDDefaultPanelAttitudePitch .AttitudeScale4 {
+ background-color: #FFFFFF;
+ }
+
+ /* Roll */
+ #CtrlGroup_PFDDefaultPanelAttitudeRollScale .AttitudeScale0,
+ #CtrlGroup_PFDDefaultPanelAttitudeRollScale .AttitudeScale1,
+ #CtrlGroup_PFDDefaultPanelAttitudeRollScale .AttitudeScale2 {
+ background-color: #FFFFFF;
+ }
+ #ProgringFg_PFDDefaultPanelAttitudeRoll {
+ stroke: #FFFFFF;
+ }
+ #PFDDefaultPanelAttitudeRollPointer {
+ background-color: #FFFFFF;
+ }
+
+ /* Aircraft symbol */
+ #Ctrl_PFDDefaultPanelAttitudeAircraftSymbol .CtrlGroup {
+ filter: drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF);
+ }
+ #Ctrl_PFDDefaultPanelAttitudeAircraftSymbol .Ctrl {
+ background-color: #202020;
+ }
+
+ /* Info bar */
+ #CtrlGroup_PFDDefaultPanelFlapStatus .Progbar {
+ border: 1px solid #FFFFFF;
+ }
+ #ProgbarFg_PFDDefaultPanelFlaps {
+ background-color: #FFFFFF;
+ }
+
+ /* FMA 1 */
+ #Ctnr_PFDDefaultPanelFMA1 {
+ background-color: #20202080;
+ }
+
+ /* Speed */
+ /* Tape */
+ .Tape {
+ background-color: #20202080;
+ }
+ .TapeScale {
+ background-color: #FFFFFF80;
+ }
+ .TapeNumber {
+ color: #FFFFFF80;
+ }
+
+ /* Additional indicators */
+ /* Other speeds */
+ #CtrlGroup_PFDDefaultPanelOtherSpeeds .Ctrl {
+ background-color: #FF4040;
+ }
+
+ /* Balloon */
+ #Ctrl_PFDDefaultPanelSpeedBalloon {
+ filter: drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF);
+ }
+ #Ctrl_PFDDefaultPanelSpeedBalloon > .CtrlGroup > .Ctrl {
+ background-color: #202020;
+ }
+
+ /* Altitude */
+ /* Additional indicators */
+ /* Other altitudes */
+ #CtrlGroup_PFDDefaultPanelOtherAltitudes #Ctrl_PFDDefaultPanelDecisionAltitude {
+ background-color: #00A0E0;
+ }
+ #CtrlGroup_PFDDefaultPanelOtherAltitudes #Ctrl_PFDDefaultPanelDecisionAltitude.Active {
+ background-color: #FF8000;
+ }
+ #CtrlGroup_PFDDefaultPanelOtherAltitudes #Ctrl_PFDDefaultPanelGroundAltitude {
+ border-top: 2px solid #FFFFFF80;
+ background: repeating-linear-gradient(45deg, transparent, transparent 4px, #FFFFFF80 5px, #FFFFFF80 6px, transparent 7px);
+ }
+
+ /* Balloon */
+ #Ctrl_PFDDefaultPanelAltitudeBalloon {
+ filter: drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF);
+ }
+ #Ctrl_PFDDefaultPanelAltitudeBalloon > .CtrlGroup > .Ctrl {
+ background-color: #202020;
+ }
+
+ /* Vertical speed */
+ /* Balloon */
+ #Ctrl_PFDDefaultPanelVerticalSpeedBalloon {
+ background-color: #202020; filter: drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF);
+ }
+
+ /* Heading */
+ /* Balloon */
+ #Ctrl_PFDDefaultPanelHeadingBalloon {
+ filter: drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF);
+ }
+ #Ctrl_PFDDefaultPanelHeadingBalloon > .CtrlGroup > .Ctrl {
+ background-color: #202020;
+ }
+
+ /* Radio altitude */
+ #Ctnr_PFDDefaultPanelRadioAltitude .DialScale {
+ background-color: #FFFFFF;
+ }
+ #Ctnr_PFDDefaultPanelRadioAltitude .ProgringFg {
+ stroke: #FFFFFF;
+ }
+
+ /* Decision altitude */
+ #Ctnr_PFDDefaultPanelDecisionAltitude.Active {
+ background-color: #202020;
+ color: #FFA000;
+ }
+
+ /* Warning */
+ #Ctnr_PFDDefaultPanelWarning {
+ background-color: #202020;
+ }
+
+ /* Blink */
+ #Ctnr_PFDDefaultPanel .ModeChanged {
+ background-color: #202020; box-shadow: 0 0 0 3px #00C000;
+ color: #00E000;
+ }
+ @keyframes Anim_PFDDefaultPanelModeChanged {
+ 0%, 75%, 100% {
+ box-shadow: none;
+ }
+ 25%, 50% {
+ box-shadow: 0 0 0 3px #00C000;
+ }
+ }
+ #Ctnr_PFDDefaultPanel .Caution {
+ box-shadow: 0 0 0 3px #FF8000;
+ }
+ @keyframes Anim_PFDDefaultPanelCaution {
+ 0%, 75%, 100% {
+ box-shadow: none;
+ }
+ 25%, 50% {
+ box-shadow: 0 0 0 3px #FF8000;
+ }
+ }
+ #Ctnr_PFDDefaultPanel .Warning {
+ box-shadow: 0 0 0 3px #FF4040;
+ color: #FF6060;
+ }
+ @keyframes Anim_PFDDefaultPanelWarning {
+ 0%, 75%, 100% {
+ box-shadow: none;
+ }
+ 25%, 50% {
+ box-shadow: 0 0 0 3px #FF4040;
+ }
+ }
+ #Ctnr_PFDDefaultPanel .BankAngleWarning {
+ background-color: #FF4040;
+ stroke: #FF4040;
+ }
+ @keyframes Anim_PFDDefaultPanelBankAngleWarning {
+ 0%, 75%, 100% {
+ background-color: #FFFFFF;
+ stroke: #FFFFFF;
+ }
+ 25%, 50% {
+ background-color: #FF4040;
+ stroke: #FF4040;
+ }
+ }
+
+ /* Menu */
+ /* Sections */
+ .PFDMenuContent {
+ border-bottom: 1px solid #FFFFFF40;
+ }
+
+ /* Area specific */
+ /* Ctrl */
+ /* Flaps */
+ .FlapsScale {
+ background-color: #FFFFFF;
+ }
+
+ /* Tech info */
+ #PFDTechInfoSpeedVectorGraph {
+ background-color: #FFFFFF20;
+ }
diff --git a/PROJECT/GPS-PFD/styles/style_Genshin.css b/PROJECT/GPS-PFD/styles/style_Genshin.css
new file mode 100644
index 0000000..a0908a7
--- /dev/null
+++ b/PROJECT/GPS-PFD/styles/style_Genshin.css
@@ -0,0 +1,217 @@
+/* For SamToki.github.io/GPS-PFD */
+/* Released under GNU GPL v3 open source license. */
+/* © 2025 SAM TOKI STUDIO */
+
+/* This is a theme variant, so only colors along with few things are configured. */
+
+/* Area specific */
+ /* Main */
+ /* PFD */
+ /* Panels */
+ /* Default panel */
+ /* Attitude */
+ /* Status */
+ .StatusBox {
+ background-color: #48526780;
+ }
+
+ /* Bg (Artificial horizon) */
+ #PFDDefaultPanelAttitudeSky {
+ background-color: #0080C0C0;
+ }
+ #PFDDefaultPanelAttitudeTerrain {
+ background-color: #806000C0;
+ }
+
+ /* Pitch */
+ #CtrlGroup_PFDDefaultPanelAttitudePitch .AttitudeScale0,
+ #CtrlGroup_PFDDefaultPanelAttitudePitch .AttitudeScale1,
+ #CtrlGroup_PFDDefaultPanelAttitudePitch .AttitudeScale2,
+ #CtrlGroup_PFDDefaultPanelAttitudePitch .AttitudeScale4 {
+ background-color: #EEE4D9;
+ }
+
+ /* Roll */
+ #CtrlGroup_PFDDefaultPanelAttitudeRollScale .AttitudeScale0,
+ #CtrlGroup_PFDDefaultPanelAttitudeRollScale .AttitudeScale1,
+ #CtrlGroup_PFDDefaultPanelAttitudeRollScale .AttitudeScale2 {
+ background-color: #EEE4D9;
+ }
+ #ProgringFg_PFDDefaultPanelAttitudeRoll {
+ stroke: #EEE4D9;
+ }
+ #PFDDefaultPanelAttitudeRollPointer {
+ background-color: #EEE4D9;
+ }
+
+ /* Aircraft symbol */
+ #Ctrl_PFDDefaultPanelAttitudeAircraftSymbol .CtrlGroup {
+ filter: drop-shadow(0 0 0.5px #EEE4D9) drop-shadow(0 0 0.5px #EEE4D9) drop-shadow(0 0 0.5px #EEE4D9) drop-shadow(0 0 0.5px #EEE4D9);
+ }
+ #Ctrl_PFDDefaultPanelAttitudeAircraftSymbol .Ctrl {
+ background-color: #485267;
+ }
+
+ /* Info bar */
+ #CtrlGroup_PFDDefaultPanelFlapStatus .Progbar {
+ border: 1px solid #EEE4D9;
+ }
+ #ProgbarFg_PFDDefaultPanelFlaps {
+ background-color: #EEE4D9;
+ }
+
+ /* FMA 1 */
+ #Ctnr_PFDDefaultPanelFMA1 {
+ background-color: #48526780;
+ }
+
+ /* Speed */
+ /* Tape */
+ .Tape {
+ background-color: #48526780;
+ }
+ .TapeScale {
+ background-color: #EEE4D980;
+ }
+ .TapeNumber {
+ color: #EEE4D980;
+ }
+
+ /* Additional indicators */
+ /* Other speeds */
+ #CtrlGroup_PFDDefaultPanelOtherSpeeds .Ctrl {
+ background-color: #FF4040;
+ }
+
+ /* Balloon */
+ #Ctrl_PFDDefaultPanelSpeedBalloon {
+ filter: drop-shadow(0 0 0.5px #EEE4D9) drop-shadow(0 0 0.5px #EEE4D9) drop-shadow(0 0 0.5px #EEE4D9) drop-shadow(0 0 0.5px #EEE4D9);
+ }
+ #Ctrl_PFDDefaultPanelSpeedBalloon > .CtrlGroup > .Ctrl {
+ background-color: #485267;
+ }
+
+ /* Altitude */
+ /* Additional indicators */
+ /* Other altitudes */
+ #CtrlGroup_PFDDefaultPanelOtherAltitudes #Ctrl_PFDDefaultPanelDecisionAltitude {
+ background-color: #00A0E0;
+ }
+ #CtrlGroup_PFDDefaultPanelOtherAltitudes #Ctrl_PFDDefaultPanelDecisionAltitude.Active {
+ background-color: #FF8000;
+ }
+ #CtrlGroup_PFDDefaultPanelOtherAltitudes #Ctrl_PFDDefaultPanelGroundAltitude {
+ border-top: 2px solid #EEE4D980;
+ background: repeating-linear-gradient(45deg, transparent, transparent 4px, #EEE4D980 5px, #EEE4D980 6px, transparent 7px);
+ }
+
+ /* Balloon */
+ #Ctrl_PFDDefaultPanelAltitudeBalloon {
+ filter: drop-shadow(0 0 0.5px #EEE4D9) drop-shadow(0 0 0.5px #EEE4D9) drop-shadow(0 0 0.5px #EEE4D9) drop-shadow(0 0 0.5px #EEE4D9);
+ }
+ #Ctrl_PFDDefaultPanelAltitudeBalloon > .CtrlGroup > .Ctrl {
+ background-color: #485267;
+ }
+
+ /* Vertical speed */
+ /* Balloon */
+ #Ctrl_PFDDefaultPanelVerticalSpeedBalloon {
+ background-color: #485267; filter: drop-shadow(0 0 0.5px #EEE4D9) drop-shadow(0 0 0.5px #EEE4D9) drop-shadow(0 0 0.5px #EEE4D9) drop-shadow(0 0 0.5px #EEE4D9);
+ }
+
+ /* Heading */
+ /* Balloon */
+ #Ctrl_PFDDefaultPanelHeadingBalloon {
+ filter: drop-shadow(0 0 0.5px #EEE4D9) drop-shadow(0 0 0.5px #EEE4D9) drop-shadow(0 0 0.5px #EEE4D9) drop-shadow(0 0 0.5px #EEE4D9);
+ }
+ #Ctrl_PFDDefaultPanelHeadingBalloon > .CtrlGroup > .Ctrl {
+ background-color: #485267;
+ }
+
+ /* Radio altitude */
+ #Ctnr_PFDDefaultPanelRadioAltitude .DialScale {
+ background-color: #EEE4D9;
+ }
+ #Ctnr_PFDDefaultPanelRadioAltitude .ProgringFg {
+ stroke: #EEE4D9;
+ }
+
+ /* Decision altitude */
+ #Ctnr_PFDDefaultPanelDecisionAltitude.Active {
+ background-color: #485267;
+ color: #FFB000;
+ }
+
+ /* Warning */
+ #Ctnr_PFDDefaultPanelWarning {
+ background-color: #485267;
+ }
+
+ /* Blink */
+ #Ctnr_PFDDefaultPanel .ModeChanged {
+ background-color: #485267; box-shadow: 0 0 0 3px #00C000;
+ color: #96D722;
+ }
+ @keyframes Anim_PFDDefaultPanelModeChanged {
+ 0%, 75%, 100% {
+ box-shadow: none;
+ }
+ 25%, 50% {
+ box-shadow: 0 0 0 3px #00C000;
+ }
+ }
+ #Ctnr_PFDDefaultPanel .Caution {
+ box-shadow: 0 0 0 3px #FF8000;
+ }
+ @keyframes Anim_PFDDefaultPanelCaution {
+ 0%, 75%, 100% {
+ box-shadow: none;
+ }
+ 25%, 50% {
+ box-shadow: 0 0 0 3px #FF8000;
+ }
+ }
+ #Ctnr_PFDDefaultPanel .Warning {
+ box-shadow: 0 0 0 3px #FF4040;
+ color: #FF7070;
+ }
+ @keyframes Anim_PFDDefaultPanelWarning {
+ 0%, 75%, 100% {
+ box-shadow: none;
+ }
+ 25%, 50% {
+ box-shadow: 0 0 0 3px #FF4040;
+ }
+ }
+ #Ctnr_PFDDefaultPanel .BankAngleWarning {
+ background-color: #FF4040;
+ stroke: #FF4040;
+ }
+ @keyframes Anim_PFDDefaultPanelBankAngleWarning {
+ 0%, 75%, 100% {
+ background-color: #EEE4D9;
+ stroke: #EEE4D9;
+ }
+ 25%, 50% {
+ background-color: #FF4040;
+ stroke: #FF4040;
+ }
+ }
+
+ /* Menu */
+ /* Sections */
+ .PFDMenuContent {
+ border-bottom: 1px solid #EEE4D940;
+ }
+
+ /* Area specific */
+ /* Ctrl */
+ /* Flaps */
+ .FlapsScale {
+ background-color: #EEE4D9;
+ }
+
+ /* Tech info */
+ #PFDTechInfoSpeedVectorGraph {
+ background-color: #EEE4D920;
+ }
diff --git a/PROJECT/GPS-PFD/styles/style_HighContrast.css b/PROJECT/GPS-PFD/styles/style_HighContrast.css
new file mode 100644
index 0000000..b352261
--- /dev/null
+++ b/PROJECT/GPS-PFD/styles/style_HighContrast.css
@@ -0,0 +1,222 @@
+/* For SamToki.github.io/GPS-PFD */
+/* Released under GNU GPL v3 open source license. */
+/* © 2025 SAM TOKI STUDIO */
+
+/* This is a theme variant, so only colors along with few things are configured. */
+
+/* Area specific */
+ /* Main */
+ /* PFD */
+ /* Panels */
+ /* Default panel */
+ /* Attitude */
+ /* Status */
+ .StatusBox {
+ background-color: #404040;
+ }
+
+ /* Bg (Artificial horizon) */
+ #PFDDefaultPanelAttitudeSky {
+ background-color: #0060A0;
+ }
+ #PFDDefaultPanelAttitudeTerrain {
+ background-color: #604000;
+ }
+
+ /* Pitch */
+ #CtrlGroup_PFDDefaultPanelAttitudePitch .AttitudeScale0,
+ #CtrlGroup_PFDDefaultPanelAttitudePitch .AttitudeScale1,
+ #CtrlGroup_PFDDefaultPanelAttitudePitch .AttitudeScale2,
+ #CtrlGroup_PFDDefaultPanelAttitudePitch .AttitudeScale4 {
+ background-color: #FFFFFF;
+ }
+
+ /* Roll */
+ #CtrlGroup_PFDDefaultPanelAttitudeRollScale .AttitudeScale0,
+ #CtrlGroup_PFDDefaultPanelAttitudeRollScale .AttitudeScale1,
+ #CtrlGroup_PFDDefaultPanelAttitudeRollScale .AttitudeScale2 {
+ background-color: #FFFFFF;
+ }
+ #ProgringFg_PFDDefaultPanelAttitudeRoll {
+ stroke: #FFFFFF;
+ }
+ #PFDDefaultPanelAttitudeRollPointer {
+ background-color: #FFFFFF;
+ }
+
+ /* Aircraft symbol */
+ #Ctrl_PFDDefaultPanelAttitudeAircraftSymbol .CtrlGroup {
+ filter: drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF);
+ }
+ #Ctrl_PFDDefaultPanelAttitudeAircraftSymbol .Ctrl {
+ background-color: #000000;
+ }
+
+ /* Info bar */
+ #CtrlGroup_PFDDefaultPanelFlapStatus .Progbar {
+ border: 1px solid #FFFFFF;
+ }
+ #ProgbarFg_PFDDefaultPanelFlaps {
+ background-color: #FFFFFF;
+ }
+
+ /* FMA 1 */
+ #Ctnr_PFDDefaultPanelFMA1 {
+ background-color: #404040;
+ }
+
+ /* Speed */
+ /* Tape */
+ .Tape {
+ background-color: #404040;
+ }
+ .TapeScale {
+ background-color: #FFFFFF;
+ }
+ .TapeNumber {
+ color: #FFFFFF;
+ }
+
+ /* Additional indicators */
+ /* Speed trend */
+ #Needle_PFDDefaultPanelSpeedTrend .NeedleFg, #Needle_PFDDefaultPanelSpeedTrend .NeedleArrow {
+ background-color: #00FF00;
+ }
+
+ /* Other speeds */
+ #CtrlGroup_PFDDefaultPanelOtherSpeeds .Ctrl {
+ background-color: #FF4040;
+ }
+
+ /* Balloon */
+ #Ctrl_PFDDefaultPanelSpeedBalloon {
+ filter: drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF);
+ }
+ #Ctrl_PFDDefaultPanelSpeedBalloon > .CtrlGroup > .Ctrl {
+ background-color: #000000;
+ }
+
+ /* Altitude */
+ /* Additional indicators */
+ /* Other altitudes */
+ #CtrlGroup_PFDDefaultPanelOtherAltitudes #Ctrl_PFDDefaultPanelDecisionAltitude {
+ background-color: #00FF00;
+ }
+ #CtrlGroup_PFDDefaultPanelOtherAltitudes #Ctrl_PFDDefaultPanelDecisionAltitude.Active {
+ background-color: #FFE000;
+ }
+ #CtrlGroup_PFDDefaultPanelOtherAltitudes #Ctrl_PFDDefaultPanelGroundAltitude {
+ border-top: 2px solid #FFFFFF;
+ background: repeating-linear-gradient(45deg, transparent, transparent 4px, #FFFFFF 5px, #FFFFFF 6px, transparent 7px);
+ }
+
+ /* Balloon */
+ #Ctrl_PFDDefaultPanelAltitudeBalloon {
+ filter: drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF);
+ }
+ #Ctrl_PFDDefaultPanelAltitudeBalloon > .CtrlGroup > .Ctrl {
+ background-color: #000000;
+ }
+
+ /* Vertical speed */
+ /* Balloon */
+ #Ctrl_PFDDefaultPanelVerticalSpeedBalloon {
+ background-color: #000000; filter: drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF);
+ }
+
+ /* Heading */
+ /* Balloon */
+ #Ctrl_PFDDefaultPanelHeadingBalloon {
+ filter: drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF) drop-shadow(0 0 0.5px #FFFFFF);
+ }
+ #Ctrl_PFDDefaultPanelHeadingBalloon > .CtrlGroup > .Ctrl {
+ background-color: #000000;
+ }
+
+ /* Radio altitude */
+ #Ctnr_PFDDefaultPanelRadioAltitude .DialScale {
+ background-color: #FFFFFF;
+ }
+ #Ctnr_PFDDefaultPanelRadioAltitude .ProgringFg {
+ stroke: #FFFFFF;
+ }
+
+ /* Decision altitude */
+ #Ctnr_PFDDefaultPanelDecisionAltitude.Active {
+ background-color: #000000;
+ color: #FFE000;
+ }
+
+ /* Warning */
+ #Ctnr_PFDDefaultPanelWarning {
+ background-color: #000000;
+ }
+
+ /* Blink */
+ #Ctnr_PFDDefaultPanel .ModeChanged {
+ background-color: #000000; box-shadow: 0 0 0 3px #00FF00;
+ color: #00FF00;
+ }
+ @keyframes Anim_PFDDefaultPanelModeChanged {
+ 0%, 75%, 100% {
+ box-shadow: none;
+ }
+ 25%, 50% {
+ box-shadow: 0 0 0 3px #00FF00;
+ }
+ }
+ #Ctnr_PFDDefaultPanel .Caution {
+ box-shadow: 0 0 0 3px #FFE000;
+ }
+ @keyframes Anim_PFDDefaultPanelCaution {
+ 0%, 75%, 100% {
+ box-shadow: none;
+ }
+ 25%, 50% {
+ box-shadow: 0 0 0 3px #FFE000;
+ }
+ }
+ #Ctnr_PFDDefaultPanel .Warning {
+ box-shadow: 0 0 0 3px #FF4040;
+ color: #FF8080;
+ }
+ @keyframes Anim_PFDDefaultPanelWarning {
+ 0%, 75%, 100% {
+ box-shadow: none;
+ }
+ 25%, 50% {
+ box-shadow: 0 0 0 3px #FF4040;
+ }
+ }
+ #Ctnr_PFDDefaultPanel .BankAngleWarning {
+ background-color: #FF4040;
+ stroke: #FF4040;
+ }
+ @keyframes Anim_PFDDefaultPanelBankAngleWarning {
+ 0%, 75%, 100% {
+ background-color: #FFFFFF;
+ stroke: #FFFFFF;
+ }
+ 25%, 50% {
+ background-color: #FF4040;
+ stroke: #FF4040;
+ }
+ }
+
+ /* Menu */
+ /* Sections */
+ .PFDMenuContent {
+ border-bottom: 1px solid #FFFFFF;
+ }
+
+ /* Area specific */
+ /* Ctrl */
+ /* Flaps */
+ .FlapsScale {
+ background-color: #FFFFFF;
+ }
+
+ /* Tech info */
+ #PFDTechInfoSpeedVectorGraph {
+ background-color: #404040;
+ }
diff --git a/PROJECT/audio/Beep.mp3 b/PROJECT/audio/Beep.mp3
new file mode 100644
index 0000000..e066d3a
Binary files /dev/null and b/PROJECT/audio/Beep.mp3 differ
diff --git a/PROJECT/cursors/BTRAhoge.cur b/PROJECT/cursors/BTRAhoge.cur
new file mode 100644
index 0000000..0a32b41
Binary files /dev/null and b/PROJECT/cursors/BTRAhoge.cur differ
diff --git a/PROJECT/cursors/Genshin.cur b/PROJECT/cursors/Genshin.cur
new file mode 100644
index 0000000..b75367d
Binary files /dev/null and b/PROJECT/cursors/Genshin.cur differ
diff --git a/PROJECT/cursors/GenshinFurina.cur b/PROJECT/cursors/GenshinFurina.cur
new file mode 100644
index 0000000..068a7ec
Binary files /dev/null and b/PROJECT/cursors/GenshinFurina.cur differ
diff --git a/PROJECT/cursors/GenshinNahida.cur b/PROJECT/cursors/GenshinNahida.cur
new file mode 100644
index 0000000..e751dc8
Binary files /dev/null and b/PROJECT/cursors/GenshinNahida.cur differ
diff --git a/PROJECT/images/Background.jpg b/PROJECT/images/Background.jpg
new file mode 100644
index 0000000..07b4bec
Binary files /dev/null and b/PROJECT/images/Background.jpg differ
diff --git a/PROJECT/scripts/common.js b/PROJECT/scripts/common.js
new file mode 100644
index 0000000..c915a8c
--- /dev/null
+++ b/PROJECT/scripts/common.js
@@ -0,0 +1,850 @@
+// For SamToki.github.io
+// Released under GNU GPL v3 open source license.
+// © 2023 SAM TOKI STUDIO
+
+// Reminders
+ // About abbreviations
+ // Do not abuse abbreviations. Use only when a word is longer than 8 letters.
+ // For example, abbreviate "Animation" into "Anim", but do not abbreviate "Language" into "Lang".
+ // Exceptions: "Ctrl", "Cmd", "Avg", etc.
+
+// Initialization
+ // Declare variables
+ "use strict";
+ // Unsaved
+ var Document = {
+ NavCtrls: document.getElementsByClassName("Nav"),
+ Sections: document.getElementsByTagName("section"),
+ ActiveSectionID: "",
+ PWAInstallation: null
+ },
+ Interaction = {
+ DoNotHide: [0],
+ Dialog: [0],
+ IsPointerDown: false, IsInIMEComposition: false, ScreenWakeLock: null
+ },
+ Automation = {
+ HighlightActiveSectionInNav: null,
+ FadeHotkeyIndicators: null, HideToast: null
+ };
+
+ // Saved
+ var System = {
+ Display: {
+ Theme: "Auto", Cursor: "Default",
+ BlurBgImage: true,
+ HotkeyIndicators: "ShowOnAnyKeyPress",
+ Anim: 250
+ },
+ Audio: {
+ PlayAudio: true
+ },
+ I18n: {
+ Language: "Auto"
+ },
+ DontShowAgain: [0],
+ Dev: {
+ TryToOptimizePerformance: false,
+ ShowDebugOutlines: false,
+ UseJapaneseGlyph: false,
+ Font: ""
+ },
+ Version: {}
+ };
+
+// Simplifications
+ // Read
+ // Element
+ function IsElementExisting(ID) {
+ return document.getElementById(ID) != null;
+ }
+
+ // Class
+ function IsClassContained(ID, Class) {
+ return document.getElementById(ID).classList.contains(Class);
+ }
+
+ // Text & value
+ function ReadText(ID) {
+ return document.getElementById(ID).innerHTML;
+ }
+ function ReadValue(ID) {
+ return document.getElementById(ID).value;
+ }
+ function ReadLanguage(ID) {
+ return document.getElementById(ID).lang;
+ }
+
+ // Position
+ function ReadTop(ID) {
+ return document.getElementById(ID).offsetTop;
+ }
+ function ReadLeft(ID) {
+ return document.getElementById(ID).offsetLeft;
+ }
+
+ // Size
+ function ReadWidth(ID) {
+ return document.getElementById(ID).offsetWidth;
+ }
+ function ReadHeight(ID) {
+ return document.getElementById(ID).offsetHeight;
+ }
+
+ // Layout
+ function IsMobileLayout() {
+ return window.innerWidth <= 880;
+ }
+ function IsFullscreen() {
+ return document.fullscreenElement != null;
+ }
+
+ // Functionality
+ function IsChecked(ID) {
+ return document.getElementById(ID).checked;
+ }
+ function IsImageLoaded(ID) {
+ return document.getElementById(ID).complete;
+ }
+ function IsAudioLoaded(ID) {
+ return document.getElementById(ID).readyState == 4;
+ }
+
+ // Write
+ // Element
+ function RemoveElement(ID) {
+ document.getElementById(ID).remove();
+ }
+
+ // Class
+ function AddClass(ID, Class) {
+ document.getElementById(ID).classList.add(Class);
+ }
+ function AddClassByClass(Class1, Class2) {
+ let Elements = document.getElementsByClassName(Class1);
+ for(let Looper = 0; Looper < Elements.length; Looper++) {
+ Elements[Looper].classList.add(Class2);
+ }
+ }
+ function RemoveClass(ID, Class) {
+ document.getElementById(ID).classList.remove(Class);
+ }
+ function RemoveClassByClass(Class1, Class2) {
+ let Elements = document.getElementsByClassName(Class1);
+ if(Class1 != Class2) {
+ for(let Looper = 0; Looper < Elements.length; Looper++) {
+ Elements[Looper].classList.remove(Class2);
+ }
+ } else {
+ // When removing a class from all elements, Class1 (class to be removed) and Class2 (searching elements with this class) are the same.
+ // The array "Elements" will change dynamically as it is based on the removed class, so here we use "while" instead of "for" to avoid omission.
+ while(Elements.length > 0) {
+ Elements[0].classList.remove(Class2);
+ }
+ }
+ }
+ function ChangeIndicatorLight(ID, Value) {
+ if(IsClassContained(ID, "IndicatorLight") == true) {
+ RemoveClass(ID, "Off");
+ RemoveClass(ID, "Red");
+ RemoveClass(ID, "Orange");
+ RemoveClass(ID, "Green");
+ RemoveClass(ID, "Blue");
+ AddClass(ID, Value);
+ } else {
+ AlertSystemError("Function ChangeIndicatorLight received an element \"" + ID + "\" without class IndicatorLight.");
+ }
+ }
+
+ // Text & value
+ function ChangeText(ID, Text) {
+ document.getElementById(ID).innerHTML = Text;
+ }
+ function AddText(ID, Text) {
+ document.getElementById(ID).innerHTML += Text;
+ }
+ function ChangeValue(ID, Value) {
+ document.getElementById(ID).value = Value;
+ }
+ function ChangePlaceholder(ID, Value) {
+ document.getElementById(ID).placeholder = Value;
+ }
+ function ChangeTooltip(ID, Value) {
+ document.getElementById(ID).title = Value;
+ }
+ function ChangeAriaLabel(ID, Value) {
+ document.getElementById(ID).ariaLabel = Value;
+ }
+ function ChangeLanguage(ID, Value) {
+ document.getElementById(ID).lang = Value;
+ }
+ function ChangeMin(ID, Value) {
+ document.getElementById(ID).min = Value;
+ }
+ function ChangeMax(ID, Value) {
+ document.getElementById(ID).max = Value;
+ }
+ function ChangeStep(ID, Value) {
+ document.getElementById(ID).step = Value;
+ }
+
+ // Position
+ function ChangeTop(ID, Value) {
+ document.getElementById(ID).style.top = Value;
+ }
+ function ChangeLeft(ID, Value) {
+ document.getElementById(ID).style.left = Value;
+ }
+ function ChangeBottom(ID, Value) {
+ document.getElementById(ID).style.bottom = Value;
+ }
+ function ChangeRight(ID, Value) {
+ document.getElementById(ID).style.right = Value;
+ }
+ function ChangeScale(ID, Value) {
+ if(Value != "") {
+ document.getElementById(ID).style.transform = "scale(" + Value + ")";
+ } else {
+ document.getElementById(ID).style.transform = "";
+ }
+ }
+ function ChangeRotate(ID, Value) {
+ if(Value != "") {
+ document.getElementById(ID).style.transform = "rotate(" + Value + "deg)";
+ } else {
+ document.getElementById(ID).style.transform = "";
+ }
+ }
+
+ // Size
+ function ChangeWidth(ID, Value) {
+ document.getElementById(ID).style.width = Value;
+ }
+ function ChangeHeight(ID, Value) {
+ document.getElementById(ID).style.height = Value;
+ }
+
+ // Background
+ function ChangeBgImage(Value) {
+ if(Value != "") {
+ document.getElementById("BgImage").style.backgroundImage = "url(" + Value + ")";
+ } else {
+ document.getElementById("BgImage").style.backgroundImage = "";
+ }
+ }
+ function ChangeImage(ID, Value) {
+ document.getElementById(ID).src = Value;
+ }
+ function ChangeFilter(ID, Value) {
+ document.getElementById(ID).style.filter = Value;
+ }
+
+ // Foreground
+ function ChangeFont(ID, Value) {
+ document.getElementById(ID).style.fontFamily = Value;
+ }
+ function ChangeProgbar(ID, HorizontalOrVertical, Percentage) {
+ switch(HorizontalOrVertical) {
+ case "Horizontal":
+ ChangeWidth(ID, "calc(10px + (100% - 10px) * " + (Percentage / 100) + ")");
+ break;
+ case "Vertical":
+ ChangeHeight(ID, "calc(10px + (100% - 10px) * " + (Percentage / 100) + ")");
+ break;
+ default:
+ AlertSystemError("The value of HorizontalOrVertical \"" + HorizontalOrVertical + "\" in function ChangeProgbar is invalid.");
+ break;
+ }
+ }
+ function ChangeShapedProgbar(ID, HorizontalOrVertical, Percentage) {
+ switch(HorizontalOrVertical) {
+ case "Horizontal":
+ document.getElementById(ID).style.clipPath = "inset(0 " + (100 - Percentage) + "% 0 0)";
+ break;
+ case "Vertical":
+ document.getElementById(ID).style.clipPath = "inset(" + (100 - Percentage) + "% 0 0 0)";
+ break;
+ default:
+ AlertSystemError("The value of HorizontalOrVertical \"" + HorizontalOrVertical + "\" in function ChangeShapedProgbar is invalid.");
+ break;
+ }
+ }
+ function ChangeProgring(ID, Diameter, Percentage) {
+ document.getElementById(ID).style.strokeDasharray = (Math.PI * (Diameter - 5)) * (Percentage / 100) + "px, " + (Math.PI * (Diameter - 5)) * (1 - Percentage / 100) + "px";
+ }
+
+ // Layout
+ function Show(ID) {
+ RemoveClass(ID, "Hidden");
+ RemoveClass(ID, "HiddenHorizontally");
+ RemoveClass(ID, "HiddenToCorner");
+ RemoveClass(ID, "HiddenInMobileLayout");
+ RemoveClass(ID, "Faded");
+ ChangeInert(ID, false);
+ Interaction.DoNotHide[Interaction.DoNotHide.length] = ID;
+ setTimeout(function() {
+ Interaction.DoNotHide.splice(1, 1);
+ }, 20);
+ }
+ function ShowWithoutProtection(ID) {
+ RemoveClass(ID, "Hidden");
+ RemoveClass(ID, "HiddenHorizontally");
+ RemoveClass(ID, "HiddenToCorner");
+ RemoveClass(ID, "HiddenInMobileLayout");
+ RemoveClass(ID, "Faded");
+ ChangeInert(ID, false);
+ }
+ function ShowByClass(Class) {
+ let Elements = document.getElementsByClassName(Class);
+ for(let Looper = 0; Looper < Elements.length; Looper++) {
+ Elements[Looper].classList.remove("Hidden");
+ Elements[Looper].classList.remove("HiddenHorizontally");
+ Elements[Looper].classList.remove("HiddenToCorner");
+ Elements[Looper].classList.remove("HiddenInMobileLayout");
+ Elements[Looper].classList.remove("Faded");
+ Elements[Looper].inert = false;
+ }
+ Interaction.DoNotHide[Interaction.DoNotHide.length] = Class;
+ setTimeout(function() {
+ Interaction.DoNotHide.splice(1, 1);
+ }, 20);
+ }
+ function Hide(ID) {
+ if(Interaction.DoNotHide.includes(ID) == false) {
+ AddClass(ID, "Hidden");
+ ChangeInert(ID, true);
+ }
+ }
+ function HideByClass(Class) {
+ if(Interaction.DoNotHide.includes(Class) == false) {
+ let Elements = document.getElementsByClassName(Class);
+ for(let Looper = 0; Looper < Elements.length; Looper++) {
+ if(Interaction.DoNotHide.includes(Elements[Looper].id) == false) {
+ Elements[Looper].classList.add("Hidden");
+ Elements[Looper].inert = true;
+ }
+ }
+ }
+ }
+ function HideHorizontally(ID) {
+ if(Interaction.DoNotHide.includes(ID) == false) {
+ AddClass(ID, "HiddenHorizontally");
+ ChangeInert(ID, true);
+ }
+ }
+ function HideToCorner(ID) {
+ if(Interaction.DoNotHide.includes(ID) == false) {
+ AddClass(ID, "HiddenToCorner");
+ ChangeInert(ID, true);
+ }
+ }
+ function Fade(ID) {
+ if(Interaction.DoNotHide.includes(ID) == false) {
+ AddClass(ID, "Faded");
+ ChangeInert(ID, true);
+ }
+ }
+ function FadeByClass(Class) {
+ if(Interaction.DoNotHide.includes(Class) == false) {
+ let Elements = document.getElementsByClassName(Class);
+ for(let Looper = 0; Looper < Elements.length; Looper++) {
+ if(Interaction.DoNotHide.includes(Elements[Looper].id) == false) {
+ Elements[Looper].classList.add("Faded");
+ Elements[Looper].inert = true;
+ }
+ }
+ }
+ }
+ function ScrollIntoView(ID) {
+ document.getElementById(ID).scrollIntoView();
+ }
+ function ScrollToBottom(ID) {
+ document.getElementById(ID).scrollTop = document.getElementById(ID).scrollHeight;
+ }
+ function ToggleFullscreen() {
+ if(IsFullscreen() == false) {
+ document.body.requestFullscreen();
+ } else {
+ document.exitFullscreen();
+ }
+ }
+
+ // Animation
+ function ChangeAnim(ID, Value) {
+ document.getElementById(ID).style.transition = Value;
+ }
+ function ChangeAnimOverall(Value) {
+ if(Value > 0) {
+ document.getElementById("Html").style.transition = Value + "ms, z-index 0ms";
+ let Elements = document.getElementsByTagName("*");
+ for(let Looper = 0; Looper < Elements.length; Looper++) {
+ Elements[Looper].style.animation = "";
+ }
+ document.getElementById("Html").style.scrollBehavior = "";
+ } else {
+ document.getElementById("Html").style.transition = "none";
+ let Elements = document.getElementsByTagName("*");
+ for(let Looper = 0; Looper < Elements.length; Looper++) {
+ Elements[Looper].style.animation = "none";
+ }
+ document.getElementById("Html").style.scrollBehavior = "auto";
+ }
+ }
+
+ // Functionality
+ function ChangeLink(ID, Value) {
+ document.getElementById(ID).href = Value;
+ }
+ function ChangeMediaCondition(ID, Value) {
+ document.getElementById(ID).media = Value;
+ }
+ function ChangeChecked(ID, Value) {
+ document.getElementById(ID).checked = Value;
+ }
+ function ChangeDisabled(ID, Value) {
+ document.getElementById(ID).disabled = Value;
+ }
+ function ChangeInert(ID, Value) {
+ document.getElementById(ID).inert = Value;
+ }
+ function ChangeCursor(ID, Value) {
+ document.getElementById(ID).style.cursor = Value;
+ }
+ function ChangeCursorOverall(Value) {
+ let Elements = document.getElementsByTagName("*");
+ for(let Looper = 0; Looper < Elements.length; Looper++) {
+ Elements[Looper].style.cursor = Value;
+ }
+ }
+
+ // Audio
+ function LoadAudio(ID, Value) { // Value example: "audio/Beep.mp3"
+ StopAudio(ID);
+ if(System.Audio.PlayAudio == true && document.getElementById(ID).volume > 0 && Value != "") {
+ ChangeText(ID, "");
+ document.getElementById(ID).load();
+ }
+ }
+ function PlayAudio(ID, Value) {
+ StopAudio(ID);
+ if(System.Audio.PlayAudio == true && document.getElementById(ID).volume > 0) {
+ ChangeText(ID, "");
+ document.getElementById(ID).load();
+ document.getElementById(ID).currentTime = 0;
+ document.getElementById(ID).play();
+ }
+ }
+ function StopAudio(ID) {
+ document.getElementById(ID).pause();
+ }
+ function StopAllAudio() {
+ let Elements = document.getElementsByClassName("Audio");
+ for(let Looper = 0; Looper < Elements.length; Looper++) {
+ Elements[Looper].pause();
+ }
+ }
+ function ChangeVolume(ID, Percentage) {
+ document.getElementById(ID).volume = Percentage / 100;
+ }
+ function ChangeAudioLoop(ID, Value) {
+ document.getElementById(ID).loop = Value;
+ }
+
+ // Interact
+ function Click(ID) {
+ if(document.getElementById(ID).closest("[inert]") == null) {
+ document.getElementById(ID).click();
+ }
+ }
+ function Focus(ID) {
+ if(document.getElementById(ID).closest("[inert]") == null) {
+ document.getElementById(ID).focus();
+ }
+ }
+ function SelectText(ID) {
+ if(document.getElementById(ID).closest("[inert]") == null) {
+ document.getElementById(ID).select();
+ }
+ }
+
+// Cmd
+ // General
+ function ShowIAmHere(ID) {
+ if(System.Display.Anim > 0) {
+ setTimeout(function() {
+ ChangeAnim(ID, "250ms");
+ AddClass(ID, "IAmHere");
+ }, 500);
+ setTimeout(function() {
+ RemoveClass(ID, "IAmHere");
+ }, 770);
+ setTimeout(function() {
+ AddClass(ID, "IAmHere");
+ }, 1040);
+ setTimeout(function() {
+ RemoveClass(ID, "IAmHere");
+ }, 1310);
+ setTimeout(function() {
+ ChangeAnim(ID, "");
+ }, 1580);
+ } else {
+ setTimeout(function() {
+ AddClass(ID, "IAmHere");
+ }, 250);
+ setTimeout(function() {
+ RemoveClass(ID, "IAmHere");
+ }, 500);
+ setTimeout(function() {
+ AddClass(ID, "IAmHere");
+ }, 750);
+ setTimeout(function() {
+ RemoveClass(ID, "IAmHere");
+ }, 1000);
+ }
+ }
+
+ // Settings
+ // Display
+ function SetTheme() {
+ System.Display.Theme = ReadValue("Combobox_SettingsTheme");
+ RefreshSystem();
+ }
+ function SetCursor() {
+ System.Display.Cursor = ReadValue("Combobox_SettingsCursor");
+ RefreshSystem();
+ }
+ function SetBlurBgImage() {
+ System.Display.BlurBgImage = IsChecked("Checkbox_SettingsBlurBgImage");
+ RefreshSystem();
+ }
+ function SetHotkeyIndicators() {
+ System.Display.HotkeyIndicators = ReadValue("Combobox_SettingsHotkeyIndicators");
+ RefreshSystem();
+ }
+ function SetAnim() {
+ System.Display.Anim = Number(ReadValue("Combobox_SettingsAnim"));
+ RefreshSystem();
+ }
+
+ // Audio
+ function SetPlayAudio() {
+ System.Audio.PlayAudio = IsChecked("Checkbox_SettingsPlayAudio");
+ RefreshSystem();
+ }
+
+ // I18n
+ function SetLanguage() {
+ System.I18n.Language = ReadValue("Combobox_SettingsLanguage");
+ RefreshSystem();
+ }
+
+ // PWA
+ function InstallPWA() {
+ if(Document.PWAInstallation != null) {
+ Document.PWAInstallation.prompt();
+ }
+ }
+
+ // Dev
+ function SetTryToOptimizePerformance() {
+ System.Dev.TryToOptimizePerformance = IsChecked("Checkbox_SettingsTryToOptimizePerformance");
+ RefreshSystem();
+ }
+ function SetShowDebugOutlines() {
+ System.Dev.ShowDebugOutlines = IsChecked("Checkbox_SettingsShowDebugOutlines");
+ RefreshSystem();
+ }
+ function SetUseJapaneseGlyph() {
+ System.Dev.UseJapaneseGlyph = IsChecked("Checkbox_SettingsUseJapaneseGlyph");
+ RefreshSystem();
+ }
+ function SetFont() {
+ System.Dev.Font = ReadValue("Textbox_SettingsFont");
+ RefreshSystem();
+ }
+
+// Listeners
+ // On scroll
+ document.addEventListener("scroll", HighlightActiveSectionInNav);
+
+ // On click (mouse left button, Enter key or Space key)
+ document.addEventListener("click", function() {
+ setTimeout(HideDropctrlGroups, 0);
+ Interaction.IsPointerDown = false;
+ });
+
+ // On mouse button
+ document.addEventListener("pointerdown", function() {
+ FadeHotkeyIndicators();
+ Interaction.IsPointerDown = true;
+ });
+ document.addEventListener("pointerup", function() {
+ Interaction.IsPointerDown = false;
+ });
+
+ // On Esc key
+ document.addEventListener("keydown", function(Hotkey) {
+ if(Hotkey.key == "Escape") {
+ HideDropctrlGroups();
+ if(Interaction.Dialog.length > 1) {
+ setTimeout(function() { // Set a delay for the Esc key event listener in script.js to respond first, so it knows whether a dialog is present, before that dialog gets dismissed.
+ AnswerDialog(3);
+ }, 0);
+ }
+ }
+ });
+
+ // On IME composition
+ document.addEventListener("compositionstart", function() {
+ Interaction.IsInIMEComposition = true;
+ })
+ document.addEventListener("compositionend", function() {
+ Interaction.IsInIMEComposition = false;
+ })
+
+ // On resizing window
+ window.addEventListener("resize", function() {
+ if(IsMobileLayout() == false) {
+ HideHorizontally("Button_Nav");
+ ChangeInert("DropctrlGroup_Nav", false);
+ } else {
+ Show("Button_Nav");
+ ChangeInert("DropctrlGroup_Nav", true);
+ }
+ HideDropctrlGroups();
+ });
+
+ // On toggling fullscreen
+ document.addEventListener("fullscreenchange", function() { // Here an anonymous function must be used. Because the function "RefreshSystem" is defined in the "script.js" file, which is after this "common.js" file. Writing "RefreshSystem" straightforwardly will cause an error.
+ RefreshSystem();
+ });
+
+ // Screen wake lock (https://developer.chrome.com/docs/capabilities/web-apis/wake-lock)
+ document.addEventListener("visibilitychange", async() => {
+ if(Interaction.ScreenWakeLock != null && document.visibilityState == "visible") {
+ await RequestScreenWakeLock();
+ }
+ });
+
+ // When PWA installation is available
+ window.addEventListener("beforeinstallprompt", function(Event) { // This does not seem to work.
+ Document.PWAInstallation = Event;
+ ChangeDisabled("Button_SettingsInstallPWA", false);
+ });
+
+// Automations
+Automation.HighlightActiveSectionInNav = setInterval(HighlightActiveSectionInNav, 500);
+
+// Features
+ // Maths
+ function Randomize(Min, Max) { // Return an integer between two integers.
+ return Min + Math.trunc(Math.random() * (Max - Min + 1));
+ }
+
+ // Highlight active section in nav
+ function HighlightActiveSectionInNav() {
+ for(let Looper = 0; Looper < Document.Sections.length; Looper++) {
+ if(scrollY >= Document.Sections[Looper].offsetTop - 200) {
+ Document.ActiveSectionID = Document.Sections[Looper].getAttribute("id");
+ }
+ }
+ for(let Looper = 0; Looper < Document.NavCtrls.length; Looper++) {
+ if(Document.NavCtrls[Looper].getAttribute("id") == "Nav_" + Document.ActiveSectionID) {
+ if(IsMobileLayout() == false) {
+ ChangeTop("Ctrl_NavUnderline", "calc(100% - 2px)");
+ ChangeLeft("Ctrl_NavUnderline", Document.NavCtrls[Looper].offsetLeft + 4 + "px");
+ ChangeWidth("Ctrl_NavUnderline", Document.NavCtrls[Looper].offsetWidth - 8 + "px");
+ ChangeHeight("Ctrl_NavUnderline", "2px");
+ } else {
+ ChangeTop("Ctrl_NavUnderline", Document.NavCtrls[Looper].offsetTop + 4 + "px");
+ ChangeLeft("Ctrl_NavUnderline", "0");
+ ChangeWidth("Ctrl_NavUnderline", "2px");
+ ChangeHeight("Ctrl_NavUnderline", Document.NavCtrls[Looper].offsetHeight - 8 + "px");
+ }
+ }
+ }
+ }
+
+ // Hide DropctrlGroups
+ function HideDropctrlGroups() {
+ let Elements = document.getElementsByClassName("DropctrlGroup");
+ for(let Looper = 0; Looper < Elements.length; Looper++) {
+ if(Interaction.DoNotHide.includes(Elements[Looper].id) == false) {
+ if(Elements[Looper].id != "DropctrlGroup_Nav") {
+ Elements[Looper].classList.add("HiddenToCorner");
+ Elements[Looper].inert = true;
+ } else {
+ Elements[Looper].classList.add("HiddenInMobileLayout");
+ if(IsMobileLayout() == true) {
+ Elements[Looper].inert = true;
+ }
+ }
+ }
+ }
+ }
+
+ // Hotkey indicators
+ function ShowHotkeyIndicators() {
+ switch(System.Display.HotkeyIndicators) {
+ case "ShowOnWrongKeyPress":
+ case "ShowOnAnyKeyPress":
+ ShowByClass("HotkeyIndicator");
+ clearTimeout(Automation.FadeHotkeyIndicators);
+ Automation.FadeHotkeyIndicators = setTimeout(FadeHotkeyIndicators, System.Display.Anim + 15000);
+ break;
+ case "AlwaysShow":
+ ShowByClass("HotkeyIndicator");
+ clearTimeout(Automation.FadeHotkeyIndicators);
+ break;
+ default:
+ AlertSystemError("The value of System.Display.HotkeyIndicators \"" + System.Display.HotkeyIndicators + "\" in function ShowHotkeyIndicators is invalid.");
+ break;
+ }
+ }
+ function FadeHotkeyIndicators() {
+ if(System.Display.HotkeyIndicators != "AlwaysShow") {
+ FadeByClass("HotkeyIndicator");
+ }
+ clearTimeout(Automation.FadeHotkeyIndicators);
+ }
+ function IsWrongKeyNegligible(HotkeyName) {
+ switch(HotkeyName) {
+ case "Control":
+ case "Shift":
+ case "Alt":
+ case "Meta": // Windows key, Linux Super key, macOS Command key.
+ case "ContextMenu":
+ case "Escape":
+ case "Tab":
+ case "Enter":
+ case " ":
+ case "ArrowUp":
+ case "ArrowDown":
+ case "ArrowLeft":
+ case "ArrowRight":
+ case "Home":
+ case "End":
+ case "PageUp":
+ case "PageDown":
+ case "NumLock":
+ case "CapsLock":
+ case "ScrollLock":
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ // Toast
+ function ShowToast(Text) {
+ ChangeText("Label_Toast", Text);
+ ShowWithoutProtection("Toast");
+ clearTimeout(Automation.HideToast);
+ Automation.HideToast = setTimeout(HideToast, System.Display.Anim + 1000);
+ }
+ function HideToast() {
+ Hide("Toast");
+ clearTimeout(Automation.HideToast);
+ }
+
+ // Dialog
+ function ShowDialog(Event, Icon, Text, CheckboxOption, Option1, Option2, Option3) {
+ // Dialog status
+ if(Event != "Previous") {
+ let IsDialogEventAlreadyExisting = false;
+ for(let Looper = 1; Looper < Interaction.Dialog.length; Looper++) {
+ if(Interaction.Dialog[Looper].Event == Event) {
+ IsDialogEventAlreadyExisting = true;
+ }
+ }
+ if(IsDialogEventAlreadyExisting == false) {
+ Interaction.Dialog[Interaction.Dialog.length] = {
+ Event: Event,
+ Icon: Icon,
+ Text: Text,
+ CheckboxOption: CheckboxOption, Option1: Option1, Option2: Option2, Option3: Option3
+ }
+ }
+ } else {
+ if(Interaction.Dialog.length > 1) {
+ Interaction.Dialog.splice(Interaction.Dialog.length - 1, 1);
+ }
+ if(Interaction.Dialog.length <= 1) {
+ Fade("ScreenFilter_Dialog");
+ Hide("Window_Dialog");
+ ChangeInert("Topbar", false);
+ ChangeInert("Main", false);
+ return;
+ }
+ }
+
+ // Icon and text
+ HideHorizontally("Ctrl_DialogIconInfo");
+ HideHorizontally("Ctrl_DialogIconQuestion");
+ HideHorizontally("Ctrl_DialogIconCaution");
+ HideHorizontally("Ctrl_DialogIconError");
+ switch(Interaction.Dialog[Interaction.Dialog.length - 1].Icon) {
+ case "Info":
+ Show("Ctrl_DialogIconInfo");
+ break;
+ case "Question":
+ Show("Ctrl_DialogIconQuestion");
+ break;
+ case "Caution":
+ Show("Ctrl_DialogIconCaution");
+ break;
+ case "Error":
+ Show("Ctrl_DialogIconError");
+ break;
+ default:
+ AlertSystemError("The value of Interaction.Dialog[Interaction.Dialog.length - 1].Icon \"" + Interaction.Dialog[Interaction.Dialog.length - 1].Icon + "\" in function ShowDialog is invalid.");
+ break;
+ }
+ ChangeText("Label_DialogText", Interaction.Dialog[Interaction.Dialog.length - 1].Text);
+
+ // Options
+ if(Interaction.Dialog[Interaction.Dialog.length - 1].CheckboxOption != "") {
+ Show("Ctrl_DialogCheckboxOption");
+ ChangeChecked("Checkbox_DialogCheckboxOption", false);
+ ChangeText("Label_DialogCheckboxOption", Interaction.Dialog[Interaction.Dialog.length - 1].CheckboxOption);
+ } else {
+ HideHorizontally("Ctrl_DialogCheckboxOption");
+ }
+ if(Interaction.Dialog[Interaction.Dialog.length - 1].Option1 != "") {
+ Show("Ctrl_DialogOption1");
+ ChangeText("Button_DialogOption1", Interaction.Dialog[Interaction.Dialog.length - 1].Option1);
+ } else {
+ HideHorizontally("Ctrl_DialogOption1");
+ }
+ if(Interaction.Dialog[Interaction.Dialog.length - 1].Option2 != "") {
+ Show("Ctrl_DialogOption2");
+ ChangeText("Button_DialogOption2", Interaction.Dialog[Interaction.Dialog.length - 1].Option2);
+ } else {
+ HideHorizontally("Ctrl_DialogOption2");
+ }
+ ChangeText("Button_DialogOption3", Interaction.Dialog[Interaction.Dialog.length - 1].Option3); // Option 3 is the default option, will be selected when pressing Esc key. Therefore: When there is a single "OK", put it here. When there are multiple options, put "Cancel" here.
+
+ // Show dialog and disable other ctrls
+ Show("ScreenFilter_Dialog");
+ Show("Window_Dialog");
+ ChangeInert("Topbar", true);
+ ChangeInert("Main", true);
+ }
+
+ // Screen wake lock
+ const RequestScreenWakeLock = async() => {
+ if(Interaction.ScreenWakeLock == null) {
+ Interaction.ScreenWakeLock = await navigator.wakeLock.request();
+ }
+ }
+ function ReleaseScreenWakeLock() {
+ if(Interaction.ScreenWakeLock != null) {
+ Interaction.ScreenWakeLock.release();
+ Interaction.ScreenWakeLock = null;
+ }
+ }
+
+// Error handling
+window.addEventListener("error", function(ErrorEvent) {
+ AlertSystemError(ErrorEvent.message);
+});
diff --git a/PROJECT/scripts/common_UserDataRepairer.js b/PROJECT/scripts/common_UserDataRepairer.js
new file mode 100644
index 0000000..4a97a28
--- /dev/null
+++ b/PROJECT/scripts/common_UserDataRepairer.js
@@ -0,0 +1,130 @@
+// For SamToki.github.io
+// Released under GNU GPL v3 open source license.
+// © 2023 SAM TOKI STUDIO
+
+// Initialization
+ // Declare variables
+ "use strict";
+
+ // Repair user data: Solves incompatibility after major version updates. A repairer may get removed if older than 12 months.
+ window.onload = RepairUserData();
+ function RepairUserData() {
+ // Timer+Lottery
+ // v3.00 (2024/11/07)
+ // Rename variable (Ringtone volume)
+ if(localStorage.TimerPlusLottery_Subsystem != undefined) {
+ let Subsystem = JSON.parse(localStorage.getItem("TimerPlusLottery_Subsystem"));
+ if(Subsystem.Audio.RingtoneVolume == undefined) {
+ Subsystem.Audio.RingtoneVolume = Subsystem.Audio.SoundVolume;
+ delete Subsystem.Audio.SoundVolume;
+ localStorage.setItem("TimerPlusLottery_Subsystem", JSON.stringify(Subsystem));
+ console.info("● User Data Repairer\n" +
+ "Repaired user data \"TimerPlusLottery Subsystem Audio RingtoneVolume\".");
+ }
+ }
+
+ // KanaMaster
+ // v2.00 (2024/10/04)
+ // New feature (Audio)
+ if(localStorage.KanaMaster_Subsystem != undefined) {
+ let Subsystem = JSON.parse(localStorage.getItem("KanaMaster_Subsystem"));
+ if(Subsystem.Audio == undefined) {
+ Subsystem.Audio = {
+ VoiceVolume: 0,
+ AlsoPlayVoiceOnMiss: false
+ };
+ localStorage.setItem("KanaMaster_Subsystem", JSON.stringify(Subsystem));
+ console.info("● User Data Repairer\n" +
+ "Repaired user data \"KanaMaster Subsystem Audio\".");
+ }
+ }
+
+ // v3.00 (2024/10/13)
+ // Rename variable (Final time limit)
+ if(localStorage.KanaMaster_Game != undefined) {
+ let Game = JSON.parse(localStorage.getItem("KanaMaster_Game"));
+ if(Game.Difficulty.TimeLimit.Final == undefined) {
+ Game.Difficulty.TimeLimit.Final = Game.Difficulty.TimeLimit.Normal;
+ delete Game.Difficulty.TimeLimit.Normal;
+ localStorage.setItem("KanaMaster_Game", JSON.stringify(Game));
+ console.info("● User Data Repairer\n" +
+ "Repaired user data \"KanaMaster Game Difficulty TimeLimit Final\".");
+ }
+ }
+
+ // v4.00 (2025/01/08)
+ // Optimize user data structure
+ if(localStorage.KanaMaster_Highscore != undefined) {
+ let Highscore = JSON.parse(localStorage.getItem("KanaMaster_Highscore"));
+ if(Highscore[1].Sequence == undefined) {
+ let NewObject = [
+ 0,
+ {Sequence: Highscore[1][1], Date: Highscore[1][2], Score: Highscore[1][3], MaxCombo: Highscore[1][4], Accuracy: Highscore[1][5], AvgReactionTime: Highscore[1][6]},
+ {Sequence: Highscore[2][1], Date: Highscore[2][2], Score: Highscore[2][3], MaxCombo: Highscore[2][4], Accuracy: Highscore[2][5], AvgReactionTime: Highscore[2][6]},
+ {Sequence: Highscore[3][1], Date: Highscore[3][2], Score: Highscore[3][3], MaxCombo: Highscore[3][4], Accuracy: Highscore[3][5], AvgReactionTime: Highscore[3][6]},
+ {Sequence: Highscore[4][1], Date: Highscore[4][2], Score: Highscore[4][3], MaxCombo: Highscore[4][4], Accuracy: Highscore[4][5], AvgReactionTime: Highscore[4][6]},
+ {Sequence: Highscore[5][1], Date: Highscore[5][2], Score: Highscore[5][3], MaxCombo: Highscore[5][4], Accuracy: Highscore[5][5], AvgReactionTime: Highscore[5][6]},
+ {Sequence: Highscore[6][1], Date: Highscore[6][2], Score: Highscore[6][3], MaxCombo: Highscore[6][4], Accuracy: Highscore[6][5], AvgReactionTime: Highscore[6][6]}
+ ];
+ localStorage.setItem("KanaMaster_Highscore", JSON.stringify(NewObject));
+ console.info("● User Data Repairer\n" +
+ "Repaired user data \"KanaMaster Highscore\".");
+ }
+ }
+
+ // Yamanobo-Ryou
+ // v2.00 (2025/01/08)
+ // Optimize user data structure
+ if(localStorage.YamanoboRyou_Game != undefined) {
+ let Game = JSON.parse(localStorage.getItem("YamanoboRyou_Game"));
+ if(Game.Terrain.Data[0].C == undefined) {
+ let NewObject = [
+ {C: "", A: 0}
+ ];
+ for(let Looper = 1; Looper < Game.Terrain.Data.length; Looper++) {
+ NewObject[Looper] = {
+ C: Game.Terrain.Data[Looper][1],
+ A: Game.Terrain.Data[Looper][2]
+ }
+ }
+ Game.Terrain.Data = structuredClone(NewObject);
+ localStorage.setItem("YamanoboRyou_Game", JSON.stringify(Game));
+ console.info("● User Data Repairer\n" +
+ "Repaired user data \"YamanoboRyou Game Terrain Data\".");
+ }
+ }
+ if(localStorage.YamanoboRyou_Highscore != undefined) {
+ let Highscore = JSON.parse(localStorage.getItem("YamanoboRyou_Highscore"));
+ if(Highscore[1].Sequence == undefined) {
+ let NewObject = [
+ 0,
+ {Sequence: Highscore[1][1], Date: Highscore[1][2], Score: Highscore[1][3], AvgSpeed: Highscore[1][4], AvgKeystrokeSpeed: Highscore[1][5], Accuracy: Highscore[1][6]},
+ {Sequence: Highscore[2][1], Date: Highscore[2][2], Score: Highscore[2][3], AvgSpeed: Highscore[2][4], AvgKeystrokeSpeed: Highscore[2][5], Accuracy: Highscore[2][6]},
+ {Sequence: Highscore[3][1], Date: Highscore[3][2], Score: Highscore[3][3], AvgSpeed: Highscore[3][4], AvgKeystrokeSpeed: Highscore[3][5], Accuracy: Highscore[3][6]},
+ {Sequence: Highscore[4][1], Date: Highscore[4][2], Score: Highscore[4][3], AvgSpeed: Highscore[4][4], AvgKeystrokeSpeed: Highscore[4][5], Accuracy: Highscore[4][6]},
+ {Sequence: Highscore[5][1], Date: Highscore[5][2], Score: Highscore[5][3], AvgSpeed: Highscore[5][4], AvgKeystrokeSpeed: Highscore[5][5], Accuracy: Highscore[5][6]},
+ {Sequence: Highscore[6][1], Date: Highscore[6][2], Score: Highscore[6][3], AvgSpeed: Highscore[6][4], AvgKeystrokeSpeed: Highscore[6][5], Accuracy: Highscore[6][6]}
+ ];
+ localStorage.setItem("YamanoboRyou_Highscore", JSON.stringify(NewObject));
+ console.info("● User Data Repairer\n" +
+ "Repaired user data \"YamanoboRyou Highscore\".");
+ }
+ }
+
+ // v2.02 (2025/01/09)
+ // Optimize user data structure
+ if(localStorage.YamanoboRyou_Game != undefined) {
+ let Game = JSON.parse(localStorage.getItem("YamanoboRyou_Game"));
+ if(Game.Stats.Keystroke == undefined) {
+ Game.Stats.Keystroke = {
+ Count: Game.Stats.KeystrokeCount,
+ Timestamp: Game.Stats.KeystrokeTimestamp
+ };
+ delete Game.Stats.KeystrokeCount;
+ delete Game.Stats.KeystrokeTimestamp;
+ localStorage.setItem("YamanoboRyou_Game", JSON.stringify(Game));
+ console.info("● User Data Repairer\n" +
+ "Repaired user data \"YamanoboRyou Game Stats Keystroke\".");
+ }
+ }
+ }
diff --git a/PROJECT/styles/common.css b/PROJECT/styles/common.css
new file mode 100644
index 0000000..e09c217
--- /dev/null
+++ b/PROJECT/styles/common.css
@@ -0,0 +1,853 @@
+/* For SamToki.github.io */
+/* Released under GNU GPL v3 open source license. */
+/* © 2023 SAM TOKI STUDIO */
+
+/* Reminders */
+ /* About abbreviations */
+ /* Do not abuse abbreviations. Use only when a word is longer than 8 letters. */
+ /* For example, abbreviate "Animation" into "Anim", but do not abbreviate "Language" into "Lang". */
+ /* Exceptions: "Ctrl", "Avg", etc. */
+ /* About simplicity */
+ /* Make rule names as simple as possible. Avoid over-specific names like "#1 #2 .3" unless needed. */
+ /* Avoid using ":is" as it is a little confusing. */
+ /* Do not abuse "!important". */
+ /* About stacking context */
+ /* "z-index" is a relative value. Avoid applying "z-index" when a parent element already has "z-index". */
+
+/* General */
+ /* Basics */
+ /* Overall */
+ * {
+ transform-origin: inherit;
+ box-sizing: inherit; padding: 0; margin: 0;
+ font-family: inherit; font-size: inherit; font-style: inherit; /* These 3 properties need to be set as inherit due to buttons. */
+ transition: inherit; scroll-behavior: inherit;
+ }
+ #Html {
+ transform-origin: center center;
+ box-sizing: border-box;
+ color: #000000; font-family: ""; font-size: 14px; /* font-family: Sarasa UI J, Source Han Sans, Hiragino Sans GB, PingFang SC, Microsoft YaHei UI, Helvetica, Arial, sans; */
+ scroll-padding-top: 60px;
+ transition: 250ms, z-index 0ms; scroll-behavior: smooth;
+ }
+ #Html:has(#Topbar.Hidden) {
+ scroll-padding-top: 0;
+ }
+ #BgImage {
+ position: fixed; top: 0; left: 0; z-index: -1;
+ width: 100%; height: 100vh;
+ background-color: #808080; background-image: url(../images/Background.jpg); background-attachment: fixed; background-position: top center; background-size: cover; background-repeat: no-repeat;
+ }
+ #BgImage.Blur {
+ filter: blur(10px);
+ }
+
+ /* Containers */
+ .NonInteractiveCtnr {
+ display: none;
+ }
+
+ /* Sections */
+ section {
+ padding: 0 15px;
+ overflow: hidden;
+ }
+ section:has(.Viewport) {
+ height: calc(100vh - 60px);
+ }
+ #Html:has(#Topbar.Hidden) section:has(.Viewport) {
+ height: 100vh;
+ }
+ .Viewport {
+ position: relative;
+ width: 100%; height: calc(100% - 30px); margin: 15px 0;
+ }
+
+ /* Group frameworks */
+ ul.ItemGroup, ul.CtrlGroup, ul.DropctrlGroup {
+ list-style-type: none;
+ }
+ .ItemGroup {
+ margin: 0 -15px;
+ display: flex; flex-wrap: wrap; justify-content: center;
+ }
+ .Item {
+ width: 410px; border-radius: 5px; padding: 15px; margin: 0 15px 30px 15px;
+ background-color: #F0F0F0E0;
+ }
+ .Prolog {
+ width: unset; max-width: 850px;
+ text-align: center;
+ }
+ .CtrlGroup {
+ display: flex; flex-wrap: wrap; justify-content: space-between;
+ }
+ .CtrlGroup.BelowParagraph {
+ margin: 10px 0 0 0;
+ }
+ .Ctrl {
+ position: relative;
+ height: 35px;
+ }
+ fieldset {
+ width: 100%; border: none; padding: 15px 0 0 0;
+ }
+ legend {
+ font-size: 1.50em; font-weight: bold;
+ }
+ fieldset > .CtrlGroup {
+ width: 100%;
+ }
+ fieldset > .CtrlGroup > .Ctrl {
+ width: 100%; max-width: 100%; margin: 0 0 10px 0;
+ }
+ fieldset > .CtrlGroup > .Ctrl:has(textarea) {
+ height: 80px;
+ }
+ .ScrollableLog {
+ height: unset; max-height: 180px;
+ overflow: scroll;
+ }
+ .ScrollableList {
+ height: 280px;
+ overflow: scroll;
+ }
+ .ScrollableList .Ctrl {
+ width: 100%;
+ }
+ label {
+ width: 100%; height: 100%; border-radius: 5px;
+ text-wrap: nowrap; /* "text-wrap: nowrap" improves animation on hiding horizontally. */
+ display: flex; align-items: center; /* "label" cannot have "overflow: hidden" because of "Textbox". */
+ }
+ label > * {
+ margin: 0 0 0 5px;
+ }
+
+ /* Texts */
+ .SectionTitle {
+ padding: 30px 10px;
+ font-size: 2.50em; text-align: center;
+ }
+ h2 {
+ padding: 20px 0;
+ font-size: 2.00em;
+ }
+ h3 {
+ padding: 10px 0;
+ font-size: 1.50em;
+ }
+ h4 {
+ padding: 5px 0;
+ font-size: 1.25em; font-weight: normal;
+ }
+ p {
+ padding: 5px 0;
+ }
+ a {
+ border-radius: 5px;
+ color: #8040A0; text-decoration: none;
+ }
+ i {
+ font-style: italic;
+ }
+
+ /* Images */
+ img {
+ border-radius: 5px;
+ object-fit: cover;
+ }
+
+ /* Icons */
+ .Icon {
+ width: 1.25em; height: 1.25em; min-width: 1.25em; min-height: 1.25em;
+ fill: currentColor;
+ }
+ .Icon.Smaller {
+ width: 1.00em; height: 1.00em; min-width: 1.00em; min-height: 1.00em;
+ }
+ .Icon.Larger {
+ width: 1.50em; height: 1.50em; min-width: 1.50em; min-height: 1.50em;
+ }
+ .LabelAfterIcon {
+ margin: 0 0 0 5px;
+ }
+
+ /* Shapes */
+ .Shape {
+ width: 100%; height: 100%;
+ fill: #FFFFFF;
+ stroke: #00000040; stroke-width: 1px; stroke-linecap: round; stroke-linejoin: round;
+ overflow: visible;
+ }
+
+ /* Rolling digits */
+ .RollingDigit {
+ position: absolute; top: unset; left: 0;
+ width: 100%;
+ transition: none !important;
+ }
+ .RollingDigit span {
+ width: 100%;
+ text-align: center;
+ display: flex; justify-content: center; align-items: center;
+ }
+
+ /* Progress indicators */
+ .Progbar {
+ position: relative;
+ width: 100%; height: 100%; border-radius: 5px;
+ background-color: #00000020;
+ }
+ .Progbar.Shaped {
+ border: none;
+ background-color: transparent;
+ }
+ .Progbar.Shaped .Shape {
+ fill: #00000020;
+ }
+ .Progbar.Shaped .ProgbarText {
+ color: #000000;
+ }
+ .ProgbarFg {
+ position: absolute; top: 0; left: 0;
+ width: 10px; height: 100%; border-radius: 5px;
+ background-color: #8040A0;
+ }
+ .ProgbarFg.Vertical {
+ bottom: 0; top: unset;
+ width: 100%; height: 10px;
+ }
+ .ProgbarFg.Shaped {
+ width: 100%; height: 100%; clip-path: inset(0 100% 0 0);
+ background-color: transparent;
+ }
+ .ProgbarFg.Vertical.Shaped {
+ clip-path: inset(100% 0 0 0);
+ }
+ .ProgbarFg.Shaped .Shape {
+ fill: #D090F0;
+ }
+ .Progring {
+ width: 100%; height: 100%;
+ }
+ .ProgringBg {
+ fill: transparent;
+ stroke: #00000020; stroke-width: 5;
+ }
+ .ProgringFg {
+ transform: rotate(-90deg);
+ fill: transparent;
+ stroke: #8040A0; stroke-width: 5; stroke-linecap: round;
+ }
+ .ProgringText {
+ position: absolute; top: 0; left: 0;
+ width: 100%; height: 100%;
+ text-align: center;
+ display: flex; justify-content: center; align-items: center;
+ }
+ .Needle {
+ height: 100%;
+ }
+ .NeedleFg {
+ width: 100%; height: 50%;
+ background-color: #000000;
+ }
+ .NeedleArrow {
+ position: absolute; top: 0; left: 0;
+ width: 100%; clip-path: polygon(50% 0, 100% 100%, 0 100%);
+ background-color: #000000;
+ }
+
+ /* Indicator lights */
+ .IndicatorLight {
+ width: 15px; height: 15px;
+ }
+ .IndicatorLight.Off {
+ background-color: #00000040;
+ }
+ .IndicatorLight.Red {
+ background-color: #FF0000;
+ }
+ .IndicatorLight.Orange {
+ background-color: #E06000;
+ }
+ .IndicatorLight.Green {
+ background-color: #00A000;
+ }
+ .IndicatorLight.Blue {
+ background-color: #0090D0;
+ }
+
+ /* Interactive ctrls */
+ .Button, .Combobox {
+ width: 100%; height: 100%; border: 1px solid #00000040; border-radius: 5px; padding: 5px 10px;
+ background-color: #FFFFFF;
+ color: #000000; text-align: center; text-wrap: nowrap;
+ display: flex; justify-content: center; align-items: center; overflow: hidden;
+ }
+ .Button.Shaped, .Button.ShownAsLabel {
+ border: none; padding: 0;
+ background-color: transparent !important;
+ }
+ .Button.Shaped .Shape {
+ position: absolute; top: 0; left: 0;
+ }
+ .Button.ShownAsLabel {
+ justify-content: start;
+ }
+ .DropctrlGroup {
+ position: absolute; top: unset; left: unset; z-index: 2;
+ width: 122px; border: 1px solid #00000040; border-radius: 5px;
+ background-color: #FFFFFF; box-shadow: 0 0 4px 2px #00000040;
+ display: flex; flex-wrap: wrap; overflow: hidden;
+ }
+ .Dropctrl {
+ width: 100%; height: 35px;
+ }
+ .Dropctrl .Button {
+ border: none !important;
+ }
+ .Combobox {
+ padding: 5px;
+ text-align: unset;
+ }
+ fieldset .Combobox {
+ width: 200px;
+ }
+ .Textbox {
+ width: 100%; height: 100%; border: 1px solid #00000040; border-radius: 5px; padding: 5px;
+ background-color: #FFFFE8;
+ color: #000000;
+ }
+ .Textbox.ShownAsLabel {
+ border: none;
+ background-color: transparent;
+ }
+ fieldset .Textbox {
+ width: 240px;
+ }
+ textarea {
+ resize: none;
+ tab-size: 4;
+ }
+ .Slider {
+ width: 240px;
+ }
+
+ /* Floating ctrls */
+ .ScreenFilter {
+ position: fixed; top: 0; left: 0;
+ width: 100%; height: 100vh;
+ background-color: #00000080;
+ }
+ .ScreenFilter.AsWindow {
+ background-color: #F0F0F0E0; backdrop-filter: blur(5px);
+ }
+ .Window {
+ z-index: 2;
+ border-radius: 5px;
+ background-color: #F0F0F0E0; backdrop-filter: blur(5px); box-shadow: 0 0 4px 2px #00000040;
+ overflow: hidden;
+ }
+ /* Hotkey indicators */
+ .HotkeyIndicator {
+ position: absolute; bottom: -9px; left: calc(50% - 12.5px); z-index: 1;
+ width: 25px; height: 18px; border-radius: 5px;
+ background-color: #FFFFFF; box-shadow: 0 0 4px 2px #00000040;
+ font-size: 14px; text-align: center;
+ display: flex; justify-content: center; align-items: center; overflow: hidden;
+ pointer-events: none;
+ }
+ .HotkeyIndicator.TopAligned {
+ top: -9px; bottom: unset;
+ }
+ .HotkeyIndicator.LeftAligned {
+ bottom: calc(50% - 9px); left: -12.5px;
+ }
+ .HotkeyIndicator.RightAligned {
+ bottom: calc(50% - 9px); right: -12.5px; left: unset;
+ }
+
+ /* Toast */
+ #Toast {
+ position: fixed; top: calc(50vh + 5px); left: 0; z-index: 102;
+ width: 100%; height: 50px;
+ background-color: #F0D0FFE0; backdrop-filter: blur(5px); box-shadow: 0 0 4px 2px #00000040;
+ font-size: 2.00em; text-align: center;
+ display: flex; justify-content: center; align-items: center; overflow: hidden;
+ pointer-events: none;
+ }
+ #Html:has(#Topbar.Hidden) #Toast {
+ top: calc(50vh - 25px);
+ }
+
+ /* Dialog */
+ #ScreenFilter_Dialog {
+ z-index: 110;
+ }
+ #Window_Dialog {
+ position: fixed; top: calc(50vh - 100px); left: calc(50% - 190px); z-index: 111;
+ width: 380px; height: 200px;
+ display: flex; flex-wrap: wrap;
+ }
+ #Window_Dialog .HiddenHorizontally {
+ display: none;
+ }
+ #CtrlGroup_DialogPrompt {
+ width: 100%; height: 95px; padding: 15px 15px 0 15px;
+ }
+ .DialogIcon {
+ width: 35px; height: 35px;
+ }
+ #DialogIcon_Info {
+ fill: #00A000;
+ }
+ #DialogIcon_Question {
+ fill: #0090D0;
+ }
+ #DialogIcon_Caution {
+ fill: #E06000;
+ }
+ #DialogIcon_Error {
+ fill: #FF0000;
+ }
+ #Ctrl_DialogText {
+ width: calc(100% - 35px); height: 100%; padding: 9px 5px 0 10px;
+ overflow: scroll;
+ }
+ #CtrlGroup_DialogCheckboxOption {
+ width: 100%; height: 55px; padding: 10px 15px;
+ }
+ #CtrlGroup_DialogCheckboxOption .Ctrl {
+ width: 100%;
+ }
+ #CtrlGroup_DialogOptions {
+ width: 100%; height: 50px; padding: 0 15px 15px 15px;
+ flex-wrap: nowrap; justify-content: end;
+ }
+ #CtrlGroup_DialogOptions .Ctrl {
+ min-width: 100px; margin: 0 0 0 10px;
+ }
+
+ /* Watermarks */
+ .Watermark {
+ position: fixed; top: unset; left: unset; z-index: 121;
+ color: #00000080; text-shadow: 0 0 5px #FFFFFF; font-weight: bold; text-align: center;
+ display: flex; justify-content: center; align-items: center;
+ pointer-events: none;
+ }
+ #Watermark_TestVersion {
+ top: 100vh; left: 0;
+ width: 100%;
+ }
+ .Watermark span {
+ margin: 15px;
+ }
+ #Watermark_TestVersion span {
+ position: absolute; bottom: 0; left: unset;
+ }
+
+ /* Variants */
+ /* Size */
+ .WidthDividedBy2 {
+ width: calc((100% - 10px) / 2) !important;
+ }
+ .WidthDividedBy3 {
+ width: calc((100% - 20px) / 3) !important;
+ }
+ .WidthDividedBy4 {
+ width: calc((100% - 30px) / 4) !important;
+ }
+ .WidthDividedBy5 {
+ width: calc((100% - 40px) / 5) !important;
+ }
+ .Width60 {
+ width: 60px !important;
+ }
+ .Width80 {
+ width: 80px !important;
+ }
+ .Width100 {
+ width: 100px !important;
+ }
+ .Width120 {
+ width: 120px !important;
+ }
+ .Width160 {
+ width: 160px !important;
+ }
+ .Width200 {
+ width: 200px !important;
+ }
+ .Width240 {
+ width: 240px !important;
+ }
+ .Spin {
+ animation: Anim_Spin 1000ms infinite linear;
+ }
+ @keyframes Anim_Spin {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+ }
+
+ /* Background */
+ .Box {
+ border-radius: 5px;
+ background-color: #F0F0F0E0;
+ overflow: hidden;
+ }
+ .Active, .IAmHere {
+ background-color: #D0FFD0;
+ }
+ .Button.Shaped.Active .Shape {
+ fill: #D0FFD0;
+ }
+ .Transparent {
+ opacity: 0.2;
+ }
+ .Transparent.Less {
+ opacity: 0.6;
+ }
+ .Blink {
+ animation: Anim_Blink 1000ms infinite ease-in-out;
+ }
+ @keyframes Anim_Blink {
+ 0%, 80%, 100% {
+ opacity: 1;
+ }
+ 40%, 60% { /* .Transparent */
+ opacity: 0.2;
+ }
+ }
+ .Breathe {
+ animation: Anim_Breathe 1000ms infinite ease-in-out;
+ }
+ @keyframes Anim_Breathe {
+ 0%, 100% {
+ opacity: 1;
+ }
+ 50% { /* .Transparent.Less */
+ opacity: 0.6;
+ }
+ }
+ .Shadow {
+ box-shadow: 0 0 4px 2px #00000040;
+ }
+ .Glow {
+ box-shadow: 0 0 10px 5px #F0D0FF;
+ animation: Anim_Glow 1500ms infinite ease-in-out;
+ }
+ @keyframes Anim_Glow {
+ 0%, 100% {
+ box-shadow: none;
+ }
+ 50% {
+ box-shadow: 0 0 10px 5px #F0D0FF;
+ }
+ }
+
+ /* Foreground */
+ .RedText {
+ color: #E00000;
+ }
+ .OrangeText {
+ color: #D05000;
+ }
+ .GreenText {
+ color: #008000;
+ }
+ .SmallerText {
+ font-size: 0.75em;
+ }
+ .LargerText {
+ font-size: 1.25em;
+ }
+ .CenteredText {
+ text-align: center;
+ }
+ .RightAlignedText {
+ text-align: end;
+ }
+ .TableHeader {
+ font-weight: bold;
+ }
+ .Code {
+ font-family: monospace; /* font-family: Sarasa Mono J, LXGW WenKai Mono, XHei Square Mono, Consolas, monospace; */
+ white-space: pre; /* https://stackoverflow.com/a/13446005 */
+ }
+
+ /* Layout: hiding */
+ .Hidden {
+ height: 0 !important; border: none !important; padding: 0 !important; margin: 0 !important;
+ opacity: 0 !important;
+ overflow: hidden !important;
+ pointer-events: none !important;
+ }
+ #Toast.Hidden {
+ top: calc(50vh + 30px);
+ }
+ #Html:has(#Topbar.Hidden) #Toast.Hidden, #Window_Dialog.Hidden {
+ top: 50vh;
+ }
+ .HiddenHorizontally {
+ width: 0 !important; border: none !important; padding: 0 !important; margin: 0 !important;
+ opacity: 0 !important;
+ overflow: hidden !important;
+ pointer-events: none !important;
+ }
+ .HiddenToCorner {
+ width: 0 !important; height: 0 !important; border: none !important; padding: 0 !important; margin: 0 !important;
+ opacity: 0 !important;
+ overflow: hidden !important;
+ pointer-events: none !important;
+ }
+ .Faded, .ForceFaded {
+ opacity: 0 !important;
+ pointer-events: none !important;
+ }
+
+ /* Functionality */
+ /* .NeedsTooltip {
+ cursor: help;
+ } */
+
+ /* Interactions */
+ /* Hover */
+ a:hover, label:not(:has(:disabled:not(option))):hover, .Button:enabled:hover /* .Combobox:enabled:hover, .Textbox:enabled:hover */ {
+ background-color: #F0D0FF;
+ }
+ .Active:enabled:hover, .Active:not(:has(:disabled:not(option))):hover {
+ background-color: #B8E8B8;
+ }
+ .Button.Shaped:enabled:hover .Shape {
+ fill: #F0D0FF;
+ }
+ .Button.Shaped.Active:enabled:hover .Shape {
+ fill: #B8E8B8;
+ }
+ .Button.ShownAsLabel:enabled:hover {
+ color: #8040A0;
+ }
+ .Textbox.ShownAsLabel:enabled:hover, .Textbox.ShownAsLabel:focus-visible {
+ background-color: #FFFFE8;
+ }
+
+ /* Click */
+ a:active, label:not(:has(:disabled:not(option))):active, .Button:enabled:active,
+ .Combobox:enabled:active, .Textbox:enabled:active, .Textbox.ShownAsLabel:enabled:active,
+ .Active:enabled:active, .Active:not(:has(:disabled:not(option))):active {
+ background-color: #8040A0;
+ color: #FFFFFF;
+ }
+ .Button.Shaped:enabled:active .Shape, .Button.Shaped.Active:enabled:active .Shape {
+ fill: #8040A0;
+ }
+ .Button.ShownAsLabel:enabled:active {
+ color: #D090F0;
+ }
+ /* *:active {
+ transition: none; // Disabled due to SVG icon incompatibility.
+ } */
+
+ /* Focus */
+ *:focus-visible {
+ outline: none; box-shadow: 0 0 0 2px #8040A0 !important;
+ }
+ .Button.Shaped:focus-visible {
+ box-shadow: none !important;
+ }
+ .Button.Shaped:focus-visible .Shape {
+ stroke: #8040A0 !important; stroke-width: 2px !important;
+ }
+ .Dropctrl .Button:focus-visible {
+ box-shadow: 0 0 0 2px inset #8040A0 !important;
+ }
+
+ /* Disabled */
+ *:disabled {
+ opacity: 0.4;
+ }
+ *:disabled * {
+ opacity: unset;
+ }
+
+ /* Text selection */
+ ::selection {
+ background-color: #D090F0;
+ color: #000000;
+ }
+
+/* Area specific */
+ /* Header */
+ #Topbar {
+ position: fixed; top: 0; left: 0; z-index: 101;
+ width: 100%; height: 60px; padding: 12.5px 15px;
+ background-color: #FFFFFFE0; backdrop-filter: blur(5px); box-shadow: 0 0 4px 2px #00000040;
+ }
+ #Topbar .CtrlGroup {
+ position: relative;
+ width: 100%; height: 100%;
+ }
+ #Topbar .Button {
+ border: none;
+ }
+ #Topbar .Button:not(:hover):not(:active) {
+ background-color: transparent;
+ }
+ #Ctrl_Title {
+ position: absolute; top: 0; left: 0;
+ width: 35px;
+ }
+ #Ctrl_Title .Button {
+ padding: 0;
+ }
+ #Ctrl_Title img {
+ width: 100%; height: 100%;
+ }
+ #Ctrl_Nav {
+ position: absolute; top: 0; left: unset;
+ }
+ #DropctrlGroup_Nav {
+ top: 0; left: 0;
+ width: 100%; border: none;
+ background-color: transparent; box-shadow: none;
+ }
+ .Nav {
+ width: 120px;
+ }
+ #Ctrl_NavUnderline {
+ position: absolute; top: calc(100% - 2px); left: 4px;
+ width: 0; height: 2px; border-radius: 1px;
+ background-color: #000000;
+ }
+ #Ctrl_HomePage {
+ position: absolute; top: 0; right: 0;
+ width: 35px;
+ }
+
+ /* Main */
+ #Main {
+ padding: 60px 0 0 0;
+ background-color: #E0E0E0E0;
+ }
+ #Html:has(#Topbar.Hidden) #Main {
+ padding: 0;
+ }
+
+ /* Footer */
+ footer {
+ padding: 15px;
+ background-color: #E0E0E0E0;
+ color: #00000080; text-align: center;
+ display: flex; justify-content: center; align-items: center;
+ }
+
+/* Responsive web design */
+@media (max-width: 880px) {
+ /* General */
+ /* Basics */
+ /* Group frameworks */
+ .ItemGroup {
+ margin: 0 -7.5px;
+ }
+ .Item {
+ margin: 0 7.5px 15px 7.5px;
+ }
+ .ScrollableList {
+ height: 210px;
+ }
+
+ /* Floating ctrls */
+ /* Toast */
+ #Toast {
+ top: calc(50vh + 12.5px);
+ height: 35px;
+ font-size: 1.25em;
+ }
+ #Html:has(#Topbar.Hidden) #Toast {
+ top: calc(50vh - 17.5px);
+ }
+
+ /* Area specific */
+ /* Header */
+ #Ctrl_Title {
+ left: calc(50% - 17.5px);
+ }
+ #Ctrl_Nav {
+ left: 0 !important;
+ width: 35px !important;
+ }
+ #DropctrlGroup_Nav {
+ width: 122px; border: 1px solid #00000040;
+ background-color: #FFFFFF; box-shadow: 0 0 4px 2px #00000040;
+ }
+ .HiddenInMobileLayout { /* .HiddenToCorner */
+ width: 0 !important; height: 0 !important; border: none !important; padding: 0 !important; margin: 0 !important;
+ opacity: 0 !important;
+ overflow: hidden !important;
+ pointer-events: none !important;
+ }
+}
+@media (max-width: 440px) {
+ /* General */
+ /* Basics */
+ /* Group frameworks */
+ .Prolog {
+ width: 410px;
+ text-align: start;
+ }
+
+ /* Texts */
+ .SectionTitle {
+ text-align: start;
+ }
+}
+/* @media (min-width: 830px) { // Optimization for long fieldsets exceeding 600px height.
+ // These should be configured in style.css.
+} */
+/* @media (max-aspect-ratio: 0.999) {
+ / General /
+ / Basics /
+ / Overall /
+ #BgImage {
+ background-image: url(../images/Background_Narrow.jpg);
+ }
+} */
+
+/* Accessibility */
+@media (prefers-reduced-motion: reduce) {
+ * {
+ transition: none !important; animation: none !important; scroll-behavior: auto !important;
+ }
+}
+
+/* Dev */
+#Html.TryToOptimizePerformance * {
+ backdrop-filter: none !important;
+}
+#Html.ShowDebugOutlines * {
+ outline: 1px solid #00C000;
+}
+#Html.ShowDebugOutlines section {
+ outline: 3px dashed #000000;
+}
+#Html.ShowDebugOutlines .ItemGroup, #Html.ShowDebugOutlines .Viewport, #Html.ShowDebugOutlines .Window {
+ outline: 3px solid #FF8080;
+}
+#Html.ShowDebugOutlines .Item, #Html.ShowDebugOutlines .Ctnr {
+ outline: 3px dashed #FF0000;
+}
+#Html.ShowDebugOutlines .CtrlGroup, #Html.ShowDebugOutlines .DropctrlGroup {
+ outline: 2px solid #8080FF;
+}
+#Html.ShowDebugOutlines .Ctrl, #Html.ShowDebugOutlines .Dropctrl {
+ outline: 2px dashed #0000FF;
+}
+#Html.Cheat {
+ font-style: oblique 15deg !important;
+}
diff --git a/PROJECT/styles/common_Dark.css b/PROJECT/styles/common_Dark.css
new file mode 100644
index 0000000..63d1c3a
--- /dev/null
+++ b/PROJECT/styles/common_Dark.css
@@ -0,0 +1,259 @@
+/* For SamToki.github.io */
+/* Released under GNU GPL v3 open source license. */
+/* © 2023 SAM TOKI STUDIO */
+
+/* This is a theme variant, so only colors along with few things are configured. */
+
+/* General */
+ /* Basics */
+ /* Overall */
+ #Html {
+ color: #FFFFFF;
+ }
+
+ /* Group frameworks */
+ .Item {
+ background-color: #202020E0;
+ }
+
+ /* Texts */
+ a {
+ color: #D090F0;
+ }
+
+ /* Shapes */
+ .Shape {
+ fill: #303030;
+ stroke: #FFFFFF40;
+ }
+
+ /* Progress indicators */
+ .Progbar {
+ background-color: #FFFFFF20;
+ }
+ .Progbar.Shaped .Shape {
+ fill: #FFFFFF20;
+ }
+ .Progbar.Shaped .ProgbarText {
+ color: #FFFFFF;
+ }
+ .ProgbarFg {
+ background-color: #D090F0;
+ }
+ .ProgbarFg.Shaped .Shape {
+ fill: #8040A0;
+ }
+ .ProgringBg {
+ stroke: #FFFFFF20;
+ }
+ .ProgringFg {
+ stroke: #D090F0;
+ }
+ .NeedleFg, .NeedleArrow {
+ background-color: #FFFFFF;
+ }
+
+ /* Indicator lights */
+ .IndicatorLight.Off {
+ background-color: #FFFFFF40;
+ }
+ .IndicatorLight.Red {
+ background-color: #FF4040;
+ }
+ .IndicatorLight.Orange {
+ background-color: #FF8000;
+ }
+ .IndicatorLight.Green {
+ background-color: #00C000;
+ }
+ .IndicatorLight.Blue {
+ background-color: #00A0E0;
+ }
+
+ /* Interactive ctrls */
+ .Button, .Combobox {
+ border: 1px solid #FFFFFF40;
+ background-color: #303030;
+ color: #FFFFFF;
+ }
+ .DropctrlGroup {
+ border: 1px solid #FFFFFF40;
+ background-color: #303030;
+ }
+ .Textbox {
+ border: 1px solid #FFFFFF40;
+ background-color: #303018;
+ color: #FFFFFF;
+ }
+
+ /* Floating ctrls */
+ .ScreenFilter {
+ background-color: #000000C0;
+ }
+ .ScreenFilter.AsWindow, .Window {
+ background-color: #202020E0;
+ }
+ /* Hotkey indicators */
+ .HotkeyIndicator {
+ background-color: #303030;
+ }
+
+ /* Toast */
+ #Toast {
+ background-color: #583868E0;
+ }
+
+ /* Dialog */
+ #DialogIcon_Info {
+ fill: #00C000;
+ }
+ #DialogIcon_Question {
+ fill: #00A0E0;
+ }
+ #DialogIcon_Caution {
+ fill: #FF8000;
+ }
+ #DialogIcon_Error {
+ fill: #FF4040;
+ }
+
+ /* Watermarks */
+ .Watermark {
+ color: #FFFFFF80; text-shadow: 0 0 5px #000000;
+ }
+
+ /* Variants */
+ /* Background */
+ .Box {
+ background-color: #202020E0;
+ }
+ .Active, .IAmHere {
+ background-color: #004000;
+ }
+ .Button.Shaped.Active .Shape {
+ fill: #004000;
+ }
+ .Glow {
+ box-shadow: 0 0 10px 5px #583868;
+ }
+ @keyframes Anim_Glow {
+ 0%, 100% {
+ box-shadow: none;
+ }
+ 50% {
+ box-shadow: 0 0 10px 5px #583868;
+ }
+ }
+
+ /* Foreground */
+ .RedText {
+ color: #FF6060;
+ }
+ .OrangeText {
+ color: #FFA000;
+ }
+ .GreenText {
+ color: #00E000;
+ }
+
+ /* Interactions */
+ /* Hover */
+ a:hover, label:not(:has(:disabled:not(option))):hover, .Button:enabled:hover /* .Combobox:enabled:hover, .Textbox:enabled:hover */ {
+ background-color: #583868;
+ }
+ .Active:enabled:hover, .Active:not(:has(:disabled:not(option))):hover {
+ background-color: #185818;
+ }
+ .Button.Shaped:enabled:hover .Shape {
+ fill: #583868;
+ }
+ .Button.Shaped.Active:enabled:hover .Shape {
+ fill: #185818;
+ }
+ .Button.ShownAsLabel:enabled:hover {
+ color: #D090F0;
+ }
+ .Textbox.ShownAsLabel:enabled:hover, .Textbox.ShownAsLabel:focus-visible {
+ background-color: #303018;
+ }
+
+ /* Click */
+ a:active, label:not(:has(:disabled:not(option))):active, .Button:enabled:active,
+ .Combobox:enabled:active, .Textbox:enabled:active, .Textbox.ShownAsLabel:enabled:active,
+ .Active:enabled:active, .Active:not(:has(:disabled:not(option))):active {
+ background-color: #D090F0;
+ color: #000000;
+ }
+ .Button.Shaped:enabled:active .Shape, .Button.Shaped.Active:enabled:active .Shape {
+ fill: #D090F0;
+ }
+ .Button.ShownAsLabel:enabled:active {
+ color: #8040A0;
+ }
+
+ /* Focus */
+ *:focus-visible {
+ box-shadow: 0 0 0 2px #D090F0 !important;
+ }
+ .Button.Shaped:focus-visible .Shape {
+ stroke: #D090F0 !important;
+ }
+ .Dropctrl .Button:focus-visible {
+ box-shadow: 0 0 0 2px inset #D090F0 !important;
+ }
+
+ /* Text selection */
+ ::selection {
+ background-color: #8040A0;
+ color: #FFFFFF;
+ }
+
+/* Area specific */
+ /* Header */
+ #Topbar {
+ background-color: #303030E0;
+ }
+ #Ctrl_NavUnderline {
+ background-color: #FFFFFF;
+ }
+
+ /* Main */
+ #Main {
+ background-color: #000000E0;
+ }
+
+ /* Footer */
+ footer {
+ background-color: #000000E0;
+ color: #FFFFFF80;
+ }
+
+/* Responsive web design */
+@media (max-width: 880px) {
+ /* Area specific */
+ /* Header */
+ #DropctrlGroup_Nav {
+ border: 1px solid #FFFFFF40;
+ background-color: #303030;
+ }
+}
+
+/* Dev */
+#Html.ShowDebugOutlines * {
+ outline: 1px solid #008000;
+}
+#Html.ShowDebugOutlines section {
+ outline: 3px dashed #FFFFFF;
+}
+#Html.ShowDebugOutlines .ItemGroup, #Html.ShowDebugOutlines .Viewport, #Html.ShowDebugOutlines .Window {
+ outline: 3px solid #C00000;
+}
+#Html.ShowDebugOutlines .Item, #Html.ShowDebugOutlines .Ctnr {
+ outline: 3px dashed #FF6060;
+}
+#Html.ShowDebugOutlines .CtrlGroup, #Html.ShowDebugOutlines .DropctrlGroup {
+ outline: 2px solid #0000C0;
+}
+#Html.ShowDebugOutlines .Ctrl, #Html.ShowDebugOutlines .Dropctrl {
+ outline: 2px dashed #6060FF;
+}
diff --git a/PROJECT/styles/common_Genshin.css b/PROJECT/styles/common_Genshin.css
new file mode 100644
index 0000000..17b0c89
--- /dev/null
+++ b/PROJECT/styles/common_Genshin.css
@@ -0,0 +1,286 @@
+/* For SamToki.github.io */
+/* Released under GNU GPL v3 open source license. */
+/* © 2023 SAM TOKI STUDIO */
+
+/* This is a theme variant, so only colors along with few things are configured. */
+
+/* General */
+ /* Basics */
+ /* Overall */
+ #Html {
+ color: #EEE4D9;
+ }
+
+ /* Group frameworks */
+ .Item {
+ background-color: #485267E0;
+ }
+
+ /* Texts */
+ a {
+ color: #DEB76C;
+ }
+
+ /* Shapes */
+ .Shape {
+ fill: #EEE4D9;
+ stroke: #EEE4D940;
+ }
+
+ /* Progress indicators */
+ .Progbar {
+ background-color: #EEE4D920;
+ }
+ .Progbar.Shaped .Shape {
+ fill: #EEE4D920;
+ }
+ .Progbar.Shaped .ProgbarText {
+ color: #FFFFFF;
+ }
+ .ProgbarFg {
+ background-color: #96D722;
+ }
+ .ProgbarFg.Shaped .Shape {
+ fill: #73A41A;
+ }
+ .ProgringBg {
+ stroke: #EEE4D920;
+ }
+ .ProgringFg {
+ stroke: #96D722;
+ }
+ .NeedleFg, .NeedleArrow {
+ background-color: #EEE4D9;
+ }
+
+ /* Indicator lights */
+ .IndicatorLight.Off {
+ background-color: #EEE4D940;
+ }
+ .IndicatorLight.Red {
+ background-color: #FF4040;
+ }
+ .IndicatorLight.Orange {
+ background-color: #FF8000;
+ }
+ .IndicatorLight.Green {
+ background-color: #00C000;
+ }
+ .IndicatorLight.Blue {
+ background-color: #00A0E0;
+ }
+
+ /* Interactive ctrls */
+ .Button, .Combobox {
+ border: 1px solid #EEE4D940; border-radius: 17.5px;
+ background-color: #EEE4D9;
+ color: #485267;
+ }
+ .Button.ShownAsLabel {
+ border-radius: 5px;
+ color: #EEE4D9;
+ }
+ .DropctrlGroup {
+ border: 1px solid #EEE4D940; border-radius: 17.5px;
+ background-color: #EEE4D9;
+ }
+ .Combobox {
+ padding: 5px 10px;
+ }
+ .Textbox {
+ border: 1px solid #F5F5F540; border-radius: 17.5px; padding: 5px 10px;
+ background-color: #F5F5F5;
+ color: #323232;
+ }
+ .Textbox.ShownAsLabel {
+ border-radius: 5px; padding: 5px;
+ color: #EEE4D9;
+ }
+
+ /* Floating ctrls */
+ .ScreenFilter {
+ background-color: #000000C0;
+ }
+ .ScreenFilter.AsWindow, .Window {
+ background-color: #485267E0;
+ }
+ /* Hotkey indicators */
+ .HotkeyIndicator {
+ background-color: #FFFFFF;
+ color: #323232;
+ }
+
+ /* Toast */
+ #Toast {
+ background-color: #FFFFCDE0;
+ color: #A17646;
+ }
+
+ /* Dialog */
+ #CtrlGroup_DialogPrompt .DialogIcon {
+ fill: #EEE4D9;
+ }
+
+ /* Watermarks */
+ .Watermark {
+ color: #EEE4D980; text-shadow: 0 0 5px #000000;
+ }
+
+ /* Variants */
+ /* Background */
+ .Box {
+ background-color: #485267E0;
+ }
+ .Active {
+ background-color: #C8EC86;
+ color: #485267;
+ }
+ .Button.Shaped.Active .Shape {
+ fill: #C8EC86;
+ }
+ .IAmHere {
+ background-color: #618A16;
+ }
+ .Glow {
+ box-shadow: unset; filter: drop-shadow(0 0 5px #DEB76C);
+ }
+ @keyframes Anim_Glow {
+ 0%, 100% {
+ filter: none;
+ }
+ 50% {
+ filter: drop-shadow(0 0 5px #DEB76C);
+ }
+ }
+
+ /* Foreground */
+ .RedText {
+ color: #FF7070;
+ }
+ .OrangeText {
+ color: #FFB000;
+ }
+ .GreenText {
+ color: #96D722;
+ }
+
+ /* Interactions */
+ /* Hover */
+ a:hover, label:not(:has(:disabled:not(option))):hover, .Button:enabled:hover /* .Combobox:enabled:hover, .Textbox:enabled:hover */ {
+ background-color: #EEE4D9; box-shadow: 0 0 0 3px #DEB76C;
+ color: #485267;
+ }
+ .Active:enabled:hover, .Active:not(:has(:disabled:not(option))):hover {
+ background-color: #C8EC86;
+ }
+ .Button.Shaped:enabled:hover {
+ box-shadow: none;
+ }
+ .Button.Shaped:enabled:hover .Shape {
+ fill: #EEE4D9;
+ stroke: #DEB76C; stroke-width: 3px;
+ }
+ .Button.Shaped.Active:enabled:hover .Shape {
+ fill: #C8EC86;
+ }
+ .Button.ShownAsLabel:enabled:hover {
+ box-shadow: none;
+ color: #DEB76C;
+ }
+ .Dropctrl a:hover, .Dropctrl .Button:enabled:hover, .ScrollableList label:not(:has(:disabled:not(option))):hover {
+ box-shadow: 0 0 0 3px inset #DEB76C;
+ }
+ .Textbox.ShownAsLabel:enabled:hover, .Textbox.ShownAsLabel:focus-visible {
+ background-color: #F5F5F5;
+ color: #323232;
+ }
+
+ /* Click */
+ a:active, label:not(:has(:disabled:not(option))):active, .Button:enabled:active,
+ .Combobox:enabled:active, .Textbox:enabled:active, .Textbox.ShownAsLabel:enabled:active,
+ .Active:enabled:active, .Active:not(:has(:disabled:not(option))):active {
+ background-color: #D0C6BD;
+ color: #EEE4D9;
+ }
+ .Button.Shaped:enabled:active .Shape, .Button.Shaped.Active:enabled:active .Shape {
+ fill: #D0C6BD;
+ }
+ .Button.ShownAsLabel:enabled:active {
+ color: #CC972E;
+ }
+
+ /* Focus */
+ *:focus-visible {
+ box-shadow: 0 0 0 3px #DEB76C !important;
+ }
+ .Button.Shaped:focus-visible .Shape {
+ stroke: #DEB76C !important;
+ }
+ .Dropctrl .Button:focus-visible {
+ box-shadow: 0 0 0 3px inset #DEB76C !important;
+ }
+
+ /* Text selection */
+ ::selection {
+ background-color: #FDD662;
+ color: #323232;
+ }
+
+/* Area specific */
+ /* Header */
+ #Topbar {
+ background-color: #485267E0;
+ }
+ #Topbar .Button, #DropctrlGroup_Nav {
+ border-radius: 5px;
+ }
+ #Topbar .Button:not(:hover):not(:active) {
+ color: #EEE4D9;
+ }
+ #Ctrl_NavUnderline {
+ background-color: #EEE4D9;
+ }
+
+ /* Main */
+ #Main {
+ background-color: #282D38E0;
+ }
+
+ /* Footer */
+ footer {
+ background-color: #282D38E0;
+ color: #EEE4D980;
+ }
+
+/* Responsive web design */
+@media (max-width: 880px) {
+ /* Area specific */
+ /* Header */
+ #DropctrlGroup_Nav {
+ border: 1px solid #EEE4D940;
+ background-color: #EEE4D9;
+ }
+ #DropctrlGroup_Nav .Button:not(:hover):not(:active) {
+ color: #485267;
+ }
+}
+
+/* Dev */
+#Html.ShowDebugOutlines * {
+ outline: 1px solid #008000;
+}
+#Html.ShowDebugOutlines section {
+ outline: 3px dashed #FFFFFF;
+}
+#Html.ShowDebugOutlines .ItemGroup, #Html.ShowDebugOutlines .Viewport, #Html.ShowDebugOutlines .Window {
+ outline: 3px solid #C00000;
+}
+#Html.ShowDebugOutlines .Item, #Html.ShowDebugOutlines .Ctnr {
+ outline: 3px dashed #FF6060;
+}
+#Html.ShowDebugOutlines .CtrlGroup, #Html.ShowDebugOutlines .DropctrlGroup {
+ outline: 2px solid #0000C0;
+}
+#Html.ShowDebugOutlines .Ctrl, #Html.ShowDebugOutlines .Dropctrl {
+ outline: 2px dashed #6060FF;
+}
diff --git a/PROJECT/styles/common_HighContrast.css b/PROJECT/styles/common_HighContrast.css
new file mode 100644
index 0000000..37df813
--- /dev/null
+++ b/PROJECT/styles/common_HighContrast.css
@@ -0,0 +1,244 @@
+/* For SamToki.github.io */
+/* Released under GNU GPL v3 open source license. */
+/* © 2023 SAM TOKI STUDIO */
+
+/* This is a theme variant, so only colors along with few things are configured. */
+
+/* General */
+ /* Basics */
+ /* Overall */
+ #Html {
+ color: #FFFFFF;
+ }
+
+ /* Group frameworks */
+ .Item {
+ background-color: #101010;
+ }
+
+ /* Texts */
+ a {
+ color: #00C0FF; text-decoration: underline;
+ }
+
+ /* Shapes */
+ .Shape {
+ fill: #202020;
+ stroke: #FFFFFF;
+ }
+
+ /* Progress indicators */
+ .Progbar {
+ background-color: #404040;
+ }
+ .Progbar.Shaped .Shape {
+ fill: #404040;
+ }
+ .Progbar.Shaped .ProgbarText {
+ color: #FFFFFF;
+ }
+ .ProgbarFg {
+ background-color: #00E000;
+ }
+ .ProgbarFg.Shaped .Shape {
+ fill: #00A000;
+ }
+ .ProgringBg {
+ stroke: #404040;
+ }
+ .ProgringFg {
+ stroke: #00E000;
+ }
+ .NeedleFg, .NeedleArrow {
+ background-color: #FFFFFF;
+ }
+
+ /* Indicator lights */
+ .IndicatorLight.Off {
+ background-color: #404040;
+ }
+ .IndicatorLight.Red {
+ background-color: #FF6060;
+ }
+ .IndicatorLight.Orange {
+ background-color: #FFA000;
+ }
+ .IndicatorLight.Green {
+ background-color: #00E000;
+ }
+ .IndicatorLight.Blue {
+ background-color: #00C0FF;
+ }
+
+ /* Interactive ctrls */
+ .Button, .Combobox {
+ border: 1px solid #FFFFFF;
+ background-color: #202020;
+ color: #FFFFFF;
+ }
+ .DropctrlGroup {
+ border: 1px solid #FFFFFF;
+ background-color: #202020; box-shadow: none;
+ }
+ .Textbox {
+ border: 1px solid #FFFFFF;
+ background-color: #202008;
+ color: #FFFFFF;
+ }
+
+ /* Floating ctrls */
+ .ScreenFilter {
+ background-color: #000000;
+ }
+ .ScreenFilter.AsWindow {
+ background-color: #101010;
+ }
+ .Window {
+ border: 1px solid #FFFFFF;
+ background-color: #101010; box-shadow: none;
+ }
+ /* Hotkey indicators */
+ .HotkeyIndicator {
+ background-color: #202020; box-shadow: none;
+ color: #FFFF00;
+ }
+
+ /* Toast */
+ #Toast {
+ background-color: #402050; box-shadow: none;
+ }
+
+ /* Dialog */
+ #CtrlGroup_DialogPrompt .DialogIcon {
+ fill: #FFFFFF;
+ }
+
+ /* Watermarks */
+ .Watermark {
+ color: #FFFFFF80; text-shadow: 0 0 5px #000000;
+ }
+
+ /* Variants */
+ /* Background */
+ .Box {
+ background-color: #101010;
+ }
+ .Active, .IAmHere {
+ background-color: #006000;
+ }
+ .Button.Shaped.Active .Shape {
+ fill: #006000;
+ }
+ .Shadow {
+ box-shadow: none;
+ }
+ .Glow {
+ box-shadow: 0 0 10px 5px #00C0FF;
+ }
+ @keyframes Anim_Glow {
+ 0%, 100% {
+ box-shadow: none;
+ }
+ 50% {
+ box-shadow: 0 0 10px 5px #00C0FF;
+ }
+ }
+
+ /* Foreground */
+ .RedText {
+ color: #FF8080;
+ }
+ .OrangeText {
+ color: #FFC000;
+ }
+ .GreenText {
+ color: #00FF00;
+ }
+
+ /* Interactions */
+ /* Hover */
+ a:hover, label:not(:has(:disabled:not(option))):hover, .Button:enabled:hover /* .Combobox:enabled:hover, .Textbox:enabled:hover */ {
+ background-color: #00C0FF;
+ color: #000000;
+ }
+ .Active:enabled:hover, .Active:not(:has(:disabled:not(option))):hover {
+ background-color: #00E000;
+ }
+ .Button.Shaped:enabled:hover .Shape {
+ fill: #00C0FF;
+ }
+ .Button.Shaped.Active:enabled:hover .Shape {
+ fill: #00E000;
+ }
+ .Button.ShownAsLabel:enabled:hover {
+ color: #00C0FF;
+ }
+ .Textbox.ShownAsLabel:enabled:hover, .Textbox.ShownAsLabel:focus-visible {
+ background-color: #202008;
+ }
+
+ /* Click */
+ a:active, label:not(:has(:disabled:not(option))):active, .Button:enabled:active,
+ .Combobox:enabled:active, .Textbox:enabled:active, .Textbox.ShownAsLabel:enabled:active,
+ .Active:enabled:active, .Active:not(:has(:disabled:not(option))):active {
+ background-color: #00FFFF;
+ color: #000000;
+ }
+ .Button.Shaped:enabled:active .Shape, .Button.Shaped.Active:enabled:active .Shape {
+ fill: #00FFFF;
+ }
+ .Button.ShownAsLabel:enabled:active {
+ color: #00FFFF;
+ }
+
+ /* Focus */
+ *:focus-visible {
+ box-shadow: 0 0 0 2px #00FFFF !important;
+ }
+ .Button.Shaped:focus-visible .Shape {
+ stroke: #00FFFF !important;
+ }
+ .Dropctrl .Button:focus-visible {
+ box-shadow: 0 0 0 2px inset #00FFFF !important;
+ }
+
+ /* Text selection */
+ ::selection {
+ background-color: #0040FF;
+ color: #FFFFFF;
+ }
+
+/* Area specific */
+ /* Header */
+ #Topbar {
+ background-color: #202020; box-shadow: none;
+ }
+ #Ctrl_NavUnderline {
+ background-color: #FFFFFF;
+ }
+
+ /* Main */
+ #Main {
+ background-color: #000000;
+ }
+
+ /* Footer */
+ footer {
+ background-color: #000000;
+ color: #FFFFFF;
+ }
+
+/* Responsive web design */
+@media (max-width: 880px) {
+ /* Area specific */
+ /* Header */
+ #DropctrlGroup_Nav {
+ border: 1px solid #FFFFFF;
+ background-color: #202020; box-shadow: none;
+ }
+}
+
+/* Dev */
+#Html.ShowDebugOutlines * {
+ outline: none !important; /* Disabled in high contrast theme to keep accessibility. */
+}
diff --git a/README.md b/README.md
index 189352a..d5829cc 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,74 @@
-# GPS-PFD
- 利用 GPS 与加速计,在移动设备上模拟飞机驾驶舱的 PFD。 / Simulate cockpit PFD on mobile devices by using GPS and accelerometer.
+# 简介 Description
+
+GPS-PFD,利用 [GPS](https://zh.wikipedia.org/wiki/GPS) 与[加速计](https://zh.wikipedia.org/wiki/加速计),在移动设备上模拟飞机驾驶舱的 [PFD](https://zh.wikipedia.org/wiki/主飞行显示器)。
+GPS-PFD simulates the cockpit [PFD (Primary Flight Display)](https://en.wikipedia.org/wiki/Primary_flight_display) on mobile devices, by using [GPS](https://en.wikipedia.org/wiki/GPS) and [accelerometer](https://en.wikipedia.org/wiki/Accelerometer).
+
+
+
+备注:这里的「GPS」指的是位置服务,其利用以 GPS 为代表的[卫星导航系统 (GNSS)](https://zh.wikipedia.org/wiki/卫星导航系统)来精确定位。卫星导航系统包括美国 GPS、俄罗斯 [GLONASS](https://zh.wikipedia.org/wiki/GLONASS),以及中国[北斗](https://zh.wikipedia.org/wiki/北斗卫星导航系统)等。加速计亦称加速度传感器、重力感应器,用于测量姿态、速度与高度。
+Note: The "GPS" here stands for location services. Location services use [GNSSes](https://en.wikipedia.org/wiki/GNSS) such as GPS to precisely locate your position. The most widely used GNSSes include GPS from the US, [GLONASS](https://en.wikipedia.org/wiki/GLONASS) from Russia, [Beidou](https://en.wikipedia.org/wiki/BeiDou) from China, etc. Accelerometer is used to measure attitude, speed and altitude.
+
+# 预览 Preview
+
+
+
+# 使用前须知 Read Before Use
+
+## 使用环境 Usage Environment
+
+建议在搭载了 GPS 芯片与加速计的移动设备(智能手机或平板电脑)上使用本应用程序。否则核心功能可能不可用。GPS 可能会非常耗电,因此建议使用时连接移动电源(充电宝)。
+It is recommended that you use this application on a mobile device (smartphone or tablet) with a GPS chip and an accelerometer. Otherwise the basic features may become unavailable. Besides, GPS may be power hungry. It is recommended to connect your device to a power bank while using the application.
+
+## 离线使用 Offline Use
+
+在没有网络连接的飞机上,您需要离线使用本应用程序。有两种方式来离线使用:
+Offline use is required when on an airplane without Internet connection. There are two ways of offline use:
+
+- 断网前先在线访问一次。
+Visit the webpage once, before disconnecting to the Internet.
+- [下载资源](https://github.com/SamToki/GPS-PFD/releases/latest)至本地,在本地运行网页。
+[Download the resources](https://github.com/SamToki/GPS-PFD/releases/latest) and run the webpage locally.
+
+## 安全与隐私 Safety and Privacy
+
+本应用程序仅供娱乐用途。警报功能可能出现误报。使用时请注意安全。本应用程序可能使用位置服务。详见[免责声明](https://SamToki.github.io/GPS-PFD/#Item_HelpDisclaimer)与[隐私权声明](https://SamToki.github.io/GPS-PFD/#Item_HelpPrivacyStatement)。
+This application is for entertainment purposes only. The alert feature may give false warnings. Mind your safety while using. This application may use location services. Learn more at the [Disclaimer](https://SamToki.github.io/GPS-PFD/#Item_HelpDisclaimer) and [Privacy Statement](https://SamToki.github.io/GPS-PFD/#Item_HelpPrivacyStatement).
+
+## 已知问题 Known Issues
+
+本应用程序仍处于测试阶段。已知问题有:
+This application is still in beta test. Known issues are as follows:
+
+- 加速计误差严重。系统已尽可能减小误差。
+The accelerometer is serious inaccurate. The system does its best to reduce errors.
+- 在 [Chromium](https://zh.wikipedia.org/wiki/Chromium) 内核的浏览器上可能无法显示朝向。
+The heading may be unavailable on [Chromium](https://en.wikipedia.org/wiki/Chromium_(web_browser)) browsers.
+- 「保持屏幕常亮」可能不起作用。
+The "keep screen on" feature may not work.
+- 在 [Firefox](https://zh.wikipedia.org/wiki/Firefox) for Android 上偶尔会出错。
+Rare errors occur on [Firefox](https://en.wikipedia.org/wiki/Firefox) for Android.
+- 在 [Brave](https://zh.wikipedia.org/wiki/Brave浏览器) for Android 上无法使用。
+Completely unusable on [Brave](https://en.wikipedia.org/wiki/Brave_(web_browser)) for Android.
+
+若您有其他问题或建议,请向我[提供反馈](https://SamToki.github.io/GPS-PFD/#Item_HelpGetInvolved)。
+Please [provide feedback](https://SamToki.github.io/GPS-PFD/#Item_HelpGetInvolved) to me if you have other problems or suggestions.
+
+# 立即试用 Try Now
+
+https://SamToki.github.io/GPS-PFD
+
+可扫描二维码:
+Scan the QR code to use on mobile devices:
+
+
+
+# 教程 Tutorial
+
+[说明文档](/PROJECT/docs/GPS-PFD%20说明文档.pdf)
+
+# 版权说明 Copyright Info
+
+本站的源代码遵循 [GNU GPL v3 软件开源协议](https://www.gnu.org/licenses/gpl-3.0.en.html)。本站的部分内容为他人作品,版权为原作者所有,本站遵循 [fair use](https://zh.wikipedia.org/wiki/fair_use) 原则使用并于 Credits 标注来源。除上述内容之外,本站所有内容以及排版、美术设计均保留版权。
+The source codes of this website are released under [GNU GPL v3 open source license](https://www.gnu.org/licenses/gpl-3.0.en.html). Some contents in the website are works by other people, and their copyrights belong to the corresponding authors. They are used under the "[fair use](https://en.wikipedia.org/wiki/fair_use)" principle and are credited. Other contents such as the page design are copyrighted.
+
+© 2025 SAM TOKI STUDIO