File size: 27,044 Bytes
f56df65
 
 
 
 
 
 
 
 
 
d5c5881
 
6a6a99c
 
 
f56df65
d5c5881
f56df65
 
9ce1f5d
d5c5881
9ce1f5d
 
 
 
 
 
 
 
 
 
d5c5881
 
9ce1f5d
 
d04e05d
 
8010fdc
 
9ce1f5d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f364ce4
 
 
d5c5881
f364ce4
 
d5c5881
 
f364ce4
d5c5881
f364ce4
d5c5881
 
 
 
 
f364ce4
d5c5881
f364ce4
 
d5c5881
f364ce4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9ce1f5d
 
8010fdc
 
 
f56df65
d5c5881
9ce1f5d
d5c5881
 
 
 
 
 
f56df65
8010fdc
d5c5881
 
8010fdc
d5c5881
8010fdc
 
 
 
d5c5881
8010fdc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d5c5881
8010fdc
d5c5881
 
f56df65
d04e05d
d5c5881
 
 
 
 
 
 
 
 
 
 
5188732
d5c5881
 
 
 
 
 
 
 
d04e05d
d5c5881
 
 
d04e05d
d5c5881
 
 
 
 
5188732
d04e05d
 
d5c5881
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d04e05d
f56df65
6a6a99c
d5c5881
f56df65
d5c5881
f56df65
 
 
 
 
 
6a6a99c
d5c5881
 
 
 
 
f56df65
 
 
a3e7d7f
 
 
 
f56df65
 
 
f364ce4
 
 
 
f56df65
 
 
 
 
 
 
 
 
 
 
 
 
 
d5c5881
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3938a94
d5c5881
 
 
 
 
 
 
 
3938a94
 
 
d5c5881
 
 
3938a94
 
d5c5881
3938a94
d5c5881
 
3938a94
f56df65
6a6a99c
f56df65
 
6a6a99c
f56df65
6a6a99c
d5c5881
f56df65
d5c5881
 
 
 
 
 
 
 
 
 
 
 
 
 
f56df65
 
d5c5881
f56df65
 
 
f364ce4
 
 
 
f56df65
 
d5c5881
6a6a99c
d5c5881
f56df65
d5c5881
5b6c5f3
d5c5881
 
f56df65
6a6a99c
 
 
 
d5c5881
 
f56df65
 
6a6a99c
f56df65
6a6a99c
f56df65
d5c5881
 
f56df65
d5c5881
f56df65
5188732
5b6c5f3
f56df65
6a6a99c
 
 
 
f56df65
5188732
 
 
 
 
 
d5c5881
6a6a99c
f56df65
d5c5881
 
 
 
f56df65
f364ce4
5b6c5f3
f56df65
d5c5881
f56df65
f364ce4
 
 
 
6a6a99c
f364ce4
d5c5881
f364ce4
 
 
d5c5881
 
 
 
 
 
 
 
 
6a6a99c
5188732
d5c5881
f56df65
d5c5881
 
 
f56df65
d5c5881
 
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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
import gradio as gr
from typing import Dict, Any, List, Optional
import pandas as pd
import logging

logger = logging.getLogger(__name__)

