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

+
+
+
+
    +
  • + 姿态仪状态 +
  • +
  • +
    +
    +
  • +
  • +
      +
    • 90
      90
    • +
    • +
    • +
    • +
    • 80
      80
    • +
    • +
    • +
    • +
    • 70
      70
    • +
    • +
    • +
    • +
    • 60
      60
    • +
    • +
    • +
    • +
    • 50
      50
    • +
    • +
    • +
    • +
    • 40
      40
    • +
    • +
    • +
    • +
    • 30
      30
    • +
    • +
    • +
    • +
    • 20
      20
    • +
    • +
    • +
    • +
    • 10
      10
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    • 10
      10
    • +
    • +
    • +
    • +
    • 20
      20
    • +
    • +
    • +
    • +
    • 30
      30
    • +
    • +
    • +
    • +
    • 40
      40
    • +
    • +
    • +
    • +
    • 50
      50
    • +
    • +
    • +
    • +
    • 60
      60
    • +
    • +
    • +
    • +
    • 70
      70
    • +
    • +
    • +
    • +
    • 80
      80
    • +
    • +
    • +
    • +
    • 90
      90
    • +
    +
  • +
  • +
      +
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    + +
    +
  • +
  • +
      +
    • +
    • +
    • +
    • +
    • +
    +
  • +
+
+
+
    +
  • + 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
    • +
    +
  • +
  • +
    +
    +
    +
    +
      +
    • +
    • +
    +
  • +
  • +
      +
    • +
        +
      • +
        + 987654321 +
        +
      • +
      • +
        + 09876543210 +
        +
      • +
      • +
        + 987 + 1098765432109 + 210 +
        +
      • +
      +
    • +
    • +
    +
  • +
  • + 马赫数 +
  • +
+
+
+
    +
  • + 高度表状态 +
  • +
  • +
      +
    • 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
    • +
    +
  • +
  • +
    +
    +
    +
    +
      +
    • +
    • +
    +
  • +
  • +
      +
    • +
        +
      • +
        + 54321 - +
        +
      • +
      • +
        + 09876543210 + 12 +
        +
      • +
      • +
        + 09876543210 + 1234567890 +
        +
      • +
      • +
        + 008060 + 2000806040200080 + 4020002040 + 8000204060800020 + 608000 +
        +
      • +
      +
    • +
    • +
    +
  • +
  • + 米制高度 +
  • +
+
+
+
    +
  • + 垂直速度表状态 +
  • +
  • +
      +
    • 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
    • +
    • +
    +
  • +
  • +
      +
    • + 朝向 +
    • +
    • +
    +
  • +
+
+
+ 测距仪 + 距离 + 剩余时间 +
+
+
    +
  • +
      +
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    + + + 无线电
    + 高度
    +
    +
  • +
+
+
+ 决断高度 + 未知 +
+
+ 警告信息 +
+
+
+ ??? +
+
+ ??? +
+
+ ??? +
+
+ ??? +
+
+ ??? +
+
+ ??? +
+
+
    +
  • + + M +
    +
      +
    • + +
    • +
    +
      +
    • +
        +
      • + +
      • +
      +
        +
      • + +
      • + +
      • +

        襟翼

        +
          +
        • + + R
          + ↕
          + F
          +
        • +
        • + B738A320 +
            +
          • 0
          • +
          • 1~5
          • +
          • 10
          • +
          • 15
          • +
          • 25
          • +
          • 30
          • +
          • 40
          • +
          +
            +
          • 0
          • +
          • 1
          • +
          • 1+F
          • +
          • 2
          • +
          • 3
          • +
          • FULL
          • +
          +
        • +
        +
      • +
      • + +
      • +
      +
    • +
    • +
        +
      • + +
      • +
      +
        +
      • + +
      • +
      • + +
      • +
      • + +
      • +
      • + +
      • +
      • + +
      • +
      • + +
      • +
      • + +
      • +
      • + +
      • +
      +
    • +
    • +
        +
      • + +
      • +
      + +
    • +
    +
    +
  • +
+
+
+
+ +
+

设定

+ +
+ +
+

帮助

+ +
+
+ + + +
+ 正在初始化... +
+ +
+ + +
+ + + +
+ + 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). + +![封面 Cover](/PREVIEW/封面%20Cover.png) + +备注:这里的「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 + +![预览 Preview](/PREVIEW/预览%20Preview.png) + +# 使用前须知 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: + +![二维码 QR code](/PREVIEW/二维码%20QR%20code.png) + +# 教程 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