iobroker.jetframe 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +357 -0
  3. package/admin/SF-Pro.ttf +0 -0
  4. package/admin/admin.d.ts +65 -0
  5. package/admin/frame.html +982 -0
  6. package/admin/frame.html.bak-aircraft-card-real-row-20260518-1608 +1236 -0
  7. package/admin/frame.html.bak-aircraft-card-structure-20260518-1517 +1236 -0
  8. package/admin/frame.html.bak-aircraft-logo-id-fix-20260518-1639 +1239 -0
  9. package/admin/frame.html.bak-shortcut-test +1236 -0
  10. package/admin/frame.html.bak-tablet-class-20260518-1729 +1239 -0
  11. package/admin/heatmap.html +216 -0
  12. package/admin/index.html +268 -0
  13. package/admin/index_m.html +1749 -0
  14. package/admin/jetframe.css +1260 -0
  15. package/admin/jetframe.css.bak-airbus-landscape-fix +4630 -0
  16. package/admin/jetframe.css.bak-aircraft-card-clean-equal-20260518-1438 +4899 -0
  17. package/admin/jetframe.css.bak-aircraft-card-real-row-20260518-1608 +4814 -0
  18. package/admin/jetframe.css.bak-aircraft-card-row-left-20260518-1525 +4604 -0
  19. package/admin/jetframe.css.bak-aircraft-card-slim-equal-20260518-1446 +4647 -0
  20. package/admin/jetframe.css.bak-aircraft-card-structure-20260518-1517 +4646 -0
  21. package/admin/jetframe.css.bak-aircraft-inline-final-20260518-1527 +4654 -0
  22. package/admin/jetframe.css.bak-aircraft-row-compact-fix-20260518-1639 +4763 -0
  23. package/admin/jetframe.css.bak-before-aircrafttype-purge +4818 -0
  24. package/admin/jetframe.css.bak-before-cleanup +4670 -0
  25. package/admin/jetframe.css.bak-before-remove-tablet-only-20260518-1711 +4896 -0
  26. package/admin/jetframe.css.bak-before-tablet-layout-rework-20260518-1650 +4914 -0
  27. package/admin/jetframe.css.bak-clean-duplicate-fonts-20260518-1340 +4975 -0
  28. package/admin/jetframe.css.bak-clean-old-index-fix-20260518-1937 +5167 -0
  29. package/admin/jetframe.css.bak-hardleft-airbus +4751 -0
  30. package/admin/jetframe.css.bak-index-iphone-landscape-20260518-1931 +5030 -0
  31. package/admin/jetframe.css.bak-index-landscape-final-20260518-1941 +5167 -0
  32. package/admin/jetframe.css.bak-index-landscape-real-20260518-1936 +5186 -0
  33. package/admin/jetframe.css.bak-landscape-compact-jumbo-bold-20260518-1343 +4802 -0
  34. package/admin/jetframe.css.bak-logo-align-final +4551 -0
  35. package/admin/jetframe.css.bak-logo-final2 +4551 -0
  36. package/admin/jetframe.css.bak-narrowbody-font-fix +4992 -0
  37. package/admin/jetframe.css.bak-nuke-airbus-align +4790 -0
  38. package/admin/jetframe.css.bak-pill-balance-20260518-1603 +4773 -0
  39. package/admin/jetframe.css.bak-pill-balance-fix +4910 -0
  40. package/admin/jetframe.css.bak-radar-fix-fonts +4710 -0
  41. package/admin/jetframe.css.bak-shortcut-test +4899 -0
  42. package/admin/jetframe.css.bak-smaller-aircraft-card-fonts-20260518-1345 +4897 -0
  43. package/admin/jetframe.css.bak-tablet-fix-real-20260518-1748 +4945 -0
  44. package/admin/jetframe.css.bak-tablet-fullscreen-fix-20260518-1804 +4972 -0
  45. package/admin/jetframe.css.bak-tablet-landscape-layout-20260518-1645 +4802 -0
  46. package/admin/jetframe.css.bak-tablet-layout-final-20260518-1839 +4802 -0
  47. package/admin/jetframe.css.bak-tablet-layout-v3-20260518-1729 +4802 -0
  48. package/admin/jetframe.css.bak-tablet-layout-v4-20260518-1801 +4957 -0
  49. package/admin/jetframe.css.bak-tablet-layout-v5-20260518-1843 +4970 -0
  50. package/admin/jetframe.css.bak-tablet-layout-v6-20260518-1848 +4958 -0
  51. package/admin/jetframe.css.bak-tablet-layout-v7-20260518-1909 +4985 -0
  52. package/admin/jetframe.css.bak-tablet-only-landscape-v2-20260518-1707 +4802 -0
  53. package/admin/jetframe.css.bak-tablet-pages-final-20260519-1857 +5188 -0
  54. package/admin/jetframe.css.bak-tablet-pages-final-20260519-1859 +5347 -0
  55. package/admin/jetframe.css.bak-tablet-pages-v2-20260519-190807 +5349 -0
  56. package/admin/jetframe.css.bak-typography-align-final +4818 -0
  57. package/admin/jetframe.png +0 -0
  58. package/admin/manifest.webmanifest +15 -0
  59. package/admin/src/app.tsx +58 -0
  60. package/admin/src/components/settings.tsx +97 -0
  61. package/admin/src/i18n/de.json +11 -0
  62. package/admin/src/i18n/en.json +11 -0
  63. package/admin/src/i18n/es.json +11 -0
  64. package/admin/src/i18n/fr.json +11 -0
  65. package/admin/src/i18n/i18n.d.ts +28 -0
  66. package/admin/src/i18n/it.json +11 -0
  67. package/admin/src/i18n/nl.json +11 -0
  68. package/admin/src/i18n/pl.json +11 -0
  69. package/admin/src/i18n/pt.json +11 -0
  70. package/admin/src/i18n/ru.json +11 -0
  71. package/admin/src/i18n/uk.json +11 -0
  72. package/admin/src/i18n/zh-cn.json +11 -0
  73. package/admin/src/index.tsx +25 -0
  74. package/admin/stats.html +228 -0
  75. package/admin/style.css +32 -0
  76. package/admin/tsconfig.json +11 -0
  77. package/admin/words.js +46 -0
  78. package/build/lib/adsb.js +218 -0
  79. package/build/lib/adsb.js.map +7 -0
  80. package/build/lib/airportNamesDe.js +131 -0
  81. package/build/lib/airportNamesDe.js.map +7 -0
  82. package/build/lib/airports.js +281 -0
  83. package/build/lib/airports.js.map +7 -0
  84. package/build/lib/classify.js +339 -0
  85. package/build/lib/classify.js.map +7 -0
  86. package/build/lib/config.js +103 -0
  87. package/build/lib/config.js.map +7 -0
  88. package/build/lib/flightInfo.js +1409 -0
  89. package/build/lib/flightInfo.js.map +7 -0
  90. package/build/lib/geo.js +84 -0
  91. package/build/lib/geo.js.map +7 -0
  92. package/build/lib/images.js +422 -0
  93. package/build/lib/images.js.map +7 -0
  94. package/build/lib/specialLiveries.js +342 -0
  95. package/build/lib/specialLiveries.js.map +7 -0
  96. package/build/lib/states.js +971 -0
  97. package/build/lib/states.js.map +7 -0
  98. package/build/lib/staticFiles.js +73 -0
  99. package/build/lib/staticFiles.js.map +7 -0
  100. package/build/lib/types.js +17 -0
  101. package/build/lib/types.js.map +7 -0
  102. package/build/lib/visConfig.js +52 -0
  103. package/build/lib/visConfig.js.map +7 -0
  104. package/build/main.js +1454 -0
  105. package/build/main.js.map +7 -0
  106. package/io-package.json +169 -0
  107. package/package.json +82 -0