def create_enhanced_okr_tab():
    """
    Creates a modern, visually appealing OKR tab with improved layout and styling.
    This version includes robust support for Gradio's dark mode with multiple
    detection methods and fallback mechanisms.

    Returns:
        gr.HTML: The Gradio HTML component that will display the formatted OKRs.
    """
    # Enhanced CSS for modern OKR styling with improved Dark Mode support
    okr_custom_css = """
    <style>
    /* ----------------------------------------- */
    /* --- LIGHT MODE THEME & COLOR VARIABLES --- */
    /* ----------------------------------------- */
    :root {
        --okr-bg-start: #667eea;
        --okr-bg-end: #764ba2;
        --header-text-color: white;
        --header-text-shadow: rgba(0,0,0,0.1);
        --stat-card-bg: rgba(255, 255, 255, 0.15);
        --stat-card-bg-hover: rgba(255, 255, 255, 0.2);
        --stat-card-border: rgba(255, 255, 255, 0.2);
        --stat-number-color: #fbbf24;
        --content-bg: white;
        --content-shadow: rgba(0,0,0,0.1);
        --objective-card-bg: #f8fafc;
        --objective-card-border: #3b82f6;
        --objective-shadow: 0 4px 16px rgba(0,0,0,0.05);
        --objective-shadow-hover: 0 8px 24px rgba(0,0,0,0.1);
        --objective-header-bg: transparent;
        --objective-title-color: #1e40af;
        --objective-meta-text-color: #475569;
        --key-result-bg: white;
        --key-result-border: #e5e7eb;
        --key-result-border-hover: #3b82f6;
        --kr-header-bg: #f8fafc;
        --kr-title-color: #1e293b;
        --kr-metric-bg: rgba(59, 130, 246, 0.1);
        --kr-metric-color: #1e40af;
        --kr-metric-border: rgba(59, 130, 246, 0.2);
        --task-item-bg: #f9fafb;
        --task-item-bg-hover: #f3f4f6;
        --task-item-border: #e5e7eb;
        --task-item-border-hover: #d1d5db;
        --task-title-color: #111827;
        --task-detail-label-color: #374151;
        --task-detail-text-color: #6b7280;
        --task-description-bg: white;
        --task-description-border: #3b82f6;
        --task-description-color: #4b5563;
        --priority-high-bg: #fef2f2;
        --priority-high-color: #dc2626;
        --priority-high-border: #fca5a5;
        --priority-medium-bg: #fffbeb;
        --priority-medium-color: #d97706;
        --priority-medium-border: #fcd34d;
        --priority-low-bg: #f0fdf4;
        --priority-low-color: #16a34a;
        --priority-low-border: #86efac;
    }

    /* ----------------------------------------- */
    /* --- DARK MODE COLOR VARIABLES --- */
    /* ----------------------------------------- */
    html.dark {
        --okr-bg-start: #1a1a2e;
        --okr-bg-end: #16213e;
        --header-text-color: #e5e7eb;
        --header-text-shadow: rgba(0,0,0,0.5);
        --stat-card-bg: rgba(28, 28, 28, 0.7);
        --stat-card-bg-hover: rgba(40, 40, 40, 0.8);
        --stat-card-border: rgba(60, 60, 60, 0.8);
        --stat-number-color: #fbbf24;
        --content-bg: #0d1117;
        --content-shadow: rgba(0,0,0,0.6);
        --objective-card-bg: #161b22;
        --objective-card-border: #58a6ff;
        --objective-shadow: 0 8px 24px rgba(0,0,0,0.5);
        --objective-shadow-hover: 0 10px 28px rgba(0,0,0,0.6);
        --objective-header-bg: transparent;
        --objective-title-color: #58a6ff;
        --objective-meta-text-color: #8b949e;
        --key-result-bg: #161b22;
        --key-result-border: #30363d;
        --key-result-border-hover: #58a6ff;
        --kr-header-bg: #161b22;
        --kr-title-color: #c9d1d9;
        --kr-metric-bg: rgba(56, 139, 253, 0.15);
        --kr-metric-color: #58a6ff;
        --kr-metric-border: rgba(56, 139, 253, 0.4);
        --task-item-bg: #21262d;
        --task-item-bg-hover: #30363d;
        --task-item-border: #30363d;
        --task-item-border-hover: #8b949e;
        --task-title-color: #c9d1d9;
        --task-detail-label-color: #8b949e;
        --task-detail-text-color: #8b949e;
        --task-description-bg: #0d1117;
        --task-description-border: #30363d;
        --task-description-color: #8b949e;
        --priority-high-bg: rgba(248, 81, 73, 0.15);
        --priority-high-color: #ff7b72;
        --priority-high-border: rgba(248, 81, 73, 0.4);
        --priority-medium-bg: rgba(210, 153, 34, 0.15);
        --priority-medium-color: #d29922;
        --priority-medium-border: rgba(210, 153, 34, 0.4);
        --priority-low-bg: rgba(63, 185, 80, 0.15);
        --priority-low-color: #56d364;
        --priority-low-border: rgba(63, 185, 80, 0.4);
    }

    /* ----------------------------------------- */
    /* --- BASE STYLES (Uses Variables) --- */
    /* ----------------------------------------- */
    .okr-container {
        font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
        background: linear-gradient(135deg, var(--okr-bg-start) 0%, var(--okr-bg-end) 100%);
        min-height: 100vh;
        padding: 2rem;
        margin: -1rem;
        box-sizing: border-box;
        overflow-y: auto;
        color: var(--header-text-color);
    }
    .okr-header { text-align: center; margin-bottom: 3rem; color: var(--header-text-color); }
    .okr-title { font-size: 2.5rem; font-weight: 700; margin-bottom: 0.5rem; text-shadow: 0 2px 4px var(--header-text-shadow); display: flex; justify-content: center; align-items: center; gap: 0.75rem; color: var(--header-text-color); }
    .okr-title-content { background: linear-gradient(45deg, var(--header-text-color), rgba(255,255,255,0.8)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; color: var(--header-text-color); }
    .okr-title-emoji { font-size: 2.5rem; -webkit-text-fill-color: initial; }
    .okr-subtitle { font-size: 1.2rem; opacity: 0.9; font-weight: 300; letter-spacing: 0.5px; color: var(--header-text-color); }
    .okr-stats-bar { display: flex; justify-content: center; gap: 2rem; margin: 2rem 0; flex-wrap: wrap; }
    .stat-card { background: var(--stat-card-bg); backdrop-filter: blur(10px); border: 1px solid var(--stat-card-border); border-radius: 16px; padding: 1.5rem; text-align: center; color: var(--header-text-color); min-width: 140px; transition: all 0.3s ease; }
    .stat-card:hover { transform: translateY(-2px); background: var(--stat-card-bg-hover); box-shadow: 0 8px 32px var(--content-shadow); }
    .stat-number { font-size: 2rem; font-weight: 700; margin-bottom: 0.25rem; color: var(--stat-number-color); }
    .stat-label { font-size: 0.9rem; opacity: 0.9; text-transform: uppercase; letter-spacing: 1px; color: var(--header-text-color); }
    .okr-content { background: var(--content-bg); border-radius: 24px; padding: 0; box-shadow: 0 20px 40px var(--content-shadow); overflow: hidden; margin-top: 2rem; }
    .okr-objective { background: var(--objective-card-bg); border-left: 6px solid var(--objective-card-border); margin: 2rem; border-radius: 16px; overflow: hidden; box-shadow: var(--objective-shadow); transition: all 0.3s ease; }
    .okr-objective:hover { transform: translateY(-2px); box-shadow: var(--objective-shadow-hover); }
    .objective-header { padding: 2rem; background: var(--objective-header-bg); border-bottom: 1px solid var(--key-result-border); position: relative; }
    .objective-title { font-size: 1.5rem; font-weight: 700; margin-bottom: 0.75rem; position: relative; z-index: 1; color: var(--objective-title-color); }
    .objective-meta { display: flex; gap: 2rem; margin-top: 1rem; flex-wrap: wrap; position: relative; z-index: 1; }
    .meta-item { display: flex; align-items: center; gap: 0.5rem; font-size: 0.9rem; color: var(--objective-meta-text-color); }
    .meta-icon { width: 16px; height: 16px; opacity: 0.8; }
    .key-results-container { padding: 2rem; }
    .key-result { background: var(--key-result-bg); border: 2px solid var(--key-result-border); border-radius: 12px; margin: 1.5rem 0; overflow: hidden; transition: all 0.3s ease; }
    .key-result:hover { border-color: var(--key-result-border-hover); box-shadow: 0 4px 12px rgba(59, 130, 246, 0.1); }
    .kr-header { background: var(--kr-header-bg); padding: 1.5rem; border-bottom: 1px solid var(--key-result-border); }
    .kr-title { font-size: 1.2rem; font-weight: 600; color: var(--kr-title-color); margin-bottom: 0.75rem; }
    .kr-metrics { display: flex; gap: 1.5rem; flex-wrap: wrap; margin-top: 1rem; }
    .kr-metric { background: var(--kr-metric-bg); color: var(--kr-metric-color); padding: 0.5rem 1rem; border-radius: 8px; font-size: 0.85rem; font-weight: 500; border: 1px solid var(--kr-metric-border); }
    .tasks-section { padding: 1.5rem; }
    .tasks-title { font-size: 1rem; font-weight: 600; color: var(--task-detail-label-color); margin-bottom: 1rem; display: flex; align-items: center; gap: 0.5rem; }
    .task-item { background: var(--task-item-bg); border: 1px solid var(--task-item-border); border-radius: 8px; padding: 1.25rem; margin: 1rem 0; transition: all 0.2s ease; }
    .task-item:hover { background: var(--task-item-bg-hover); border-color: var(--task-item-border-hover); }
    .task-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 1rem; gap: 1rem; }
    .task-title { font-weight: 600; color: var(--task-title-color); flex: 1; line-height: 1.4; }
    .task-priority { padding: 0.25rem 0.75rem; border-radius: 12px; font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; white-space: nowrap; }
    .priority-high { background: var(--priority-high-bg); color: var(--priority-high-color); border: 1px solid var(--priority-high-border); }
    .priority-medium { background: var(--priority-medium-bg); color: var(--priority-medium-color); border: 1px solid var(--priority-medium-border); }
    .priority-low { background: var(--priority-low-bg); color: var(--priority-low-color); border: 1px solid var(--priority-low-border); }
    .task-details { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-top: 1rem; }
    .task-detail-item { display: flex; align-items: center; gap: 0.5rem; font-size: 0.875rem; color: var(--task-detail-text-color); }
    .task-detail-label { font-weight: 500; color: var(--task-detail-label-color); min-width: 80px; }
    .task-description { margin-top: 1rem; padding: 1rem; background: var(--task-description-bg); border-radius: 6px; border-left: 3px solid var(--task-description-border); font-size: 0.9rem; line-height: 1.5; color: var(--task-description-color); }
    .empty-state { text-align: center; padding: 4rem 2rem; color: var(--task-detail-text-color); }
    .empty-state-icon { font-size: 3rem; margin-bottom: 1rem; }
    .empty-state-title { font-size: 1.5rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--objective-title-color); }
    .loading-spinner { display: inline-block; width: 20px; height: 20px; border: 3px solid var(--task-item-bg-hover); border-radius: 50%; border-top-color: var(--objective-card-border); animation: spin 1s ease-in-out infinite; }
    @keyframes spin { to { transform: rotate(360deg); } }
    @media (max-width: 768px) { .okr-container { padding: 1rem; } .okr-title { font-size: 2rem; } .okr-stats-bar { gap: 1rem; } .stat-card { min-width: 120px; padding: 1rem; } .objective-meta { flex-direction: column; gap: 1rem; } .task-details { grid-template-columns: 1fr; } .task-header { flex-direction: column; align-items: flex-start; } }
    @supports (-webkit-appearance: none) { .okr-title-content { color: var(--header-text-color) !important; } }
    </style>
    <script>
        (function() {
            'use strict';
            // Function to apply the theme to the current document's HTML element
            function applyTheme(theme) {
                const htmlEl = document.documentElement;
                if (theme === 'dark') {
                    htmlEl.classList.add('dark');
                } else {
                    htmlEl.classList.remove('dark');
                }
            }

            // Function to detect the theme from the parent Gradio app
            function detectAndApplyTheme() {
                try {
                    // Access the parent document where Gradio's theme class is set
                    const parentHtml = window.parent.document.querySelector('html');
                    if (parentHtml) {
                        const isDark = parentHtml.classList.contains('dark');
                        applyTheme(isDark ? 'dark' : 'light');
                    } else {
                         // Fallback for system preference if parent is not accessible
                        const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
                        applyTheme(prefersDark ? 'dark' : 'light');
                    }
                } catch (e) {
                    console.warn('OKR Tab: Could not access parent theme. Falling back to system preference.', e.message);
                    // Fallback for system preference in case of cross-origin issues
                    const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
                    applyTheme(prefersDark ? 'dark' : 'light');
                }
            }

            // Use a MutationObserver to watch for theme changes on the parent
            function observeThemeChanges() {
                try {
                    const parentHtml = window.parent.document.querySelector('html');
                    if (!parentHtml) return;

                    const observer = new MutationObserver(mutations => {
                        mutations.forEach(mutation => {
                            if (mutation.attributeName === 'class') {
                                detectAndApplyTheme();
                            }
                        });
                    });

                    observer.observe(parentHtml, {
                        attributes: true,
                        attributeFilter: ['class']
                    });
                } catch (e) {
                     console.warn('OKR Tab: Cannot observe parent theme changes.', e.message);
                }
            }
            
            // Initial check when the script loads
            if (document.readyState === 'loading') {
                document.addEventListener('DOMContentLoaded', () => {
                    detectAndApplyTheme();
                    observeThemeChanges();
                });
            } else {
                detectAndApplyTheme();
                observeThemeChanges();
            }
             // Also listen for system theme changes as a fallback
            window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', detectAndApplyTheme);
        })();
    </script>
    """
    with gr.Column(elem_classes=["okr-root-column"]):
        # Inject custom CSS and the enhanced theme-syncing JS
        gr.HTML(okr_custom_css)
        # Main OKR display area with enhanced styling
        okr_display_html = gr.HTML(
            value=get_initial_okr_display(),
            elem_classes=["okr-display"]
        )
    return okr_display_html

