C:\$Recycle.Bin\S-1-5-21-3967099092-2644009230-141905953-500\$RNUCTNM.php


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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
<!doctype html>
<html lang="de">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <title>Countdown bis nächster F1 GP (OpenF1)</title>
  <style>
    body { font-family: system-ui, Arial, sans-serif; display:flex; align-items:center; justify-content:center; height:100vh; margin:0; background:#0b1220; color:#e6eef8; }
    .card { background:#0f1724; padding:24px; border-radius:12px; box-shadow:0 6px 20px rgba(2,6,23,0.6); text-align:center; width:360px; }
    h1 { margin:0 0 12px; font-size:18px; color:#fff; }
    .race { font-weight:600; margin-bottom:8px; }
    .time { font-size:28px; letter-spacing:0.6px; margin-bottom:10px; }
    .note { font-size:13px; color:#9fb0d6; }
  </style>
</head>
<body>
  <div class="card">
    <h1>Next Live Session</h1>
    <div class="race" id="raceInfo">loading session info …</div>
    <div class="time" id="countdown">--:--:--:--</div>
    <div class="note" id="local">Local Time</div>
  </div>

  <script>
  const API = 'https://api.openf1.org/v1';

  function parseIso(s) {
    if (!s) return null;
    const d = new Date(s);
    return isNaN(d) ? null : d;
  }

  // Hol alle Meetings (oder Sessions) vom API — wir verwenden /meetings?future=true wenn verfügbar,
  // sonst fallback auf /sessions?meeting_key=latest und /meetings.
  async function fetchAllUpcomingItems() {
    // Versuche meetings?future=true (gibt typischerweise kommende Meetings)
    const tryUrls = [
      `${API}/meetings?future=true`,
      `${API}/meetings`,
      `${API}/sessions`
    ];
    for (const url of tryUrls) {
      try {
        const res = await fetch(url);
        if (!res.ok) continue;
        const data = await res.json();
        // data kann array oder object mit meetings/sessions
        if (Array.isArray(data) && data.length) return data;
        if (Array.isArray(data?.meetings) && data.meetings.length) return data.meetings;
        if (Array.isArray(data?.sessions) && data.sessions.length) return data.sessions;
      } catch (e) {
        // next
      }
    }
    throw new Error('no session data available');
  }

  // Extrahiere Zeitstempel aus Meeting/Session-Objekt
  function getDateCandidates(item) {
    const list = [];
    if (item.date_start) list.push(parseIso(item.date_start));
    if (item.date_end) list.push(parseIso(item.date_end));
    if (item.date) list.push(parseIso(item.date));
    if (item.sessions && Array.isArray(item.sessions)) {
      for (const s of item.sessions) {
        if (s.date_start) list.push(parseIso(s.date_start));
        if (s.date) list.push(parseIso(s.date));
      }
    }
    return list.filter(d => d instanceof Date && !isNaN(d));
  }

  // Wähle das nächste zukünftige Session-Objekt (oder Meeting) mit frühestem zukünftigen Datum
  function pickNext(items) {
    const now = new Date();
    const candidates = [];

    for (const it of items) {
      // wenn session-Objekte in der Liste sind, prüfen wir session_name
      if (it.session_name) {
        const d = parseIso(it.date_start || it.date || it.date_time);
        candidates.push({ item: it, date: d, type: 'session' });
      } else if (it.meeting_key || it.meeting_name || it.sessions) {
        // meetings: prüfen meeting date_start und alle sessions
        const dates = getDateCandidates(it);
        for (const d of dates) candidates.push({ item: it, date: d, type: 'meeting' });
        // und wenn meeting enthält sessions, auch einzelne sessions als separate Kandidaten
        if (Array.isArray(it.sessions)) {
          for (const s of it.sessions) {
            const sd = parseIso(s.date_start || s.date);
            if (sd) candidates.push({ item: s, date: sd, parent: it, type: 'session' });
          }
        }
      } else {
        // generischer fallback: prüfen mögliche date*-Felder
        const d = parseIso(it.date_start || it.date || it.date_time);
        if (d) candidates.push({ item: it, date: d, type: 'unknown' });
      }
    }

    // nur zukünftige
    const future = candidates.filter(c => c.date && c.date > now);
    if (future.length === 0) return null;

    // Bevorzuge Einträge, deren item.session_name enthält 'Race'
    const futureRaces = future.filter(c => c.item?.session_name && /race/i.test(c.item.session_name));
    const listToSort = futureRaces.length ? futureRaces : future;

    // Sortiere nach frühestem Datum
    listToSort.sort((a,b) => a.date - b.date);
    return listToSort[0];
  }

  function startCountdown(targetDate, onTick) {
    let interval = null;
    function update() {
      const now = new Date();
      let diff = targetDate - now;
      if (diff < 0) diff = 0;
      const days = Math.floor(diff / (1000*60*60*24));
      const hours = Math.floor((diff%(1000*60*60*24)) / (1000*60*60));
      const minutes = Math.floor((diff%(1000*60*60)) / (1000*60));
      const seconds = Math.floor((diff%(1000*60)) / 1000);
      onTick({ days, hours, minutes, seconds, remainingMs: diff });
      if (diff === 0 && interval !== null) {
        clearInterval(interval);
        interval = null;
      }
    }
    update();
    interval = setInterval(update, 1000);
    return () => { if (interval !== null) { clearInterval(interval); interval = null; } };
  }

  (async function init() {
    const raceInfoEl = document.getElementById('raceInfo');
    const countdownEl = document.getElementById('countdown');
    const localEl = document.getElementById('local');

    try {
      const items = await fetchAllUpcomingItems();
      const picked = pickNext(items);
      if (!picked) throw new Error('Kein zukünftiges Rennen gefunden');

      // picked.item kann Session oder Meeting; parent vorhanden wenn session aus meeting extrahiert wurde
      const obj = picked.item;
      const parent = picked.parent || null;
      const date = picked.date;
      if (!date) throw new Error('Kein Datum im ausgewählten Element');

      // Bestimme Anzeige-Infos
      const sessionName = obj.session_name || obj.name || obj.race_name || null;
      const meetingName = (parent && (parent.meeting_name || parent.meeting_key)) || obj.meeting_name || obj.meeting_key || null;
      const circuit = (parent && (parent.circuit_name || parent.location)) || obj.circuit_name || obj.location || '';
      const country = (parent && (parent.country || parent.location_country)) || obj.country || obj.location_country || '';
      const event = (parent && (parent.meeting_key || parent.meeting_name)) || obj.session || '';

      const displayName = sessionName ? sessionName + (meetingName ? ' — ' + meetingName : '') : (meetingName || 'Next Session') + (Event ? ' — ' + Event : '') : (Event || 'Next Session');

      raceInfoEl.textContent = `${displayName}${circuit ? ' — ' + circuit : ''}${country ? ' ('+country+')' : ''}`;
      localEl.textContent = `Start (local time): ${date.toLocaleString(undefined, { dateStyle:'medium', timeStyle:'short' })}`;

      startCountdown(date, ({days, hours, minutes, seconds, remainingMs}) => {
        const dstr = days > 0 ? days + 'd ' : '';
        countdownEl.textContent = `${dstr}${String(hours).padStart(2,'0')}:${String(minutes).padStart(2,'0')}:${String(seconds).padStart(2,'0')}`;
        if (remainingMs === 0) countdownEl.textContent = 'Rennen begonnen!';
      });

    } catch (err) {
      raceInfoEl.textContent = 'error loading';
      countdownEl.textContent = '--:--:--:--';
      console.error(err);
    }
  })();
  </script>
</body>
</html>