skander101 commited on
Commit
cfb7830
·
1 Parent(s): 7232589

remove old Jinja2 templates

Browse files
Files changed (2) hide show
  1. templates/index.html +0 -635
  2. templates/sponsors.html +0 -199
templates/index.html DELETED
@@ -1,635 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>News Digest</title>
7
- <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Serif:ital,wght@0,400;0,600;0,700;1,400&family=Lora:wght@400;600;700&display=swap" rel="stylesheet">
8
- <style>
9
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
10
- body {
11
- font-family: 'IBM Plex Serif', Georgia, 'Times New Roman', serif;
12
- background: #000;
13
- color: #a0a0a0;
14
- min-height: 100vh;
15
- overflow-x: hidden;
16
- }
17
- a { color: #d72924; text-decoration: none; }
18
- a:hover, a:focus, a:active { color: #df5450; }
19
- h1, h2, h3, h4, h5, h6 { font-family: Lora, Cambria, sans-serif; color: #fff; }
20
- .header {
21
- background: linear-gradient(0deg, rgba(0,0,0,0.92) 0%, rgba(215,41,36,0.92) 100%);
22
- padding: 20px 32px;
23
- position: sticky;
24
- top: 0;
25
- z-index: 50;
26
- }
27
- .header-inner {
28
- max-width: 1400px;
29
- margin: 0 auto;
30
- display: flex;
31
- align-items: center;
32
- justify-content: space-between;
33
- flex-wrap: wrap;
34
- gap: 12px;
35
- }
36
- .header h1 {
37
- font-size: 22px;
38
- font-weight: 700;
39
- font-family: Lora, Cambria, sans-serif;
40
- color: #fff;
41
- }
42
- .header span { font-size: 13px; color: #c0c0c0; }
43
- .status { color: #c0c0c0; font-size: 13px; display: flex; align-items: center; gap: 8px; }
44
- .status-dot {
45
- width: 8px;
46
- height: 8px;
47
- border-radius: 50%;
48
- background: #22c55e;
49
- display: inline-block;
50
- animation: pulse-dot 2s ease-in-out infinite;
51
- }
52
- @keyframes pulse-dot {
53
- 0%, 100% { opacity: 1; transform: scale(1); box-shadow: 0 0 0 0 rgba(34,197,94,0.6); }
54
- 50% { opacity: 0.7; transform: scale(0.85); box-shadow: 0 0 0 6px rgba(34,197,94,0); }
55
- }
56
- .container { max-width: 1400px; margin: 0 auto; padding: 20px 32px; }
57
-
58
- /* Tabs */
59
- .tabs {
60
- display: flex;
61
- gap: 4px;
62
- margin-bottom: 24px;
63
- border-bottom: 1px solid #333;
64
- overflow-x: auto;
65
- position: relative;
66
- }
67
- .tab {
68
- padding: 10px 20px 10px 20px;
69
- border: none;
70
- background: none;
71
- color: #666;
72
- font-family: 'IBM Plex Serif', Georgia, serif;
73
- font-size: 14px;
74
- font-weight: 600;
75
- cursor: pointer;
76
- white-space: nowrap;
77
- position: relative;
78
- transition: color 0.25s ease;
79
- }
80
- .tab:hover { color: #c0c0c0; }
81
- .tab.active { color: #d72924; }
82
- .tab-count { font-size: 11px; color: #555; margin-left: 4px; transition: color 0.25s; }
83
- .tab.active .tab-count { color: rgba(215,41,36,0.7); }
84
- .tab-underline {
85
- position: absolute;
86
- bottom: -1px;
87
- height: 2px;
88
- background: #d72924;
89
- transition: left 0.3s cubic-bezier(0.4, 0, 0.2, 1), width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
90
- box-shadow: 0 0 8px rgba(215,41,36,0.5);
91
- }
92
-
93
- .tab-content { display: none; }
94
- .tab-content.active { display: block; }
95
-
96
- /* ── Card entrance ── */
97
- .card {
98
- background: rgba(12, 12, 12, 0.92);
99
- border-radius: 0;
100
- overflow: hidden;
101
- border: 1px solid #222;
102
- display: flex;
103
- flex-direction: column;
104
- box-shadow: 1px 2px 3px rgba(255,255,255,.1);
105
- opacity: 0;
106
- transform: translateY(15px);
107
- transition: transform 0.2s ease-out, box-shadow 0.2s ease-out, border-color 0.2s ease-out, opacity 0.3s ease-out;
108
- }
109
- .card.revealed {
110
- opacity: 1;
111
- transform: translateY(0);
112
- }
113
- .card:hover {
114
- transform: translateY(-3px) scale(1.02);
115
- box-shadow: 0 0 20px rgba(215,41,36,0.15), 0 8px 24px rgba(255,255,255,.1);
116
- border-color: rgba(215,41,36,0.3);
117
- }
118
- .card-image-wrap {
119
- overflow: hidden;
120
- height: 180px;
121
- }
122
- .card-image {
123
- width: 100%;
124
- height: 180px;
125
- object-fit: cover;
126
- background: #111;
127
- display: block;
128
- transition: transform 0.3s ease-out;
129
- }
130
- .card:hover .card-image { transform: scale(1.05); }
131
- .card-image-placeholder {
132
- width: 100%;
133
- height: 180px;
134
- background: linear-gradient(135deg, #111, #000);
135
- display: flex;
136
- align-items: center;
137
- justify-content: center;
138
- font-size: 40px;
139
- color: #333;
140
- }
141
- .card-body { padding: 14px 18px 18px; flex: 1; display: flex; flex-direction: column; }
142
- .card-rank {
143
- font-size: 10px;
144
- font-weight: 700;
145
- color: #666;
146
- letter-spacing: .05em;
147
- margin-bottom: 4px;
148
- }
149
- .card-topic {
150
- font-size: 10px;
151
- font-weight: 600;
152
- text-transform: uppercase;
153
- letter-spacing: .08em;
154
- color: #d72924;
155
- margin-bottom: 6px;
156
- }
157
- .card-title {
158
- font-size: 15px;
159
- font-weight: 600;
160
- font-family: Lora, Cambria, sans-serif;
161
- line-height: 1.4;
162
- margin-bottom: 6px;
163
- color: #fff;
164
- display: -webkit-box;
165
- -webkit-line-clamp: 2;
166
- -webkit-box-orient: vertical;
167
- overflow: hidden;
168
- }
169
- .card-summary {
170
- font-size: 13px;
171
- color: #a0a0a0;
172
- line-height: 1.5;
173
- margin-bottom: 10px;
174
- display: -webkit-box;
175
- -webkit-line-clamp: 2;
176
- -webkit-box-orient: vertical;
177
- overflow: hidden;
178
- flex: 1;
179
- }
180
- .card-meta { display: flex; align-items: center; gap: 10px; font-size: 12px; color: #c0c0c0; margin-bottom: 10px; flex-wrap: wrap; }
181
- .card-meta .source { color: #c0c0c0; }
182
- .sponsor { font-size: 10px; color: #fbbf24; background: rgba(251, 191, 36, 0.1); padding: 2px 6px; border-radius: 4px; }
183
- .bias-badge { font-size: 10px; font-weight: 600; padding: 2px 6px; border-radius: 4px; letter-spacing: .03em; }
184
- .bias-left { background: rgba(59,130,246,0.15); color: #60a5fa; }
185
- .bias-left-center { background: rgba(99,102,241,0.15); color: #818cf8; }
186
- .bias-center { background: rgba(148,163,184,0.15); color: #94a3b8; }
187
- .bias-right-center { background: rgba(239,68,68,0.15); color: #f87171; }
188
- .bias-right { background: rgba(220,38,38,0.15); color: #ef4444; }
189
- .bias-satire { background: rgba(168,85,247,0.15); color: #c084fc; }
190
- .factuality-badge { font-size: 10px; font-weight: 600; padding: 2px 6px; border-radius: 4px; letter-spacing: .03em; }
191
- .factuality-high { background: rgba(34,197,94,0.15); color: #4ade80; }
192
- .factuality-mixed { background: rgba(251,191,36,0.15); color: #fbbf24; }
193
- .factuality-low { background: rgba(239,68,68,0.15); color: #f87171; }
194
- .factuality-satire { background: rgba(168,85,247,0.15); color: #c084fc; }
195
- .category-badge { font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .08em; padding: 3px 8px; }
196
- .cat-government { background: rgba(239,68,68,0.15); color: #f87171; }
197
- .cat-conglomerate { background: rgba(139,92,246,0.15); color: #a78bfa; }
198
- .cat-private_equity { background: rgba(251,191,36,0.15); color: #fbbf24; }
199
- .cat-wealthy_private { background: rgba(236,72,153,0.15); color: #f472b6; }
200
- .cat-corporate { background: rgba(59,130,246,0.15); color: #60a5fa; }
201
- .cat-independent { background: rgba(34,197,94,0.15); color: #4ade80; }
202
- .score-bar-wrap { height: 4px; background: #222; border-radius: 2px; margin-bottom: 3px; overflow: hidden; }
203
- .score-bar-fill {
204
- height: 100%;
205
- border-radius: 2px;
206
- background: linear-gradient(90deg, #d72924, #df5450);
207
- width: 0%;
208
- transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1);
209
- }
210
- .score-label { font-size: 10px; color: #666; display: flex; justify-content: space-between; }
211
- .card-actions { display: flex; gap: 6px; margin-top: 6px; }
212
- .btn {
213
- display: inline-flex;
214
- align-items: center;
215
- gap: 4px;
216
- padding: 6px 14px;
217
- border-radius: 4px;
218
- font-size: 12px;
219
- font-weight: 600;
220
- font-family: 'IBM Plex Serif', Georgia, serif;
221
- text-decoration: none;
222
- cursor: pointer;
223
- border: none;
224
- transition: transform 0.15s ease, background 0.2s ease, filter 0.2s ease;
225
- }
226
- .btn:hover { filter: brightness(1.2); transform: scale(1.04); }
227
- .btn:active { transform: scale(0.97) !important; }
228
- .btn-primary {
229
- background: linear-gradient(to right, #d72924 0%, #db3e3a 51%, #d72924 100%);
230
- color: #fff;
231
- }
232
- .btn-primary:hover { background: linear-gradient(to right, #db3e3a 0%, #e36966 51%, #db3e3a 100%); filter: brightness(1.15); color: #fff; }
233
- .btn-secondary { background: #222; color: #c0c0c0; }
234
- .btn-secondary:hover { background: #333; filter: brightness(1.2); color: #fff; }
235
- .card-date {
236
- margin-left: auto;
237
- font-size: 12px;
238
- font-weight: 700;
239
- color: #d72924;
240
- background: rgba(215, 41, 36, 0.08);
241
- padding: 3px 10px;
242
- border: 1px solid rgba(215, 41, 36, 0.2);
243
- transition: background 0.2s, border-color 0.2s;
244
- }
245
- .card:hover .card-date { background: rgba(215,41,36,0.15); border-color: rgba(215,41,36,0.4); }
246
- .card-topics { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 10px; }
247
- .topic-tag { font-size: 10px; padding: 2px 7px; background: #222; color: #c0c0c0; }
248
-
249
- .date-group { margin-bottom: 16px; }
250
- .date-header {
251
- font-size: 12px;
252
- font-weight: 700;
253
- color: #d72924;
254
- text-transform: uppercase;
255
- letter-spacing: .08em;
256
- margin-bottom: 10px;
257
- padding-bottom: 6px;
258
- border-bottom: 1px solid #333;
259
- }
260
-
261
- .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); gap: 16px; }
262
-
263
- .empty { text-align: center; padding: 60px 20px; color: #666; }
264
- .empty h2 { font-size: 18px; margin-bottom: 6px; color: #fff; }
265
-
266
- /* ── Shimmer skeleton ── */
267
- @keyframes shimmer {
268
- 0% { background-position: -200px 0; }
269
- 100% { background-position: calc(200px + 100%) 0; }
270
- }
271
- .skeleton {
272
- background: linear-gradient(90deg, #1a1a1a 25%, #2a2a2a 50%, #1a1a1a 75%);
273
- background-size: 200px 100%;
274
- animation: shimmer 1.5s ease-in-out infinite;
275
- border-radius: 2px;
276
- }
277
- .skeleton-img { height: 180px; width: 100%; }
278
- .skeleton-text { height: 14px; margin-bottom: 8px; width: 80%; }
279
- .skeleton-text-short { height: 14px; margin-bottom: 8px; width: 50%; }
280
-
281
- /* Modal */
282
- .modal-overlay {
283
- display: none;
284
- position: fixed;
285
- inset: 0;
286
- background: rgba(0,0,0,0.85);
287
- z-index: 1000;
288
- justify-content: center;
289
- align-items: center;
290
- padding: 20px;
291
- opacity: 0;
292
- transition: opacity 0.25s ease;
293
- }
294
- .modal-overlay.active {
295
- display: flex;
296
- opacity: 1;
297
- }
298
- .modal {
299
- background: rgba(12,12,12,0.96);
300
- border: 1px solid #333;
301
- max-width: 480px;
302
- width: 100%;
303
- padding: 28px;
304
- position: relative;
305
- box-shadow: 0 0 40px rgba(215,41,36,0.15);
306
- transform: translateY(20px) scale(0.95);
307
- transition: transform 0.25s ease, opacity 0.25s ease;
308
- opacity: 0;
309
- }
310
- .modal-overlay.active .modal {
311
- transform: translateY(0) scale(1);
312
- opacity: 1;
313
- }
314
- .modal-close {
315
- position: absolute;
316
- top: 12px;
317
- right: 16px;
318
- background: none;
319
- border: none;
320
- color: #666;
321
- font-size: 22px;
322
- cursor: pointer;
323
- font-family: 'IBM Plex Serif', Georgia, serif;
324
- transition: color 0.2s, transform 0.15s;
325
- }
326
- .modal-close:hover { color: #d72924; transform: rotate(90deg); }
327
- .modal h2 { font-size: 18px; color: #d72924; margin-bottom: 4px; }
328
- .modal .parent { font-size: 13px; color: #c0c0c0; margin-bottom: 12px; }
329
- .modal-row { display: flex; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; }
330
- .modal-section { margin-bottom: 14px; }
331
- .modal-section h3 { font-size: 11px; font-weight: 600; color: #666; text-transform: uppercase; letter-spacing: .06em; margin-bottom: 6px; }
332
- .owner-list { display: flex; flex-wrap: wrap; gap: 6px; }
333
- .owner-tag { font-size: 12px; padding: 4px 10px; background: rgba(215,41,36,0.08); color: #d72924; border: 1px solid rgba(215,41,36,0.2); }
334
- .owner-wiki { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; font-size: 14px; background: rgba(215,41,36,0.08); color: #d72924; border: 1px solid rgba(215,41,36,0.2); text-decoration: none; transition: background 0.2s, transform 0.15s; }
335
- .owner-wiki:hover { background: rgba(215,41,36,0.25); color: #df5450; transform: scale(1.1); }
336
- .modal-wiki {
337
- display: inline-block;
338
- margin-top: 6px;
339
- padding: 8px 16px;
340
- background: linear-gradient(to right, #d72924 0%, #db3e3a 51%, #d72924 100%);
341
- color: #fff;
342
- text-decoration: none;
343
- font-size: 13px;
344
- font-weight: 600;
345
- font-family: 'IBM Plex Serif', Georgia, serif;
346
- transition: opacity 0.2s, transform 0.15s;
347
- }
348
- .modal-wiki:hover { opacity: .85; color: #fff; transform: scale(1.03); }
349
-
350
- @media (max-width: 640px) {
351
- .header { padding: 14px; }
352
- .container { padding: 14px; }
353
- .grid { grid-template-columns: 1fr; }
354
- }
355
- </style>
356
- </head>
357
- <body>
358
- <div class="header">
359
- <div class="header-inner">
360
- <div>
361
- <h1>News Digest</h1>
362
- <span><span class="count-to" data-target="{{ total }}">0</span> stories across {{ categories|length }} topics</span>
363
- </div>
364
- <div style="display:flex; align-items:center; gap:12px;">
365
- <span class="status">
366
- <span class="status-dot"></span>
367
- {{ status }}
368
- </span>
369
- </div>
370
- </div>
371
- </div>
372
-
373
- <div class="container">
374
- {% if total == 0 %}
375
- <div class="empty">
376
- <h2>No news yet</h2>
377
- <p>Run <code>python webapp.py</code> to fetch and analyze articles</p>
378
- </div>
379
- {% else %}
380
- <div class="tabs" id="tabs">
381
- {% for cat in categories %}
382
- <button class="tab {% if loop.first %}active{% endif %}" data-tab="{{ cat | replace(' ', '_') | replace('/', '_') }}">
383
- {{ cat }} <span class="tab-count">(<span class="count-to" data-target="{{ tab_counts[cat] }}">0</span>)</span>
384
- </button>
385
- {% endfor %}
386
- </div>
387
- <div class="tab-underline"></div>
388
-
389
- {% for cat in categories %}
390
- <div class="tab-content {% if loop.first %}active{% endif %}" id="tab-{{ cat | replace(' ', '_') | replace('/', '_') }}">
391
- <div class="skeleton-container">
392
- <div class="grid">
393
- {% for i in range(6) %}
394
- <div class="card" style="opacity:1;transform:none;border-color:#1a1a1a;">
395
- <div class="skeleton skeleton-img"></div>
396
- <div class="card-body">
397
- <div class="skeleton skeleton-text-short"></div>
398
- <div class="skeleton skeleton-text"></div>
399
- <div class="skeleton skeleton-text" style="width:60%;"></div>
400
- <div style="margin-top:auto;">
401
- <div class="skeleton" style="height:4px;margin-bottom:3px;"></div>
402
- <div class="skeleton skeleton-text-short" style="margin-bottom:0;"></div>
403
- </div>
404
- </div>
405
- </div>
406
- {% endfor %}
407
- </div>
408
- </div>
409
- {% set date_groups = grouped_by_date[cat] %}
410
- {% if date_groups %}
411
- {% for group in date_groups %}
412
- <div class="date-group">
413
- <div class="date-header">{{ group.date }}</div>
414
- <div class="grid">
415
- {% for c in group['items'] %}
416
- <div class="card">
417
- {% if c.image_url %}
418
- <div class="card-image-wrap"><img class="card-image" src="{{ c.image_url }}" alt="" loading="lazy" onerror="this.parentElement.innerHTML='<div class=card-image-placeholder>📰</div>'"></div>
419
- {% else %}
420
- <div class="card-image-placeholder">📰</div>
421
- {% endif %}
422
- <div class="card-body">
423
- <div class="card-rank">#{{ loop.index }}</div>
424
- <div class="card-topic">{{ c.topic }}</div>
425
- <div class="card-title">{{ c.articles[0].title }}</div>
426
- {% if c.articles[0].summary %}
427
- <div class="card-summary">{{ c.articles[0].summary[:160] }}{% if c.articles[0].summary|length > 160 %}…{% endif %}</div>
428
- {% endif %}
429
- <div class="card-topics">
430
- {% for t in c.articles[0].topics[:3] %}
431
- <span class="topic-tag">{{ t }}</span>
432
- {% endfor %}
433
- </div>
434
- <div class="card-meta">
435
- <span class="source">{{ c.articles[0].domain }}</span>
436
- <span>🛡️ {{ c.trust }}</span>
437
- {% if c.articles[0].article_leaning %}<span class="bias-badge bias-{{ c.articles[0].article_leaning }}">{{ c.articles[0].article_leaning }}</span>{% endif %}
438
- {% if c.articles[0].source_bias and c.articles[0].source_bias != c.articles[0].article_leaning %}<span style="font-size:9px;color:#64748b;">src:{{ c.articles[0].source_bias }}</span>{% endif %}
439
- {% if c.articles[0].source_factuality %}<span class="factuality-badge factuality-{{ c.articles[0].source_factuality }}">{{ c.articles[0].source_factuality }}</span>{% endif %}
440
- {% if c.coverage > 1 %}<span>📰 {{ c.coverage }}</span>{% endif %}
441
- {% if c.articles[0].sponsor %}<span class="sponsor">🏢 {{ c.articles[0].sponsor }}</span>{% endif %}
442
- </div>
443
- <div class="score-bar-wrap">
444
- <div class="score-bar-fill" style="width:{{ c.score * 100 }}%"></div>
445
- </div>
446
- <div class="score-label">
447
- <span>Relevance</span>
448
- <span>{{ c.score }}</span>
449
- </div>
450
- <div class="card-actions">
451
- <button class="btn btn-primary" data-sponsor='{{ c.articles[0].sponsor_info | tojson }}'>🏢 Sponsors</button>
452
- <a href="{{ c.top_post_url }}" class="btn btn-secondary" target="_blank">Open</a>
453
- {% if c.articles[0].published %}
454
- <span class="card-date">{{ c.articles[0].published }}</span>
455
- {% endif %}
456
- </div>
457
- </div>
458
- </div>
459
- {% endfor %}
460
- </div>
461
- </div>
462
- {% endfor %}
463
- {% else %}
464
- <div class="empty"><h2>No {{ cat }} stories found</h2></div>
465
- {% endif %}
466
- </div>
467
- {% endfor %}
468
- {% endif %}
469
- </div>
470
-
471
- <div class="modal-overlay" id="sponsorModal">
472
- <div class="modal">
473
- <button class="modal-close" onclick="closeSponsorModal()">✕</button>
474
- <div id="sponsorModalContent"></div>
475
- </div>
476
- </div>
477
-
478
- <script>
479
- /* ── Tab underline ── */
480
- function moveUnderline(tab) {
481
- var ul = document.querySelector('.tab-underline');
482
- if (!tab) { ul.style.width = '0'; return; }
483
- var tabsRect = document.querySelector('.tabs').getBoundingClientRect();
484
- ul.style.left = (tab.offsetLeft) + 'px';
485
- ul.style.width = tab.offsetWidth + 'px';
486
- }
487
-
488
- document.querySelectorAll('.tab').forEach(function(tab) {
489
- tab.addEventListener('click', function() {
490
- document.querySelectorAll('.tab').forEach(function(t) { t.classList.remove('active'); });
491
- document.querySelectorAll('.tab-content').forEach(function(tc) { tc.classList.remove('active'); });
492
- this.classList.add('active');
493
- document.getElementById('tab-' + this.dataset.tab).classList.add('active');
494
- moveUnderline(this);
495
- revealCards(this.dataset.tab);
496
- });
497
- });
498
-
499
- var activeTab = document.querySelector('.tab.active');
500
- if (activeTab) moveUnderline(activeTab);
501
- window.addEventListener('resize', function() {
502
- var at = document.querySelector('.tab.active');
503
- if (at) moveUnderline(at);
504
- });
505
-
506
- /* ── Skeleton removal + scroll-based card reveal ── */
507
- function revealCards(tabId) {
508
- var tc = document.getElementById('tab-' + tabId);
509
- if (!tc) return;
510
- var skeleton = tc.querySelector('.skeleton-container');
511
- if (skeleton) skeleton.style.display = 'none';
512
- tc.querySelectorAll('.card:not(.revealed)').forEach(function(c) {
513
- cardObserver.observe(c);
514
- });
515
- }
516
-
517
- function animateScoreBar(card) {
518
- var bar = card.querySelector('.score-bar-fill');
519
- if (bar && bar.dataset.target === undefined) {
520
- var styleWidth = bar.getAttribute('style') || '';
521
- var match = styleWidth.match(/width:\s*([\d.]+)%/);
522
- bar.dataset.target = match ? parseFloat(match[1]) : 0;
523
- bar.style.width = '0%';
524
- requestAnimationFrame(function() {
525
- requestAnimationFrame(function() {
526
- bar.style.width = bar.dataset.target + '%';
527
- });
528
- });
529
- }
530
- }
531
-
532
- /* ── Count-up ── */
533
- function animateCounters() {
534
- document.querySelectorAll('.count-to').forEach(function(el) {
535
- var target = parseInt(el.getAttribute('data-target'));
536
- if (isNaN(target) || target === 0) { el.textContent = target; return; }
537
- var current = 0;
538
- var step = Math.max(1, Math.floor(target / 20));
539
- var interval = setInterval(function() {
540
- current += step;
541
- if (current >= target) {
542
- current = target;
543
- clearInterval(interval);
544
- }
545
- el.textContent = current;
546
- }, 40);
547
- });
548
- }
549
-
550
- /* ── Scroll reveal (single observer for all cards) ── */
551
- var cardObserver = new IntersectionObserver(function(entries) {
552
- entries.forEach(function(entry) {
553
- if (entry.isIntersecting) {
554
- entry.target.classList.add('revealed');
555
- animateScoreBar(entry.target);
556
- cardObserver.unobserve(entry.target);
557
- }
558
- });
559
- }, { threshold: 0.1, rootMargin: '0px 0px 40px 0px' });
560
-
561
- /* ── Sponsor modal ── */
562
- document.querySelectorAll('.btn-primary[data-sponsor]').forEach(function(btn) {
563
- btn.addEventListener('click', function() {
564
- var info = JSON.parse(this.getAttribute('data-sponsor'));
565
- var modal = document.getElementById('sponsorModal');
566
- var content = document.getElementById('sponsorModalContent');
567
-
568
- if (!info || !info.display) {
569
- content.innerHTML = '<p style="color:#666;">No sponsor data available.</p>';
570
- modal.classList.add('active');
571
- return;
572
- }
573
-
574
- var catLabels = {
575
- government: '\u{1F3DB}\uFE0F Government',
576
- conglomerate: '\u{1F3E2} Media Conglomerate',
577
- private_equity: '\u{1F4B0} Private Equity',
578
- wealthy_private: '\u{1F464} Wealthy Private Owner',
579
- corporate: '\u{1F4CA} Corporate',
580
- independent: '\u2705 Independent'
581
- };
582
- var catLabel = catLabels[info.category] || info.category;
583
-
584
- var ownersHtml = info.owners && info.owners.length
585
- ? '<div class="modal-section"><h3>Shareholders / Funders</h3><div class="owner-list">' +
586
- info.owners.map(function(o) {
587
- var wikiUrl = info.owner_wikis && info.owner_wikis[o];
588
- var tag = '<span class="owner-tag">' + o + '</span>';
589
- if (wikiUrl) {
590
- tag += '<a href="' + wikiUrl + '" class="owner-wiki" target="_blank" title="Wikipedia">\u{1F4D6}</a>';
591
- }
592
- return tag;
593
- }).join('') +
594
- '</div></div>'
595
- : '';
596
-
597
- var wikiHtml = info.wikipedia
598
- ? '<a href="' + info.wikipedia + '" class="modal-wiki" target="_blank">\u{1F4D6} Wikipedia</a>'
599
- : '';
600
-
601
- content.innerHTML =
602
- '<h2>' + info.display + '</h2>' +
603
- '<div class="parent">Parent: ' + (info.parent || '\u2014') + '</div>' +
604
- '<div class="modal-row">' +
605
- '<span class="category-badge cat-' + info.category + '">' + catLabel + '</span>' +
606
- (info.bias ? '<span class="bias-badge bias-' + info.bias + '">' + info.bias + '</span>' : '') +
607
- (info.factuality ? '<span class="factuality-badge factuality-' + info.factuality + '">' + info.factuality + '</span>' : '') +
608
- '</div>' +
609
- ownersHtml +
610
- wikiHtml;
611
-
612
- modal.classList.add('active');
613
- });
614
- });
615
-
616
- function closeSponsorModal() {
617
- document.getElementById('sponsorModal').classList.remove('active');
618
- }
619
-
620
- document.getElementById('sponsorModal').addEventListener('click', function(e) {
621
- if (e.target === this) closeSponsorModal();
622
- });
623
- document.addEventListener('keydown', function(e) {
624
- if (e.key === 'Escape') closeSponsorModal();
625
- });
626
-
627
- /* ── Init ── */
628
- document.addEventListener('DOMContentLoaded', function() {
629
- animateCounters();
630
- var firstTab = document.querySelector('.tab.active');
631
- if (firstTab) revealCards(firstTab.dataset.tab);
632
- });
633
- </script>
634
- </body>
635
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/sponsors.html DELETED
@@ -1,199 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Sponsors — News Digest</title>
7
- <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Serif:ital,wght@0,400;0,600;0,700;1,400&family=Lora:wght@400;600;700&display=swap" rel="stylesheet">
8
- <style>
9
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
10
- body {
11
- font-family: 'IBM Plex Serif', Georgia, 'Times New Roman', serif;
12
- background: #000;
13
- color: #a0a0a0;
14
- min-height: 100vh;
15
- }
16
- a { color: #d72924; text-decoration: none; }
17
- a:hover, a:focus, a:active { color: #df5450; }
18
- h1, h2, h3, h4, h5, h6 { font-family: Lora, Cambria, sans-serif; color: #fff; }
19
- .header {
20
- background: linear-gradient(0deg, rgba(0,0,0,0.92) 0%, rgba(215,41,36,0.92) 100%);
21
- padding: 20px 32px;
22
- position: sticky;
23
- top: 0;
24
- z-index: 50;
25
- }
26
- .header-inner {
27
- max-width: 1200px;
28
- margin: 0 auto;
29
- display: flex;
30
- align-items: center;
31
- justify-content: space-between;
32
- flex-wrap: wrap;
33
- gap: 12px;
34
- }
35
- .header h1 {
36
- font-size: 22px;
37
- font-weight: 700;
38
- font-family: Lora, Cambria, sans-serif;
39
- color: #fff;
40
- }
41
- .subtitle { font-size: 13px; color: #c0c0c0; }
42
- .back-btn {
43
- padding: 8px 18px;
44
- font-size: 13px;
45
- font-weight: 600;
46
- font-family: 'IBM Plex Serif', Georgia, serif;
47
- text-decoration: none;
48
- background: #222;
49
- color: #c0c0c0;
50
- transition: background 0.45s;
51
- }
52
- .back-btn:hover { background: #333; color: #fff; }
53
- .grid { display: grid; gap: 16px; }
54
- .card {
55
- background: rgba(12, 12, 12, 0.92);
56
- padding: 20px 24px;
57
- border: 1px solid #222;
58
- box-shadow: 1px 2px 3px rgba(255,255,255,.1);
59
- transition: border-color 0.45s;
60
- }
61
- .card:hover { border-color: #d72924; }
62
- .card-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; flex-wrap: wrap; gap: 8px; }
63
- .card h2 { font-size: 16px; font-weight: 700; font-family: Lora, Cambria, sans-serif; color: #d72924; }
64
- .category-badge {
65
- font-size: 10px;
66
- font-weight: 600;
67
- text-transform: uppercase;
68
- letter-spacing: .08em;
69
- padding: 3px 8px;
70
- }
71
- .cat-government { background: rgba(239,68,68,0.15); color: #f87171; }
72
- .cat-conglomerate { background: rgba(139,92,246,0.15); color: #a78bfa; }
73
- .cat-private_equity { background: rgba(251,191,36,0.15); color: #fbbf24; }
74
- .cat-wealthy_private { background: rgba(236,72,153,0.15); color: #f472b6; }
75
- .cat-corporate { background: rgba(59,130,246,0.15); color: #60a5fa; }
76
- .cat-independent { background: rgba(34,197,94,0.15); color: #4ade80; }
77
- .cat-satire { background: rgba(168,85,247,0.15); color: #c084fc; }
78
- .bias-badge {
79
- font-size: 10px;
80
- font-weight: 600;
81
- padding: 2px 7px;
82
- letter-spacing: .03em;
83
- }
84
- .bias-left { background: rgba(59,130,246,0.15); color: #60a5fa; }
85
- .bias-left-center { background: rgba(99,102,241,0.15); color: #818cf8; }
86
- .bias-center { background: rgba(148,163,184,0.15); color: #94a3b8; }
87
- .bias-right-center { background: rgba(239,68,68,0.15); color: #f87171; }
88
- .bias-right { background: rgba(220,38,38,0.15); color: #ef4444; }
89
- .bias-satire { background: rgba(168,85,247,0.15); color: #c084fc; }
90
- .factuality-badge {
91
- font-size: 10px;
92
- font-weight: 600;
93
- padding: 2px 7px;
94
- letter-spacing: .03em;
95
- }
96
- .factuality-high { background: rgba(34,197,94,0.15); color: #4ade80; }
97
- .factuality-mixed { background: rgba(251,191,36,0.15); color: #fbbf24; }
98
- .factuality-low { background: rgba(239,68,68,0.15); color: #f87171; }
99
- .factuality-satire { background: rgba(168,85,247,0.15); color: #c084fc; }
100
- .card .parent { font-size: 13px; color: #c0c0c0; margin-bottom: 12px; }
101
- .owners { margin-bottom: 12px; }
102
- .owners h3 { font-size: 11px; font-weight: 600; color: #666; text-transform: uppercase; letter-spacing: .06em; margin-bottom: 6px; }
103
- .owner-list { display: flex; flex-wrap: wrap; gap: 6px; }
104
- .owner-tag {
105
- font-size: 12px;
106
- padding: 4px 10px;
107
- background: rgba(215,41,36,0.08);
108
- color: #d72924;
109
- border: 1px solid rgba(215,41,36,0.2);
110
- }
111
- .sources { margin-top: 12px; padding-top: 12px; border-top: 1px solid #333; }
112
- .sources h3 { font-size: 11px; font-weight: 600; color: #666; text-transform: uppercase; letter-spacing: .06em; margin-bottom: 6px; }
113
- .source-list { display: flex; flex-wrap: wrap; gap: 6px; }
114
- .source-tag {
115
- font-size: 12px;
116
- padding: 4px 10px;
117
- background: #222;
118
- color: #c0c0c0;
119
- border: 1px solid #333;
120
- }
121
- .empty { text-align: center; padding: 60px 20px; color: #666; }
122
- .empty h2 { font-size: 18px; margin-bottom: 6px; color: #fff; }
123
- @media (max-width: 640px) {
124
- .header { padding: 14px; }
125
- .container { padding: 14px; }
126
- }
127
- </style>
128
- </head>
129
- <body>
130
- <div class="header">
131
- <div class="header-inner">
132
- <div>
133
- <h1>🏢 Who Owns the News?</h1>
134
- <div class="subtitle">Track the parent companies and shareholders behind each news source</div>
135
- </div>
136
- <a href="/" class="back-btn">← Back to News</a>
137
- </div>
138
- </div>
139
- <div class="container">
140
- {% if sponsors %}
141
- <div class="grid">
142
- {% for s in sponsors %}
143
- <div class="card">
144
- <div class="card-header">
145
- <h2>{{ s.display }}</h2>
146
- <span class="category-badge cat-{{ s.category }}">
147
- {% if s.category == "government" %}🏛️ Government
148
- {% elif s.category == "conglomerate" %}🏢 Media Conglomerate
149
- {% elif s.category == "private_equity" %}💰 Private Equity
150
- {% elif s.category == "wealthy_private" %}👤 Wealthy Private Owner
151
- {% elif s.category == "corporate" %}📊 Corporate
152
- {% elif s.category == "independent" %}✅ Independent
153
- {% else %}{{ s.category }}{% endif %}
154
- </span>
155
- </div>
156
- <div class="parent">Parent: {{ s.parent }}</div>
157
- {% if s.bias or s.factuality %}
158
- <div style="display:flex; gap:6px; margin-bottom:12px;">
159
- {% if s.bias %}
160
- <span class="bias-badge bias-{{ s.bias }}">{{ s.bias }}</span>
161
- {% endif %}
162
- {% if s.factuality %}
163
- <span class="factuality-badge factuality-{{ s.factuality }}">{{ s.factuality }}</span>
164
- {% endif %}
165
- </div>
166
- {% endif %}
167
- {% if s.owners %}
168
- <div class="owners">
169
- <h3>Shareholders / Funders</h3>
170
- <div class="owner-list">
171
- {% for owner in s.owners %}
172
- <span class="owner-tag">{{ owner }}</span>
173
- {% endfor %}
174
- </div>
175
- </div>
176
- {% endif %}
177
- <div class="sources">wiki
178
- <h3>{{ s.source_count }} outlet{% if s.source_count != 1 %}s{% endif %} owned</h3>
179
- <div class="source-list">
180
- {% for domain in s.sources %}
181
- <span class="source-tag">{{ domain }}</span>
182
- {% endfor %}
183
- </div>
184
- {% if s.wikipedia %}
185
- <a href="{{ s.wikipedia }}" target="_blank" class="btn" style="display:inline-block;margin-top:12px;padding:8px 16px;background:linear-gradient(to right, #d72924 0%, #db3e3a 51%, #d72924 100%);color:#fff;font-size:13px;font-weight:600;font-family:'IBM Plex Serif',Georgia,serif;text-decoration:none;">📖 Wikipedia</a>
186
- {% endif %}
187
- </div>
188
- </div>
189
- {% endfor %}
190
- </div>
191
- {% else %}
192
- <div class="empty">
193
- <h2>No sponsor data yet</h2>
194
- <p>Run a data refresh to populate ownership information</p>
195
- </div>
196
- {% endif %}
197
- </div>
198
- </body>
199
- </html>