def get_initial_okr_display() -> str:
    """
    Returns the initial HTML display for the OKR tab, showing a loading state.
    Returns:
        str: HTML string for the initial OKR display.
    """
    return """
    <div class="okr-container">
        <div class="okr-header">
            <div class="okr-title">
                <span class="okr-title-emoji">🎯</span>
                <span class="okr-title-content">AI-Generated OKRs & Strategic Tasks</span>
            </div>
            <div class="okr-subtitle">Intelligent objectives and key results based on your LinkedIn analytics</div>
        </div>
        <div class="okr-stats-bar">
            <div class="stat-card"><div class="stat-number">-</div><div class="stat-label">Objectives</div></div>
            <div class="stat-card"><div class="stat-number">-</div><div class="stat-label">Key Results</div></div>
            <div class="stat-card"><div class="stat-number">-</div><div class="stat-label">Tasks</div></div>
            <div class="stat-card"><div class="stat-number">-</div><div class="stat-label">High Priority</div></div>
        </div>
        <div class="okr-content">
            <div class="empty-state">
                <div class="empty-state-icon">⏳</div>
                <div class="empty-state-title">Loading OKR Analysis</div>
                <div class="empty-state-description">
                    <div class="loading-spinner"></div>
                    Generating intelligent objectives and actionable tasks from your LinkedIn data...
                </div>
            </div>
        </div>
    </div>
    """