@@ -0,0 +1,1260 @@
1
+ /* ════════════════════════════════════════════════════════════════
2
+ JetFrame · Clean Stylesheet · 2026
3
+ ────────────────────────────────────────────────────────────────
4
+ Eine Datei. Eine Media-Query (orientation).
5
+ Alle Größen über clamp() — voll responsiv vom iPhone bis 4K.
6
+ ════════════════════════════════════════════════════════════════ */
7
+
8
+ /* ─── Design Tokens ──────────────────────────────────────────── */
9
+ :root {
10
+ --bg-1: #0d1020;
11
+ --bg-2: #050709;
12
+ --surface: rgba(255,255,255,.075);
13
+ --border: rgba(255,255,255,.12);
14
+ --border-hi: rgba(255,255,255,.18);
15
+ --text: #fff;
16
+ --muted: rgba(255,255,255,.58);
17
+ --green: #32e879;
18
+ --blue: #56a8ff;
19
+ --yellow: #ffd84d;
20
+ --red: #ff4d5e;
21
+ --font: "SF Pro Display", -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;
22
+
23
+ --gap: clamp(8px, 1.4vmin, 18px);
24
+ --pad: clamp(12px, 2vmin, 24px);
25
+ --r-shell: clamp(22px, 3.4vmin, 38px);
26
+ --r-box: clamp(14px, 2.2vmin, 26px);
27
+ --r-pill: 999px;
28
+ }
29
+
30
+ *, *::before, *::after { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
31
+
32
+ html {
33
+ margin: 0; padding: 0;
34
+ width: 100%; height: 100%;
35
+ /* Safari nutzt diese Farbe für den Bereich unter der URL-Leiste */
36
+ background-color: #050709;
37
+ color-scheme: dark;
38
+ }
39
+
40
+ body {
41
+ margin: 0; padding: 0;
42
+ width: 100%; height: 100%;
43
+ overflow: hidden;
44
+ background:
45
+ radial-gradient(ellipse at 18% 0%, rgba(60,110,255,.28), transparent 44%),
46
+ radial-gradient(ellipse at 88% 12%, rgba(255,120,50,.16), transparent 38%),
47
+ linear-gradient(160deg, var(--bg-1) 0%, var(--bg-2) 70%);
48
+ color: var(--text);
49
+ font-family: var(--font);
50
+ -webkit-font-smoothing: antialiased;
51
+ overscroll-behavior: none;
52
+ }
53
+
54
+ button { font: inherit; cursor: pointer; border: none; outline: none; background: none; color: inherit; padding: 0; }
55
+ a { text-decoration: none; color: inherit; }
56
+
57
+ /* ─── App-Wrapper + Shell ───────────────────────────────────── */
58
+ #app {
59
+ position: fixed;
60
+ top: 0; left: 0; right: 0; bottom: 0;
61
+ padding:
62
+ calc(env(safe-area-inset-top) + 10px)
63
+ calc(env(safe-area-inset-right) + 10px)
64
+ calc(env(safe-area-inset-bottom) + 10px)
65
+ calc(env(safe-area-inset-left) + 10px);
66
+ display: flex;
67
+ align-items: center;
68
+ justify-content: center;
69
+ overflow: hidden;
70
+ }
71
+
72
+ .jf-shell {
73
+ width: 100%;
74
+ height: 100%;
75
+ max-width: 1400px;
76
+ border-radius: var(--r-shell);
77
+ padding: var(--pad);
78
+ background: linear-gradient(148deg, rgba(55,62,88,.55), rgba(14,15,20,.82));
79
+ border: 1px solid var(--border);
80
+ box-shadow: 0 28px 100px rgba(0,0,0,.55), inset 0 1px 0 rgba(255,255,255,.16);
81
+ backdrop-filter: blur(24px);
82
+ -webkit-backdrop-filter: blur(24px);
83
+ overflow: hidden;
84
+ position: relative;
85
+ display: flex;
86
+ flex-direction: column;
87
+ gap: var(--gap);
88
+ }
89
+
90
+ /* ─── Header ────────────────────────────────────────────────── */
91
+ .header {
92
+ display: grid;
93
+ grid-template-columns: auto 1fr auto;
94
+ align-items: center;
95
+ gap: var(--gap);
96
+ flex-shrink: 0;
97
+ }
98
+
99
+ .backBtn {
100
+ display: grid; place-items: center;
101
+ width: clamp(34px, 5vmin, 46px);
102
+ height: clamp(34px, 5vmin, 46px);
103
+ border-radius: var(--r-pill);
104
+ background: var(--surface);
105
+ border: 1px solid var(--border);
106
+ font-size: clamp(18px, 3vmin, 26px);
107
+ font-weight: 900;
108
+ color: rgba(255,255,255,.85);
109
+ }
110
+
111
+ .title {
112
+ font-size: clamp(22px, 3.6vmin, 40px);
113
+ font-weight: 950;
114
+ letter-spacing: -.06em;
115
+ line-height: .96;
116
+ white-space: nowrap;
117
+ }
118
+
119
+ .sub {
120
+ font-size: clamp(11px, 1.6vmin, 16px);
121
+ font-weight: 700;
122
+ color: var(--muted);
123
+ margin-top: 2px;
124
+ white-space: nowrap;
125
+ overflow: hidden;
126
+ text-overflow: ellipsis;
127
+ }
128
+
129
+ .livePill {
130
+ display: inline-flex;
131
+ align-items: center;
132
+ gap: .5em;
133
+ padding: clamp(6px,1vmin,10px) clamp(12px,1.9vmin,18px);
134
+ border-radius: var(--r-pill);
135
+ background: rgba(50,232,121,.12);
136
+ border: 1px solid rgba(50,232,121,.30);
137
+ color: rgba(210,255,225,.94);
138
+ font-size: clamp(11px, 1.6vmin, 16px);
139
+ font-weight: 900;
140
+ white-space: nowrap;
141
+ }
142
+
143
+ .livePill .dot {
144
+ width: clamp(7px,1.2vmin,10px);
145
+ height: clamp(7px,1.2vmin,10px);
146
+ border-radius: 50%;
147
+ background: var(--green);
148
+ box-shadow: 0 0 12px rgba(50,232,121,.7);
149
+ animation: pulse 2s ease-in-out infinite;
150
+ flex-shrink: 0;
151
+ }
152
+
153
+ @keyframes pulse {
154
+ 0%,100% { opacity: 1; transform: scale(1); }
155
+ 50% { opacity: .5; transform: scale(.75); }
156
+ }
157
+
158
+ /* ─── API-Warnung ──────────────────────────────────────────── */
159
+ .simpleApiWarning {
160
+ position: fixed;
161
+ left: 50%; bottom: calc(env(safe-area-inset-bottom) + 20px);
162
+ transform: translateX(-50%) translateY(14px);
163
+ z-index: 9999;
164
+ max-width: min(92vw, 540px);
165
+ padding: 12px 18px;
166
+ border-radius: 18px;
167
+ background: rgba(255,77,94,.18);
168
+ border: 1px solid rgba(255,77,94,.36);
169
+ color: rgba(255,232,232,.96);
170
+ box-shadow: 0 18px 48px rgba(0,0,0,.4);
171
+ backdrop-filter: blur(16px);
172
+ font-size: clamp(11px, 1.6vmin, 14px);
173
+ font-weight: 800;
174
+ text-align: center;
175
+ opacity: 0; pointer-events: none;
176
+ transition: opacity .25s, transform .25s;
177
+ }
178
+ .simpleApiWarning.visible { opacity: 1; transform: translateX(-50%) translateY(0); }
179
+
180
+ /* ════════════════════════════════════════════════════════════
181
+ HOME PAGE
182
+ ════════════════════════════════════════════════════════════ */
183
+
184
+ body.jf-page-home .jf-shell {
185
+ max-width: 560px;
186
+ }
187
+
188
+ .hero {
189
+ flex: 0 0 auto;
190
+ display: flex;
191
+ flex-direction: column;
192
+ justify-content: flex-start;
193
+ gap: clamp(4px, .7vmin, 8px);
194
+ padding-top: clamp(8px, 1.5vmin, 20px);
195
+ }
196
+
197
+ .logo { font-size: clamp(38px, 7vmin, 60px); line-height: 1; }
198
+
199
+ h1 {
200
+ margin: 0;
201
+ font-size: clamp(38px, 8vmin, 68px);
202
+ font-weight: 950;
203
+ letter-spacing: -.075em;
204
+ line-height: .9;
205
+ }
206
+
207
+ .tagline {
208
+ font-size: clamp(13px, 2.2vmin, 20px);
209
+ font-weight: 700;
210
+ color: var(--muted);
211
+ line-height: 1.2;
212
+ }
213
+
214
+ .statusPill {
215
+ display: inline-flex;
216
+ align-items: center;
217
+ width: fit-content;
218
+ max-width: 100%;
219
+ padding: clamp(6px,1vmin,10px) clamp(13px,2vmin,18px);
220
+ border-radius: var(--r-pill);
221
+ background: rgba(50,232,121,.12);
222
+ border: 1px solid rgba(50,232,121,.26);
223
+ color: rgba(210,255,225,.94);
224
+ font-size: clamp(11px, 1.6vmin, 15px);
225
+ font-weight: 850;
226
+ white-space: nowrap;
227
+ overflow: hidden;
228
+ text-overflow: ellipsis;
229
+ margin-top: clamp(6px, 1vmin, 12px);
230
+ }
231
+
232
+ .buttons {
233
+ display: flex;
234
+ flex-direction: column;
235
+ gap: clamp(7px, 1.2vmin, 12px);
236
+ flex-shrink: 0;
237
+ }
238
+
239
+ .btn, .powerBtn {
240
+ border-radius: var(--r-box);
241
+ padding: clamp(12px, 2vmin, 18px) clamp(14px, 2.4vmin, 22px);
242
+ background: var(--surface);
243
+ border: 1px solid var(--border);
244
+ display: flex;
245
+ align-items: center;
246
+ justify-content: space-between;
247
+ gap: 12px;
248
+ transition: background .15s, transform .1s;
249
+ box-shadow: inset 0 1px 0 rgba(255,255,255,.10);
250
+ }
251
+ .btn:active, .powerBtn:active { transform: scale(.98); background: rgba(255,255,255,.11); }
252
+
253
+ .btn.primary { background: linear-gradient(145deg, rgba(86,168,255,.36), rgba(50,232,121,.20)); border-color: rgba(86,168,255,.30); }
254
+ .btn.heat { background: linear-gradient(145deg, rgba(255,216,77,.28), rgba(255,77,94,.16)); border-color: rgba(255,216,77,.26); }
255
+
256
+ .powerBtn { background: linear-gradient(135deg, rgba(50,232,121,.16), rgba(255,255,255,.05)); border-color: rgba(50,232,121,.26); }
257
+ .powerBtn.off { background: linear-gradient(135deg, rgba(255,77,94,.16), rgba(255,255,255,.05)); border-color: rgba(255,77,94,.30); }
258
+
259
+ .btnTitle { font-size: clamp(17px, 2.8vmin, 26px); font-weight: 920; letter-spacing: -.03em; line-height: 1.1; }
260
+ .btnSub { font-size: clamp(11px, 1.6vmin, 15px); color: var(--muted); font-weight: 700; margin-top: 2px; }
261
+ .arrow { font-size: clamp(19px, 3vmin, 26px); font-weight: 700; color: rgba(255,255,255,.75); flex-shrink: 0; }
262
+
263
+ .powerBadge {
264
+ flex-shrink: 0;
265
+ padding: clamp(5px,.9vmin,8px) clamp(11px,1.8vmin,16px);
266
+ border-radius: var(--r-pill);
267
+ background: rgba(50,232,121,.18);
268
+ border: 1px solid rgba(50,232,121,.34);
269
+ font-size: clamp(11px, 1.7vmin, 15px);
270
+ font-weight: 950;
271
+ color: #e0fff0;
272
+ letter-spacing: .04em;
273
+ }
274
+ .powerBtn.off .powerBadge { background: rgba(255,77,94,.18); border-color: rgba(255,77,94,.36); color: #ffd6da; }
275
+
276
+ .statsRow {
277
+ display: grid;
278
+ grid-template-columns: repeat(3, 1fr);
279
+ gap: var(--gap);
280
+ flex-shrink: 0;
281
+ }
282
+
283
+ .statCard {
284
+ border-radius: var(--r-box);
285
+ background: var(--surface);
286
+ border: 1px solid var(--border);
287
+ display: flex;
288
+ flex-direction: column;
289
+ align-items: center;
290
+ justify-content: center;
291
+ padding: clamp(10px,1.5vmin,16px) 6px;
292
+ gap: 4px;
293
+ min-height: clamp(56px, 8vmin, 78px);
294
+ }
295
+ .statVal { font-size: clamp(22px, 4.4vmin, 38px); font-weight: 950; letter-spacing: -.05em; line-height: .9; }
296
+ .statLabel { font-size: clamp(8px, 1.1vmin, 11px); letter-spacing: .18em; color: var(--muted); font-weight: 850; text-transform: uppercase; }
297
+
298
+ /* ════════════════════════════════════════════════════════════
299
+ FRAME PAGE (Live)
300
+ ════════════════════════════════════════════════════════════ */
301
+
302
+ body.jf-page-frame .jf-shell {
303
+ display: grid;
304
+ grid-template-rows: auto 1fr;
305
+ overflow: hidden;
306
+ }
307
+
308
+ .frameHeaderActions {
309
+ display: flex;
310
+ align-items: center;
311
+ gap: clamp(7px, 1.2vmin, 12px);
312
+ }
313
+
314
+ .jfHiddenToggle { display: none !important; }
315
+
316
+ .speechBtn {
317
+ width: clamp(34px, 5vmin, 46px);
318
+ height: clamp(34px, 5vmin, 46px);
319
+ border-radius: var(--r-pill);
320
+ background: var(--surface);
321
+ border: 1px solid var(--border);
322
+ display: grid;
323
+ place-items: center;
324
+ font-size: clamp(16px, 2.4vmin, 22px);
325
+ flex-shrink: 0;
326
+ transition: background .15s;
327
+ }
328
+ .speechBtn.on { background: rgba(50,232,121,.14); border-color: rgba(50,232,121,.30); }
329
+
330
+ .jfFrameMain {
331
+ position: relative;
332
+ min-height: 0;
333
+ overflow: hidden;
334
+ display: grid;
335
+ gap: var(--gap);
336
+ grid-template-areas:
337
+ "photo"
338
+ "info"
339
+ "metrics";
340
+ grid-template-rows: auto minmax(0,1fr) auto;
341
+ }
342
+
343
+ /* ── Foto ── */
344
+ .photoWrap {
345
+ grid-area: photo;
346
+ position: relative;
347
+ border-radius: var(--r-box);
348
+ border: 1px solid var(--border);
349
+ background: var(--surface);
350
+ overflow: hidden;
351
+ aspect-ratio: 16 / 10;
352
+ width: 100%;
353
+ }
354
+
355
+ #planePhoto {
356
+ position: absolute;
357
+ inset: 0;
358
+ width: 100%;
359
+ height: 100%;
360
+ object-fit: cover;
361
+ display: none;
362
+ transition: opacity .4s ease;
363
+ }
364
+
365
+ .placeholder {
366
+ position: absolute; inset: 0;
367
+ display: grid;
368
+ place-items: center;
369
+ overflow: hidden;
370
+ }
371
+
372
+ .radarCircle {
373
+ position: absolute;
374
+ width: min(46vmin, 320px);
375
+ aspect-ratio: 1;
376
+ border-radius: 50%;
377
+ border: 1px solid rgba(255,255,255,.13);
378
+ }
379
+
380
+ .radarSweep {
381
+ position: absolute;
382
+ width: min(46vmin, 320px);
383
+ aspect-ratio: 1;
384
+ border-radius: 50%;
385
+ background: conic-gradient(from 0deg, rgba(50,232,121,.32), transparent 20%);
386
+ animation: radarSpin 2.6s linear infinite;
387
+ opacity: .42;
388
+ }
389
+
390
+ .idlePlane {
391
+ position: relative; z-index: 3;
392
+ font-size: clamp(40px, 7vmin, 76px);
393
+ filter: drop-shadow(0 10px 24px rgba(0,0,0,.35));
394
+ animation: idleFloat 3.2s ease-in-out infinite;
395
+ }
396
+
397
+ .idleFlyers { position: absolute; inset: 0; pointer-events: none; overflow: hidden; z-index: 2; }
398
+ .idleFlyers span {
399
+ position: absolute;
400
+ left: -14%;
401
+ font-size: clamp(16px, 3vmin, 28px);
402
+ opacity: 0;
403
+ animation: idleFly 5.8s ease-in-out infinite;
404
+ }
405
+ .idleFlyers span:nth-child(1) { top: 32%; animation-delay: .2s; }
406
+ .idleFlyers span:nth-child(2) { top: 56%; animation-delay: 1.8s; }
407
+ .idleFlyers span:nth-child(3) { top: 44%; animation-delay: 3.5s; }
408
+
409
+ .idleText {
410
+ position: absolute;
411
+ left: 50%; bottom: clamp(12px, 2vmin, 24px);
412
+ transform: translateX(-50%);
413
+ font-size: clamp(13px, 2vmin, 18px);
414
+ font-weight: 850;
415
+ color: rgba(255,255,255,.68);
416
+ white-space: nowrap;
417
+ z-index: 3;
418
+ }
419
+
420
+ @keyframes radarSpin { to { transform: rotate(360deg); } }
421
+ @keyframes idleFloat {
422
+ 0%,100% { transform: translateY(0) rotate(-8deg); }
423
+ 50% { transform: translateY(-10px) rotate(-2deg); }
424
+ }
425
+ @keyframes idleFly {
426
+ 0% { transform: translateX(0) translateY(16px) rotate(8deg); opacity: 0; }
427
+ 12% { opacity: .82; }
428
+ 84% { opacity: .82; }
429
+ 100% { transform: translateX(120vw) translateY(-34px) rotate(8deg); opacity: 0; }
430
+ }
431
+
432
+ .frameIdleRunwaySlot {
433
+ display: none;
434
+ grid-area: info;
435
+ align-items: center;
436
+ justify-content: center;
437
+ }
438
+ body.jf-page-frame.jf-no-flight .frameIdleRunwaySlot { display: flex; }
439
+
440
+ .idleRunwayText {
441
+ display: none;
442
+ padding: clamp(8px,1.2vmin,12px) clamp(16px,2vmin,22px);
443
+ border-radius: var(--r-pill);
444
+ background: rgba(50,232,121,.14);
445
+ border: 1px solid rgba(50,232,121,.32);
446
+ color: #dffff0;
447
+ font-size: clamp(13px, 2vmin, 18px);
448
+ font-weight: 900;
449
+ white-space: nowrap;
450
+ text-align: center;
451
+ }
452
+ .idleRunwayText.visible { display: inline-flex; }
453
+
454
+ .info {
455
+ grid-area: info;
456
+ min-height: 0;
457
+ overflow: hidden;
458
+ display: flex;
459
+ flex-direction: column;
460
+ align-items: flex-start;
461
+ gap: clamp(4px, .7vmin, 10px);
462
+ }
463
+
464
+ .mode {
465
+ font-size: clamp(18px, 2.8vmin, 32px);
466
+ font-weight: 900;
467
+ line-height: 1;
468
+ }
469
+
470
+ .flightInfoPillRow {
471
+ display: flex;
472
+ flex-wrap: wrap;
473
+ align-items: center;
474
+ gap: clamp(6px, 1vmin, 12px);
475
+ }
476
+
477
+ .windowInfo, .runwayInfo {
478
+ display: none;
479
+ align-items: center;
480
+ gap: 7px;
481
+ border-radius: var(--r-pill);
482
+ padding: clamp(5px,.9vmin,9px) clamp(11px,1.6vmin,18px);
483
+ font-size: clamp(12px, 1.8vmin, 18px);
484
+ font-weight: 900;
485
+ white-space: nowrap;
486
+ border: 1px solid var(--border-hi);
487
+ background: var(--surface);
488
+ }
489
+ .windowInfo.visible, .runwayInfo.visible { display: inline-flex; }
490
+ .windowInfo {
491
+ border-color: rgba(255,231,122,.36);
492
+ background: rgba(255,231,122,.13);
493
+ color: #fff0a2;
494
+ }
495
+
496
+ .callsigns { display: flex; flex-direction: column; }
497
+
498
+ .iataCallsign {
499
+ font-size: clamp(34px, 5.6vmin, 64px);
500
+ font-weight: 950;
501
+ letter-spacing: -.07em;
502
+ line-height: .9;
503
+ white-space: nowrap;
504
+ }
505
+
506
+ .operatorCallsign, .routeCodes, .reg {
507
+ font-size: clamp(13px, 2vmin, 22px);
508
+ font-weight: 700;
509
+ color: var(--muted);
510
+ letter-spacing: -.02em;
511
+ }
512
+
513
+ .routeCities {
514
+ font-size: clamp(24px, 4.2vmin, 50px);
515
+ font-weight: 950;
516
+ letter-spacing: -.06em;
517
+ line-height: .96;
518
+ white-space: nowrap;
519
+ max-width: 100%;
520
+ overflow: hidden;
521
+ text-overflow: ellipsis;
522
+ }
523
+
524
+ /* ── Aircraft-Card ── */
525
+ .aircraftCard {
526
+ display: flex;
527
+ flex-direction: column;
528
+ border-radius: clamp(16px, 2.4vmin, 26px);
529
+ border: 1px solid var(--border-hi);
530
+ background: var(--surface);
531
+ padding: clamp(8px, 1.3vmin, 14px) clamp(12px, 1.7vmin, 18px);
532
+ width: min(560px, 100%);
533
+ }
534
+
535
+ .acRow {
536
+ display: flex;
537
+ align-items: center;
538
+ gap: clamp(8px, 1.3vmin, 14px);
539
+ padding: clamp(5px, .8vmin, 8px) 0;
540
+ min-width: 0;
541
+ }
542
+
543
+ .aircraftSep {
544
+ width: 100%;
545
+ height: 1px;
546
+ background: rgba(255,255,255,.13);
547
+ }
548
+
549
+ .acLogo {
550
+ width: clamp(34px, 4.6vmin, 50px);
551
+ height: clamp(34px, 4.6vmin, 50px);
552
+ border-radius: 50%;
553
+ background: #fff;
554
+ display: grid;
555
+ place-items: center;
556
+ flex-shrink: 0;
557
+ overflow: hidden;
558
+ box-shadow: 0 2px 6px rgba(0,0,0,.18);
559
+ }
560
+
561
+ #airlineLogoImg,
562
+ #manufacturerLogoImg {
563
+ width: 78%;
564
+ height: 78%;
565
+ object-fit: contain;
566
+ }
567
+
568
+ #manufacturerLogoText {
569
+ width: 100%;
570
+ height: 100%;
571
+ display: grid;
572
+ place-items: center;
573
+ font-size: clamp(15px, 2.2vmin, 22px);
574
+ color: #06080f;
575
+ font-weight: 900;
576
+ }
577
+
578
+ .airline, #aircraftTypeText {
579
+ font-size: clamp(13px, 1.9vmin, 20px);
580
+ font-weight: 800;
581
+ line-height: 1.1;
582
+ }
583
+
584
+ #aircraftSize {
585
+ color: var(--yellow);
586
+ font-size: clamp(11px, 1.7vmin, 18px);
587
+ font-weight: 850;
588
+ margin-left: auto; /* immer rechts in der Zeile */
589
+ flex-shrink: 0;
590
+ }
591
+
592
+ .reg { font-size: clamp(12px, 1.8vmin, 18px); }
593
+
594
+ .special {
595
+ display: none;
596
+ padding: clamp(5px,.9vmin,9px) clamp(11px,1.6vmin,18px);
597
+ border-radius: var(--r-pill);
598
+ border: 1px solid rgba(255,231,122,.34);
599
+ background: rgba(255,231,122,.13);
600
+ color: #fff0a2;
601
+ font-size: clamp(12px, 1.8vmin, 18px);
602
+ font-weight: 900;
603
+ white-space: nowrap;
604
+ }
605
+ .special:not(:empty) { display: inline-flex; align-items: center; }
606
+ .special.emergency { border-color: rgba(255,77,94,.42); background: rgba(255,77,94,.16); color: #ffd2d7; }
607
+
608
+ /* ── Metrics ── */
609
+ .metrics {
610
+ grid-area: metrics;
611
+ display: grid;
612
+ grid-template-columns: repeat(4, 1fr);
613
+ gap: var(--gap);
614
+ }
615
+
616
+ .metric {
617
+ border-radius: var(--r-box);
618
+ background: var(--surface);
619
+ border: 1px solid var(--border);
620
+ display: flex;
621
+ flex-direction: column;
622
+ align-items: center;
623
+ justify-content: center;
624
+ text-align: center;
625
+ padding: clamp(6px, 1vmin, 12px);
626
+ min-height: clamp(54px, 7.5vmin, 84px);
627
+ }
628
+
629
+ .metricValue {
630
+ font-size: clamp(22px, 3.8vmin, 42px);
631
+ font-weight: 950;
632
+ letter-spacing: -.06em;
633
+ line-height: .9;
634
+ }
635
+
636
+ .metricLabel {
637
+ font-size: clamp(8px, 1vmin, 11px);
638
+ letter-spacing: .22em;
639
+ color: var(--muted);
640
+ font-weight: 850;
641
+ margin-top: clamp(3px, .5vmin, 5px);
642
+ text-transform: uppercase;
643
+ }
644
+
645
+ body.jf-page-frame.jf-no-flight .info,
646
+ body.jf-page-frame.jf-no-flight .metrics { display: none !important; }
647
+
648
+ /* No-Flight: Grid nur 2 Zeilen — Photo + Runway-Pille */
649
+ body.jf-page-frame.jf-no-flight .jfFrameMain {
650
+ grid-template-rows: auto auto;
651
+ grid-template-areas:
652
+ "photo"
653
+ "info";
654
+ align-content: start;
655
+ }
656
+ body.jf-page-frame.jf-no-flight .frameIdleRunwaySlot {
657
+ display: flex;
658
+ justify-content: center;
659
+ padding: clamp(8px,1.2vmin,16px) 0;
660
+ }
661
+
662
+ /* ── Preload Skeleton ── */
663
+ .jfPreloadOverlay {
664
+ /* Absolut innerhalb jfFrameMain — deckt exakt denselben Bereich ab */
665
+ position: absolute;
666
+ inset: 0;
667
+ z-index: 30;
668
+ pointer-events: none;
669
+ display: grid;
670
+ gap: var(--gap);
671
+ grid-template-rows: auto minmax(0,1fr) auto;
672
+ grid-template-areas:
673
+ "prePhoto"
674
+ "preInfo"
675
+ "preMetrics";
676
+ opacity: 0;
677
+ visibility: hidden;
678
+ transition: opacity .22s, visibility .22s;
679
+ }
680
+
681
+ .jfPreloadOverlay > * { min-height: 0; }
682
+
683
+ body.jf-page-frame.jf-preload .jfPreloadOverlay { opacity: 1; visibility: visible; }
684
+
685
+ body.jf-page-frame.jf-preload .photoWrap > *,
686
+ body.jf-page-frame.jf-preload .info > *,
687
+ body.jf-page-frame.jf-preload .metrics > * { opacity: 0 !important; }
688
+
689
+ .prePhoto {
690
+ grid-area: prePhoto;
691
+ border-radius: var(--r-box);
692
+ border: 1px solid var(--border);
693
+ background: var(--surface);
694
+ aspect-ratio: 16 / 10;
695
+ width: 100%;
696
+ }
697
+
698
+ .preInfo {
699
+ grid-area: preInfo;
700
+ display: flex;
701
+ flex-direction: column;
702
+ align-items: flex-start;
703
+ gap: clamp(6px, 1vmin, 10px);
704
+ overflow: hidden;
705
+ }
706
+
707
+ .preMetrics {
708
+ grid-area: preMetrics;
709
+ display: grid;
710
+ grid-template-columns: repeat(4, 1fr);
711
+ gap: var(--gap);
712
+ min-height: clamp(54px, 7.5vmin, 84px);
713
+ }
714
+
715
+ .sk {
716
+ display: block;
717
+ border-radius: 999px;
718
+ background: linear-gradient(110deg,
719
+ rgba(255,255,255,.08), rgba(255,255,255,.28), rgba(255,255,255,.08));
720
+ background-size: 240% 100%;
721
+ animation: shimmer 1.15s ease-in-out infinite;
722
+ }
723
+ @keyframes shimmer {
724
+ 0% { background-position: 120% 0; opacity: .55; }
725
+ 50% { opacity: 1; }
726
+ 100% { background-position: -120% 0; opacity: .55; }
727
+ }
728
+
729
+ .skMode { width: 42%; height: clamp(20px, 2.8vmin, 32px); }
730
+ .prePillRow { display: flex; gap: clamp(8px,1.2vmin,14px); }
731
+ .skPillA { width: clamp(160px, 22vmin, 260px); height: clamp(26px, 3.6vmin, 42px); }
732
+ .skPillB { width: clamp(100px, 14vmin, 160px); height: clamp(26px, 3.6vmin, 42px); }
733
+ .skCall { width: 40%; height: clamp(34px, 5.6vmin, 64px); }
734
+ .skSmall { width: 28%; height: clamp(13px, 2vmin, 22px); }
735
+ .skRoute { width: 80%; height: clamp(24px, 4.2vmin, 50px); }
736
+ .skCodes { width: 32%; height: clamp(13px, 2vmin, 22px); }
737
+ .skReg { width: 42%; height: clamp(12px, 1.8vmin, 18px); }
738
+ .skAirline { width: 55%; height: clamp(13px, 1.9vmin, 20px); }
739
+ .skType { width: 70%; height: clamp(13px, 1.9vmin, 20px); }
740
+ .skSize { width: clamp(70px,10vmin,120px); height: clamp(13px, 1.9vmin, 20px); }
741
+
742
+ .preCard {
743
+ width: min(560px, 100%);
744
+ border-radius: clamp(16px,2.4vmin,26px);
745
+ border: 1px solid var(--border-hi);
746
+ background: var(--surface);
747
+ padding: clamp(8px,1.3vmin,14px) clamp(12px,1.7vmin,18px);
748
+ display: grid;
749
+ gap: clamp(6px, 1vmin, 10px);
750
+ }
751
+
752
+ .preCardRow {
753
+ display: grid;
754
+ grid-template-columns: clamp(34px,4.6vmin,50px) 1fr auto;
755
+ align-items: center;
756
+ gap: clamp(8px,1.3vmin,14px);
757
+ }
758
+
759
+ .preLogo {
760
+ width: clamp(34px,4.6vmin,50px);
761
+ height: clamp(34px,4.6vmin,50px);
762
+ border-radius: 50%;
763
+ background: rgba(255,255,255,.18);
764
+ }
765
+
766
+ .preDivider { height: 1px; background: rgba(255,255,255,.13); }
767
+
768
+ .preMetric {
769
+ border-radius: var(--r-box);
770
+ border: 1px solid var(--border);
771
+ background: var(--surface);
772
+ display: grid;
773
+ place-items: center;
774
+ }
775
+ .preMetric .sk { width: 50%; height: clamp(11px, 1.6vmin, 18px); }
776
+
777
+ body.jf-page-frame.jf-preload:not(.jf-no-flight) .placeholder {
778
+ opacity: 0; visibility: hidden; pointer-events: none;
779
+ transition: opacity .22s, visibility .22s;
780
+ }
781
+ body.jf-page-frame.jf-preload:not(.jf-no-flight) #planePhoto {
782
+ display: block;
783
+ opacity: .22;
784
+ filter: blur(3px) saturate(.9);
785
+ transform: scale(1.01);
786
+ transition: opacity .3s, filter .36s, transform .36s;
787
+ }
788
+ body.jf-page-frame.jf-loaded #planePhoto { opacity: 1; filter: none; transform: none; }
789
+
790
+ /* ── Flight-Switch Animation ── */
791
+ .jfFlightSwitch {
792
+ position: absolute;
793
+ inset: 0;
794
+ z-index: 40;
795
+ display: grid;
796
+ place-items: center;
797
+ background: rgba(5,6,10,.72);
798
+ backdrop-filter: blur(14px);
799
+ border-radius: var(--r-box);
800
+ opacity: 0;
801
+ pointer-events: none;
802
+ transition: opacity .28s;
803
+ }
804
+
805
+ body.jf-page-frame.jf-flight-switch .jfFlightSwitch {
806
+ opacity: 1;
807
+ animation: switchPop .28s cubic-bezier(.22,.8,.34,1.2) forwards;
808
+ }
809
+ @keyframes switchPop {
810
+ 0% { opacity: 0; transform: scale(.96); }
811
+ 100% { opacity: 1; transform: scale(1); }
812
+ }
813
+
814
+ .jfSwitchText { text-align: center; }
815
+ .jfSwitchLabel { font-size: clamp(14px, 2.4vmin, 22px); font-weight: 850; color: var(--muted); }
816
+ .jfSwitchRoute { font-size: clamp(28px, 5vmin, 56px); font-weight: 950; letter-spacing: -.05em; margin-top: 6px; }
817
+
818
+ .jfSwitchRadar {
819
+ position: absolute;
820
+ width: min(60vmin, 400px);
821
+ aspect-ratio: 1;
822
+ border-radius: 50%;
823
+ border: 1px solid rgba(50,232,121,.22);
824
+ }
825
+
826
+ .jfSwitchPlane {
827
+ position: absolute;
828
+ font-size: clamp(20px, 3.4vmin, 40px);
829
+ opacity: 0;
830
+ animation: switchFly 1.75s ease-in-out infinite;
831
+ }
832
+ .jfSwitchPlaneA { top: 28%; left: -8%; animation-delay: 0s; }
833
+ .jfSwitchPlaneB { top: 52%; left: -8%; animation-delay: .32s; }
834
+ .jfSwitchPlaneC { top: 40%; left: -8%; animation-delay: .64s; }
835
+ .jfSwitchPlaneD { top: 64%; left: -8%; animation-delay: .96s; }
836
+ @keyframes switchFly {
837
+ 0% { transform: translateX(0) rotate(6deg); opacity: 0; }
838
+ 14% { opacity: .85; }
839
+ 86% { opacity: .85; }
840
+ 100% { transform: translateX(120vw) rotate(6deg); opacity: 0; }
841
+ }
842
+
843
+ .flyover {
844
+ position: fixed;
845
+ inset: 0;
846
+ z-index: 9998;
847
+ pointer-events: none;
848
+ background: radial-gradient(ellipse at 50% 40%, rgba(50,232,121,.18), transparent 62%);
849
+ opacity: 0;
850
+ }
851
+ .flyover.active {
852
+ animation: flyover 3.4s cubic-bezier(.22,.8,.34,1) forwards;
853
+ }
854
+ @keyframes flyover {
855
+ 0% { opacity: 0; transform: scale(.8); }
856
+ 18% { opacity: 1; }
857
+ 72% { opacity: .5; }
858
+ 100% { opacity: 0; transform: scale(1.18); }
859
+ }
860
+
861
+ /* ════════════════════════════════════════════════════════════
862
+ HEATMAP PAGE
863
+ ════════════════════════════════════════════════════════════ */
864
+
865
+ body.jf-page-heatmap .jf-shell {
866
+ display: grid;
867
+ grid-template-rows: auto auto auto minmax(0,1fr) auto;
868
+ }
869
+
870
+ .hmStats {
871
+ display: grid;
872
+ grid-template-columns: repeat(4, 1fr);
873
+ gap: var(--gap);
874
+ }
875
+
876
+ .hmStat {
877
+ border-radius: var(--r-box);
878
+ background: var(--surface);
879
+ border: 1px solid var(--border);
880
+ display: flex;
881
+ flex-direction: column;
882
+ align-items: center;
883
+ justify-content: center;
884
+ padding: clamp(8px,1.3vmin,14px) 6px;
885
+ min-height: clamp(52px, 7.5vmin, 78px);
886
+ }
887
+ .hmVal { font-size: clamp(20px, 4.4vmin, 38px); font-weight: 950; letter-spacing: -.05em; line-height: .9; }
888
+ .hmLabel { font-size: clamp(8px, 1.1vmin, 11px); letter-spacing: .16em; color: var(--muted); font-weight: 850; text-transform: uppercase; margin-top: 4px; white-space: nowrap; }
889
+
890
+ .rush {
891
+ display: flex;
892
+ align-items: center;
893
+ justify-content: space-between;
894
+ gap: clamp(8px,1.4vmin,16px);
895
+ padding: clamp(7px,1.1vmin,11px) clamp(14px,2vmin,22px);
896
+ border-radius: var(--r-box);
897
+ background: rgba(255,216,77,.09);
898
+ border: 1px solid rgba(255,216,77,.22);
899
+ color: rgba(255,240,170,.94);
900
+ font-size: clamp(12px, 1.8vmin, 18px);
901
+ font-weight: 900;
902
+ min-height: clamp(36px, 5vmin, 50px);
903
+ }
904
+ .rush span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
905
+ .rush.hot { background: rgba(255,77,94,.12); border-color: rgba(255,77,94,.28); color: rgba(255,220,224,.95); }
906
+
907
+ .scroller {
908
+ display: flex;
909
+ align-items: stretch;
910
+ gap: clamp(10px, 1.5vmin, 16px);
911
+ overflow-x: auto;
912
+ overflow-y: hidden;
913
+ scroll-snap-type: x mandatory;
914
+ -webkit-overflow-scrolling: touch;
915
+ min-height: 0;
916
+ height: 100%;
917
+ padding-bottom: 4px;
918
+ }
919
+ .scroller::-webkit-scrollbar { display: none; }
920
+
921
+ .hourCard {
922
+ scroll-snap-align: center;
923
+ flex: 0 0 min(380px, 75vw);
924
+ height: 100%;
925
+ border-radius: clamp(18px, 3vmin, 30px);
926
+ padding: clamp(13px, 2vmin, 20px);
927
+ background: var(--surface);
928
+ border: 1px solid var(--border);
929
+ position: relative;
930
+ overflow: hidden;
931
+ display: flex;
932
+ }
933
+
934
+ .hourCard::before {
935
+ content: "";
936
+ position: absolute; inset: 0;
937
+ opacity: var(--heat, .07);
938
+ background:
939
+ radial-gradient(circle at 50% 0%, rgba(50,232,121,.88), transparent 60%),
940
+ linear-gradient(145deg, rgba(86,168,255,.7), rgba(255,216,77,.6));
941
+ pointer-events: none;
942
+ }
943
+
944
+ .hourCard.now { border-color: rgba(50,232,121,.38); box-shadow: 0 0 24px rgba(50,232,121,.12); }
945
+ .hourCard.best { border-color: rgba(255,216,77,.36); }
946
+
947
+ .hourInner { position: relative; z-index: 2; width: 100%; display: flex; flex-direction: column; }
948
+
949
+ .hourTop {
950
+ display: flex;
951
+ justify-content: space-between;
952
+ align-items: center;
953
+ gap: 10px;
954
+ }
955
+
956
+ .hour {
957
+ font-size: clamp(30px, 6.8vmin, 60px);
958
+ font-weight: 950;
959
+ letter-spacing: -.05em;
960
+ line-height: .92;
961
+ }
962
+
963
+ .badge {
964
+ padding: clamp(4px,.7vmin,7px) clamp(8px,1.2vmin,12px);
965
+ border-radius: var(--r-pill);
966
+ background: rgba(0,0,0,.22);
967
+ border: 1px solid rgba(255,255,255,.13);
968
+ color: rgba(255,255,255,.76);
969
+ font-size: clamp(9px, 1.4vmin, 13px);
970
+ font-weight: 850;
971
+ white-space: nowrap;
972
+ }
973
+
974
+ .hourTotal {
975
+ font-size: clamp(60px, 14vmin, 120px);
976
+ line-height: .82;
977
+ font-weight: 950;
978
+ letter-spacing: -.08em;
979
+ margin-top: auto;
980
+ }
981
+
982
+ .hourSub {
983
+ font-size: clamp(13px, 2.4vmin, 22px);
984
+ font-weight: 800;
985
+ color: var(--muted);
986
+ margin-top: 6px;
987
+ }
988
+
989
+ .bars {
990
+ display: grid;
991
+ grid-template-columns: repeat(3, 1fr);
992
+ gap: clamp(6px,1vmin,10px);
993
+ margin-top: auto;
994
+ }
995
+
996
+ .bar {
997
+ height: clamp(32px, 5vmin, 48px);
998
+ border-radius: clamp(11px, 1.6vmin, 18px);
999
+ background: rgba(255,255,255,.10);
1000
+ border: 1px solid rgba(255,255,255,.12);
1001
+ display: flex;
1002
+ align-items: center;
1003
+ justify-content: center;
1004
+ font-size: clamp(11px, 1.8vmin, 16px);
1005
+ font-weight: 900;
1006
+ }
1007
+
1008
+ .rankings {
1009
+ display: grid;
1010
+ grid-template-columns: 1fr 1fr;
1011
+ gap: var(--gap);
1012
+ min-height: 0;
1013
+ }
1014
+
1015
+ .panel {
1016
+ border-radius: var(--r-box);
1017
+ background: var(--surface);
1018
+ border: 1px solid var(--border);
1019
+ padding: clamp(10px,1.5vmin,16px) clamp(12px,1.7vmin,18px);
1020
+ overflow: hidden;
1021
+ min-height: 0;
1022
+ }
1023
+
1024
+ .panel h3 {
1025
+ margin: 0 0 clamp(5px, .8vmin, 8px);
1026
+ font-size: clamp(10px, 1.4vmin, 13px);
1027
+ letter-spacing: .14em;
1028
+ color: var(--muted);
1029
+ text-transform: uppercase;
1030
+ font-weight: 850;
1031
+ }
1032
+
1033
+ .panel pre {
1034
+ margin: 0;
1035
+ font-family: var(--font);
1036
+ font-size: clamp(11px, 1.7vmin, 15px);
1037
+ line-height: 1.32;
1038
+ font-weight: 800;
1039
+ color: rgba(255,255,255,.9);
1040
+ white-space: pre;
1041
+ overflow: hidden;
1042
+ }
1043
+
1044
+ .panel pre.hm-list {
1045
+ display: flex;
1046
+ flex-direction: column;
1047
+ gap: clamp(2px,.4vmin,4px);
1048
+ white-space: normal;
1049
+ }
1050
+ .panel pre.hm-list .hm-line {
1051
+ display: block;
1052
+ width: 100%;
1053
+ white-space: nowrap;
1054
+ overflow: hidden;
1055
+ text-overflow: ellipsis;
1056
+ }
1057
+
1058
+ /* ════════════════════════════════════════════════════════════
1059
+ STATS PAGE
1060
+ ════════════════════════════════════════════════════════════ */
1061
+
1062
+ body.jf-page-stats .jf-shell {
1063
+ display: grid;
1064
+ grid-template-rows: auto auto minmax(0,1fr);
1065
+ }
1066
+
1067
+ body.jf-page-stats .grid {
1068
+ display: grid;
1069
+ grid-template-columns: repeat(3, 1fr);
1070
+ gap: var(--gap);
1071
+ }
1072
+
1073
+ body.jf-page-stats .stat {
1074
+ border-radius: var(--r-box);
1075
+ background: var(--surface);
1076
+ border: 1px solid var(--border);
1077
+ padding: clamp(10px,1.5vmin,16px) clamp(12px,1.7vmin,18px);
1078
+ display: flex;
1079
+ flex-direction: column;
1080
+ justify-content: center;
1081
+ min-height: clamp(60px, 8.5vmin, 92px);
1082
+ overflow: hidden;
1083
+ }
1084
+ body.jf-page-stats .stat.big { background: linear-gradient(145deg, rgba(86,168,255,.20), rgba(50,232,121,.12)); border-color: rgba(86,168,255,.22); }
1085
+ body.jf-page-stats .stat.warn { background: linear-gradient(145deg, rgba(255,216,77,.20), rgba(255,77,94,.10)); border-color: rgba(255,216,77,.24); }
1086
+
1087
+ .stLabel { font-size: clamp(8px, 1vmin, 12px); letter-spacing: .16em; color: var(--muted); font-weight: 850; text-transform: uppercase; }
1088
+ .stVal { font-size: clamp(24px, 4.2vmin, 46px); font-weight: 950; letter-spacing: -.05em; line-height: .9; margin-top: clamp(3px,.5vmin,7px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1089
+ .stSub { font-size: clamp(10px, 1.4vmin, 13px); font-weight: 700; color: var(--muted); margin-top: clamp(3px,.5vmin,5px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1090
+
1091
+ body.jf-page-stats .panels {
1092
+ display: grid;
1093
+ grid-template-columns: 1fr 1fr;
1094
+ grid-template-rows: 1fr 1fr;
1095
+ gap: var(--gap);
1096
+ min-height: 0;
1097
+ }
1098
+
1099
+ body.jf-page-stats .panel pre {
1100
+ font-size: clamp(10px, 1.4vmin, 14px);
1101
+ line-height: 1.22;
1102
+ font-weight: 760;
1103
+ }
1104
+
1105
+ .panel.jf-split-panel pre {
1106
+ display: grid;
1107
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1108
+ column-gap: clamp(12px, 1.8vmin, 22px);
1109
+ row-gap: 2px;
1110
+ white-space: normal;
1111
+ }
1112
+ .panel.jf-split-panel pre .jf-line {
1113
+ display: block;
1114
+ min-width: 0;
1115
+ white-space: nowrap;
1116
+ overflow: hidden;
1117
+ text-overflow: ellipsis;
1118
+ }
1119
+
1120
+ /* ════════════════════════════════════════════════════════════
1121
+ LANDSCAPE LAYOUT
1122
+ Eine Media-Query — gilt für alle Geräte im Querformat
1123
+ ════════════════════════════════════════════════════════════ */
1124
+
1125
+ @media (orientation: landscape) {
1126
+ /* HOME */
1127
+ body.jf-page-home .jf-shell {
1128
+ max-width: 1100px;
1129
+ display: grid;
1130
+ grid-template-columns: 42% 1fr;
1131
+ grid-template-rows: 1fr auto;
1132
+ gap: var(--gap) clamp(14px, 2.4vmin, 24px);
1133
+ }
1134
+ body.jf-page-home .hero { grid-column: 1; grid-row: 1 / 3; }
1135
+ body.jf-page-home .buttons { grid-column: 2; grid-row: 1; }
1136
+ body.jf-page-home .statsRow { grid-column: 2; grid-row: 2; }
1137
+
1138
+ /* FRAME — Foto links (16:10 fixed), Info rechts */
1139
+ body.jf-page-frame .jfFrameMain {
1140
+ grid-template-columns: minmax(0, 48%) minmax(0, 1fr);
1141
+ grid-template-rows: minmax(0, 1fr) auto;
1142
+ grid-template-areas:
1143
+ "photo info"
1144
+ "metrics metrics";
1145
+ align-items: start;
1146
+ }
1147
+
1148
+ /* No-Flight Landscape: Radar links, Runway-Pille rechts */
1149
+ body.jf-page-frame.jf-no-flight .jfFrameMain {
1150
+ grid-template-columns: minmax(0, 48%) minmax(0, 1fr);
1151
+ grid-template-rows: auto;
1152
+ grid-template-areas: "photo info";
1153
+ align-items: start;
1154
+ }
1155
+
1156
+ body.jf-page-frame.jf-no-flight .frameIdleRunwaySlot {
1157
+ grid-area: info;
1158
+ display: flex;
1159
+ align-items: center;
1160
+ justify-content: center;
1161
+ }
1162
+ body.jf-page-frame .jfPreloadOverlay {
1163
+ grid-template-columns: minmax(0, 48%) minmax(0, 1fr);
1164
+ grid-template-rows: auto auto;
1165
+ grid-template-areas:
1166
+ "prePhoto preInfo"
1167
+ "preMetrics preMetrics";
1168
+ align-items: start;
1169
+ }
1170
+ body.jf-page-frame .photoWrap {
1171
+ aspect-ratio: 16 / 10;
1172
+ width: 100%;
1173
+ height: auto;
1174
+ align-self: start;
1175
+ }
1176
+ /* FRAME Landscape — kompaktere Info-Schriften damit alles passt */
1177
+ body.jf-page-frame .mode { font-size: clamp(14px, 2vmin, 22px); }
1178
+ body.jf-page-frame .iataCallsign { font-size: clamp(24px, 4vmin, 44px); }
1179
+ body.jf-page-frame .routeCities { font-size: clamp(18px, 3vmin, 36px); }
1180
+ body.jf-page-frame .operatorCallsign,
1181
+ body.jf-page-frame .routeCodes,
1182
+ body.jf-page-frame .reg { font-size: clamp(11px, 1.5vmin, 16px); }
1183
+ body.jf-page-frame .aircraftCard { padding: clamp(6px,1vmin,10px) clamp(10px,1.4vmin,14px); width: 100%; }
1184
+ body.jf-page-frame .acRow { padding: clamp(3px,.5vmin,6px) 0; gap: clamp(6px,1vmin,10px); }
1185
+ body.jf-page-frame .acLogo { width: clamp(26px,3.2vmin,36px); height: clamp(26px,3.2vmin,36px); }
1186
+ body.jf-page-frame .airline,
1187
+ body.jf-page-frame #aircraftTypeText { font-size: clamp(11px,1.6vmin,16px); }
1188
+ body.jf-page-frame #aircraftSize { font-size: clamp(10px,1.4vmin,14px); }
1189
+ body.jf-page-frame .metricValue { font-size: clamp(18px,3vmin,32px); }
1190
+ body.jf-page-frame .metric { min-height: clamp(42px,6vmin,60px); }
1191
+
1192
+ body.jf-page-frame .prePhoto {
1193
+ aspect-ratio: 16 / 10;
1194
+ width: 100%;
1195
+ height: auto;
1196
+ align-self: start;
1197
+ }
1198
+ /* Preload Skeleton im Landscape: Skeleton-Balken kleiner */
1199
+ body.jf-page-frame .skMode { height: clamp(14px,2vmin,22px); }
1200
+ body.jf-page-frame .skPillA { height: clamp(20px,2.8vmin,32px); }
1201
+ body.jf-page-frame .skPillB { height: clamp(20px,2.8vmin,32px); }
1202
+ body.jf-page-frame .skCall { height: clamp(22px,3.5vmin,42px); }
1203
+ body.jf-page-frame .skSmall { height: clamp(10px,1.4vmin,16px); }
1204
+ body.jf-page-frame .skRoute { height: clamp(16px,2.5vmin,30px); }
1205
+ body.jf-page-frame .skCodes { height: clamp(10px,1.4vmin,16px); }
1206
+ body.jf-page-frame .skReg { height: clamp(10px,1.4vmin,16px); }
1207
+ body.jf-page-frame .preMetric { min-height: clamp(42px,6vmin,60px); }
1208
+ body.jf-page-frame .preCard {
1209
+ padding: clamp(6px,1vmin,10px) clamp(10px,1.4vmin,14px);
1210
+ gap: clamp(4px,.6vmin,8px);
1211
+ }
1212
+ body.jf-page-frame .preCardRow { gap: clamp(6px,1vmin,10px); }
1213
+ body.jf-page-frame .preLogo {
1214
+ width: clamp(26px,3.2vmin,36px);
1215
+ height: clamp(26px,3.2vmin,36px);
1216
+ }
1217
+ body.jf-page-frame .preInfo { gap: clamp(4px,.6vmin,8px); }
1218
+
1219
+ /* HEATMAP — Hours links, Rankings rechts */
1220
+ body.jf-page-heatmap .jf-shell {
1221
+ grid-template-columns: minmax(0, 60%) minmax(220px, 40%);
1222
+ grid-template-rows: auto auto auto minmax(0,1fr);
1223
+ }
1224
+ body.jf-page-heatmap .header,
1225
+ body.jf-page-heatmap .hmStats,
1226
+ body.jf-page-heatmap .rush { grid-column: 1 / -1; }
1227
+ body.jf-page-heatmap .scroller { grid-column: 1; grid-row: 4; }
1228
+ body.jf-page-heatmap .rankings {
1229
+ grid-column: 2; grid-row: 4;
1230
+ grid-template-columns: 1fr;
1231
+ grid-template-rows: 1fr 1fr;
1232
+ }
1233
+
1234
+ /* STATS — Stats links, Panels rechts */
1235
+ body.jf-page-stats .jf-shell {
1236
+ grid-template-columns: minmax(0, 56%) minmax(220px, 44%);
1237
+ grid-template-rows: auto minmax(0,1fr);
1238
+ }
1239
+ body.jf-page-stats .header { grid-column: 1 / -1; }
1240
+ body.jf-page-stats .grid {
1241
+ grid-column: 1;
1242
+ grid-template-columns: repeat(2, 1fr);
1243
+ grid-template-rows: repeat(3, minmax(0,1fr));
1244
+ min-height: 0;
1245
+ }
1246
+ body.jf-page-stats .panels {
1247
+ grid-column: 2;
1248
+ grid-template-columns: 1fr;
1249
+ grid-template-rows: repeat(4, minmax(0,1fr));
1250
+ min-height: 0;
1251
+ }
1252
+ body.jf-page-stats .stat { min-height: 0; }
1253
+ }
1254
+
1255
+ /* Schmale Geräte im Hochformat: Heatmap-Stats und Stats-Grid auf 2 Spalten */
1256
+ @media (orientation: portrait) and (max-width: 600px) {
1257
+ body.jf-page-heatmap .hmStats { grid-template-columns: repeat(2, 1fr); }
1258
+ body.jf-page-stats .grid { grid-template-columns: repeat(2, 1fr); }
1259
+ body.jf-page-stats .panels { grid-template-columns: 1fr; grid-template-rows: repeat(4, minmax(0,1fr)); }
1260
+ }