def get_empty_okr_state() -> str:
    """Returns empty state HTML for when no OKRs are available, using the new styles."""
    return """
    <div class="okr-container">
        <div class="okr-header">
            <div class="okr-title">
                <span class="okr-title-emoji">🎯</span>
                <span class="okr-title-content">AI-Generated OKRs & Strategic Tasks</span>
            </div>
            <div class="okr-subtitle">Intelligent objectives and key results based on your LinkedIn analytics</div>
        </div>
        <div class="okr-stats-bar">
            <div class="stat-card"><div class="stat-number">0</div><div class="stat-label">Objectives</div></div>
            <div class="stat-card"><div class="stat-number">0</div><div class="stat-label">Key Results</div></div>
            <div class="stat-card"><div class="stat-number">0</div><div class="stat-label">Tasks</div></div>
            <div class="stat-card"><div class="stat-number">0</div><div class="stat-label">High Priority</div></div>
        </div>
        <div class="okr-content">
            <div class="empty-state">
                <div class="empty-state-icon">πŸ“‹</div>
                <div class="empty-state-title">No OKRs Available</div>
                <div class="empty-state-description">
                    OKR analysis has not been generated yet or no data is available.<br>
                    Please ensure your LinkedIn data has been loaded and the AI analysis has completed.
                </div>
            </div>
        </div>
    </div>
    """

def format_okrs_for_enhanced_display(reconstruction_cache: dict) -> str:
    """
    Enhanced formatting function that creates beautiful HTML for OKR display
    from the reconstruction cache dictionary, compatible with new styling.
    Args:
        reconstruction_cache (dict): The dictionary containing 'actionable_okrs'.
    Returns:
        str: A comprehensive HTML string representing the OKRs, or an empty state HTML.
    """
    if not reconstruction_cache:
        logger.warning("No reconstruction cache found for display.")
        return get_empty_okr_state()
    
    actionable_okrs = {}
    # Extract actionable_okrs from the cache
    for report_id, report_data in reconstruction_cache.items():
        if isinstance(report_data, dict) and 'actionable_okrs' in report_data:
            actionable_okrs = report_data['actionable_okrs']
            break
    
    if not actionable_okrs:
        logger.warning("No 'actionable_okrs' found in reconstruction cache for display.")
        return get_empty_okr_state()

    okrs_list = actionable_okrs.get("okrs", [])
    if not okrs_list:
        logger.info("No OKRs found in 'actionable_okrs' list.")
        return get_empty_okr_state()

    # Calculate statistics for the stats bar
    total_objectives = len(okrs_list)
    total_key_results = sum(len(okr.get('key_results', [])) for okr in okrs_list if isinstance(okr, dict))
    total_tasks = sum(
        len(kr.get('tasks', []))
        for okr in okrs_list if isinstance(okr, dict)
        for kr in okr.get('key_results', []) if isinstance(kr, dict)
    )
    high_priority_tasks = sum(
        1 for okr in okrs_list if isinstance(okr, dict)
        for kr in okr.get('key_results', []) if isinstance(kr, dict)
        for task in kr.get('tasks', []) if isinstance(task, dict) and task.get('priority', '').lower() == 'high'
    )

    # --- Start HTML Generation ---
    html_parts = [f"""
    <div class="okr-container">
        <div class="okr-header">
            <div class="okr-title"><span class="okr-title-emoji">🎯</span><span class="okr-title-content">AI-Generated OKRs & Strategic Tasks</span></div>
            <div class="okr-subtitle">Intelligent objectives and key results based on your LinkedIn analytics</div>
        </div>
        <div class="okr-stats-bar">
            <div class="stat-card"><div class="stat-number">{total_objectives}</div><div class="stat-label">Objectives</div></div>
            <div class="stat-card"><div class="stat-number">{total_key_results}</div><div class="stat-label">Key Results</div></div>
            <div class="stat-card"><div class="stat-number">{total_tasks}</div><div class="stat-label">Tasks</div></div>
            <div class="stat-card"><div class="stat-number">{high_priority_tasks}</div><div class="stat-label">High Priority</div></div>
        </div>
        <div class="okr-content">
    """]

    # --- Loop through Objectives ---
    for okr_idx, okr_data in enumerate(okrs_list):
        if not isinstance(okr_data, dict): continue
        objective = okr_data.get('description', f"Unnamed Objective {okr_idx + 1}")
        timeline = okr_data.get('timeline', 'N/A')
        owner = okr_data.get('owner', 'N/A')
        html_parts.append(f"""
            <div class="okr-objective">
                <div class="objective-header">
                    <div class="objective-title">Objective {okr_idx + 1}: {objective}</div>
                    <div class="objective-meta">
                        <div class="meta-item"><span class="meta-icon">⏰</span> <span>Timeline: {timeline}</span></div>
                        <div class="meta-item"><span class="meta-icon">πŸ‘€</span> <span>Owner: {owner}</span></div>
                    </div>
                </div>
                <div class="key-results-container">
        """)

        key_results = okr_data.get('key_results', [])
        if not key_results:
            html_parts.append('<div class="empty-state" style="padding: 2rem;"><div class="empty-state-title" style="font-size: 1.1rem;">No Key Results</div><div class="empty-state-description">No key results defined for this objective.</div></div>')
        else:
            # --- Loop through Key Results ---
            for kr_idx, kr_data in enumerate(key_results):
                if not isinstance(kr_data, dict): continue
                kr_desc = kr_data.get('description', f"Unnamed Key Result {kr_idx + 1}")
                html_parts.append(f"""
                    <div class="key-result">
                        <div class="kr-header">
                            <div class="kr-title">Key Result {kr_idx + 1}: {kr_desc}</div>
                            <div class="kr-metrics">
                """)
                if kr_data.get('target_metric') and kr_data.get('target_value'):
                    html_parts.append(f'<div class="kr-metric">Target: {kr_data.get("target_metric")} β†’ {kr_data.get("target_value")}</div>')
                if kr_data.get('key_result_type'):
                    html_parts.append(f'<div class="kr-metric">Type: {kr_data.get("key_result_type")}</div>')
                if kr_data.get('data_subject'):
                    html_parts.append(f'<div class="kr-metric">Data Subject: {kr_data.get("data_subject")}</div>')
                html_parts.append('</div></div>') # Close kr-metrics & kr-header

                tasks = kr_data.get('tasks', [])
                html_parts.append('<div class="tasks-section">')
                if tasks:
                    html_parts.append('<div class="tasks-title"><span>πŸ“‹</span><span>Associated Tasks</span></div>')
                    # --- Loop through Tasks ---
                    for task_idx, task_data in enumerate(tasks):
                        if not isinstance(task_data, dict): continue
                        task_desc = task_data.get('description', f"Unnamed Task {task_idx + 1}")
                        priority = task_data.get('priority', 'Medium').lower()
                        priority_class = f"priority-{priority}"
                        html_parts.append(f"""
                                <div class="task-item">
                                    <div class="task-header">
                                        <div class="task-title">{task_idx + 1}. {task_desc}</div>
                                        <div class="task-priority {priority_class}">{priority.upper()}</div>
                                    </div>
                                    <div class="task-details">
                                        <div class="task-detail-item"><span class="task-detail-label">Category:</span><span>{task_data.get('category', 'N/A')}</span></div>
                                        <div class="task-detail-item"><span class="task-detail-label">Effort:</span><span>{task_data.get('effort', 'N/A')}</span></div>
                                        <div class="task-detail-item"><span class="task-detail-label">Timeline:</span><span>{task_data.get('timeline', 'N/A')}</span></div>
                                        <div class="task-detail-item"><span class="task-detail-label">Responsible:</span><span>{task_data.get('responsible_party', 'N/A')}</span></div>
                                    </div>""")
                        detail_items = {
                            'Deliverable': task_data.get('deliverable'),
                            'Success Metrics': task_data.get('success_criteria_metrics'),
                            'Why': task_data.get('why'),
                            'Priority Justification': task_data.get('priority_justification'),
                            'Dependencies': task_data.get('dependencies')
                        }
                        detail_lines = [f'<strong>{k}:</strong> {v}' for k, v in detail_items.items() if v]
                        if detail_lines:
                            html_parts.append(f'<div class="task-description">{"<br>".join(detail_lines)}</div>')
                        html_parts.append('</div>') # Close task-item
                else:
                    html_parts.append('<div style="text-align:center; padding: 1rem; color: var(--task-detail-text-color);">No tasks defined for this key result.</div>')
                html_parts.append('</div></div>') # Close tasks-section & key-result
        html_parts.append('</div></div>') # Close key-results-container & okr-objective

    html_parts.append('</div></div>') # Close okr-content & okr-container
    return ''.join(html_parts)