File size: 63,735 Bytes
f4feab0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
/* ═══════════════════════════════════════════════════════════
   POWERPATH β€” Power BI Developer Learning SPA
   Production-Grade JavaScript Module
   
   Architecture:
     1. DATA        β€” All tool content, structured & extensible
     2. NAV ENGINE  β€” Section switching, progress bar, active states
     3. RENDERER    β€” Card & hiring section dynamic rendering
     4. MODAL       β€” Tool detail expansion panel
     5. INIT        β€” Bootstrap & event binding
   ═══════════════════════════════════════════════════════════ */

(function () {
  'use strict';

  /* ═════════════════════════════════════════════════════════
     1. DATA LAYER β€” All tool content
       Each tool object shape:
       {
         name:       string,
         icon:       string (emoji/unicode),
         purpose:    string,
         when:       string,
         level:      'beginner' | 'intermediate' | 'advanced',
         example:    string,
         best:       string,
         mistake:    string,
         accentColor:string (CSS color, optional)
       }
     ═════════════════════════════════════════════════════════ */

  const DATA = {

    /* ── Core Power BI Tools ── */
    coretools: [
      {
        name: 'Power BI Desktop',
        icon: 'πŸ–₯️',
        purpose: 'The primary authoring tool for building reports, models, and dashboards locally before publishing.',
        when: 'Every time a developer creates or edits a report. It is the starting point for 90% of Power BI work.',
        level: 'beginner',
        example: 'A finance team uses Power BI Desktop to build a monthly P&L report pulling from SQL Server, applying transformations in Power Query, writing DAX measures, and designing the visual layout before publishing to Power BI Service.',
        best: 'Always design your data model first β€” spend time on star schema and clean relationships before touching a single visual.',
        mistake: 'Jumping straight into visuals without a clean data model underneath, which leads to slow performance and fragile reports.',
        accentColor: '#38a2ff'
      },
      {
        name: 'Power BI Service',
        icon: '☁️',
        purpose: 'The cloud-based platform where reports are published, shared, and consumed by end users across the organization.',
        when: 'After building in Desktop, the developer publishes here for distribution. Also used for creating dashboards by pinning visuals.',
        level: 'beginner',
        example: 'An analytics team publishes weekly KPI reports to Power BI Service, creates a pinned dashboard for the CEO, and sets up scheduled refresh from an on-premises SQL database via a gateway.',
        best: 'Use workspaces to organize reports by department or project. Leverage Apps for controlled distribution to large audiences.',
        mistake: 'Treating Service as just a "place to upload." It has its own powerful features like dataflows, scheduled refresh, and governance controls.',
        accentColor: '#38a2ff'
      },
      {
        name: 'Power BI Mobile',
        icon: 'πŸ“±',
        purpose: 'iOS and Android apps that let end users view dashboards, receive alerts, and consume reports on-the-go.',
        when: 'Used by executives and field workers who need real-time KPIs on their phones or tablets.',
        level: 'beginner',
        example: 'A regional sales manager receives a push alert on their iPhone when Q3 revenue drops below a threshold, then drills into the Power BI Mobile dashboard to investigate by region.',
        best: 'Design mobile-optimized views in Power BI Desktop using the "Mobile layout" designer to ensure dashboards look great on small screens.',
        mistake: 'Ignoring mobile entirely. Enterprise clients increasingly demand mobile-first experiences, and poorly sized visuals frustrate users.',
        accentColor: '#7dd3fc'
      },
      {
        name: 'On-Premises Data Gateway',
        icon: 'πŸ”—',
        purpose: 'Acts as a bridge to allow Power BI Service to access data sources that live inside a corporate firewall.',
        when: 'Whenever the organization has SQL Server, files, or other data sources that are not in the cloud and need to refresh reports in Service.',
        level: 'intermediate',
        example: 'An enterprise deploys a gateway on a server inside their data center. Power BI Service uses it to reach an on-premises SQL Server warehouse every night for scheduled refresh.',
        best: 'Install gateways on dedicated, always-on servers with stable network connections. Use gateway clusters for high availability.',
        mistake: 'Installing the gateway on a developer\'s personal laptop. When that laptop is off or slow, all scheduled refreshes fail silently.',
        accentColor: '#a78bfa'
      }
    ],

    /* ── Data Sources & Databases ── */
    datasources: [
      {
        name: 'SQL Server',
        icon: 'πŸ—„οΈ',
        purpose: 'Microsoft\'s relational database engine β€” the most common enterprise data source for Power BI.',
        when: 'Used whenever a company stores structured transactional or analytical data in a Microsoft SQL environment.',
        level: 'beginner',
        example: 'A retail company stores all sales orders in SQL Server. The Power BI developer writes optimized T-SQL queries with aggregations to pull summary data efficiently.',
        best: 'Never pull entire tables. Write server-side queries or use DirectQuery for large fact tables to keep model size small and refresh fast.',
        mistake: 'Using Import mode on a table with 500M+ rows. This crashes Desktop and balloons file size. Use DirectQuery or aggregation.',
        accentColor: '#38a2ff'
      },
      {
        name: 'Azure SQL',
        icon: 'β›…',
        purpose: 'Managed, cloud-hosted relational database β€” the Azure equivalent of SQL Server, with built-in HA and scaling.',
        when: 'Enterprises that have migrated or built new data platforms in Azure, or need a serverless SQL option.',
        level: 'intermediate',
        example: 'A SaaS company stores customer analytics in Azure SQL. Power BI connects via DirectQuery, leveraging Azure\'s auto-scaling for peak reporting hours.',
        best: 'Use parameterized queries and connection pooling. Leverage Azure SQL\'s built-in intelligent performance tuning.',
        mistake: 'Treating Azure SQL identically to on-prem SQL Server. Connection strings, auth (AAD vs. SQL Auth), and networking (VNet) are all different.',
        accentColor: '#7dd3fc'
      },
      {
        name: 'MySQL / PostgreSQL',
        icon: '🐬',
        purpose: 'Open-source relational databases commonly used in web applications and mixed-stack enterprise environments.',
        when: 'When the organization uses Linux-based stacks, e-commerce platforms, or SaaS tools backed by PostgreSQL or MySQL.',
        level: 'intermediate',
        example: 'A fintech startup stores user transaction data in PostgreSQL on AWS. The Power BI developer uses the PostgreSQL connector in Desktop to import aggregated reports.',
        best: 'Use native connectors in Power BI. For large tables, push down aggregation queries to the database using custom SQL sources.',
        mistake: 'Importing millions of rows from MySQL into Power BI. Always aggregate and filter at the database level first.',
        accentColor: '#34d399'
      },
      {
        name: 'Oracle Database',
        icon: 'πŸ›οΈ',
        purpose: 'Oracle\'s RDBMS β€” still dominant in large financial institutions, telecom, and government enterprises.',
        when: 'When the organization runs legacy or large-scale ERP systems (like Oracle E-Business Suite or PeopleSoft).',
        level: 'intermediate',
        example: 'A global bank pulls financial ledger data from an Oracle 19c database using the Power BI Oracle connector with optimized PL/SQL queries.',
        best: 'Ensure the Oracle Data Provider for .NET (ODP.NET) is properly installed. Use server-side queries to limit data transfer.',
        mistake: 'Forgetting driver installation. Power BI requires ODP.NET on the machine β€” without it, the Oracle connector simply will not appear.',
        accentColor: '#fb7185'
      },
      {
        name: 'Excel / CSV Files',
        icon: 'πŸ“Š',
        purpose: 'Flat-file data sources. Excel remains a critical data input in many enterprise reporting scenarios.',
        when: 'For small reference tables, manual inputs, or legacy reports that haven\'t been migrated to a database.',
        level: 'beginner',
        example: 'A finance team maintains a mapping table in Excel (product codes β†’ product names). The developer links this to the main SQL model as a dimension lookup.',
        best: 'Use Excel as a lookup or reference table only. Never build a primary data model on a single Excel file β€” it is error-prone and unscalable.',
        mistake: 'Building an entire report on a 50-sheet Excel workbook. This is brittle. Migrate core data to SQL or a proper data warehouse first.',
        accentColor: '#34d399'
      },
      {
        name: 'APIs (JSON / REST)',
        icon: 'πŸ”„',
        purpose: 'Web APIs expose data from third-party platforms. Power BI can consume REST endpoints to integrate external data.',
        when: 'When you need data from SaaS tools (CRM, marketing, project management) that expose an API.',
        level: 'advanced',
        example: 'A marketing team integrates Salesforce CRM data via its REST API into Power BI. The developer uses Power Query\'s Web.Contents function to paginate through API responses.',
        best: 'Handle pagination, rate limiting, and authentication (OAuth, Bearer tokens) in Power Query. Cache API responses where possible.',
        mistake: 'Calling an API directly on every refresh without caching. This causes timeouts, rate-limit errors, and incredibly slow refresh times.',
        accentColor: '#a78bfa'
      }
    ],

    /* ── Data Transformation & Modeling ── */
    transformation: [
      {
        name: 'Power Query Editor',
        icon: 'πŸ”§',
        purpose: 'The visual, step-based ETL tool inside Power BI for cleaning, shaping, and transforming raw data before analysis.',
        when: 'Every time data needs cleaning: removing nulls, splitting columns, pivoting, merging sources, or filtering rows.',
        level: 'beginner',
        example: 'A developer imports raw sales CSV files, uses Power Query to remove header rows, split date strings, fill null regions from a lookup, and merge with a customer table before loading.',
        best: 'Keep transformation steps minimal and readable. Name each step clearly. Avoid nested custom functions unless absolutely necessary.',
        mistake: 'Doing heavy transformations in DAX that should be done in Power Query. If you\'re reshaping data, Power Query is always the right place.',
        accentColor: '#fbbf24'
      },
      {
        name: 'M Language',
        icon: '{ }',
        purpose: 'The functional programming language behind Power Query. Writing M code gives you full programmatic control over data transformation.',
        when: 'When the visual Power Query UI is insufficient β€” dynamic queries, complex APIs, conditional logic, or reusable functions.',
        level: 'advanced',
        example: 'An engineer writes a recursive M function that dynamically paginates through a REST API, collecting all pages of JSON responses into a single table.',
        best: 'Learn M fundamentals: let-in expressions, function syntax, List and Table libraries. Write reusable functions and store them as shared queries.',
        mistake: 'Avoiding M entirely and relying only on the UI. Power Query\'s most powerful capabilities (dynamic sources, parameterized queries) require M code.',
        accentColor: '#fbbf24'
      },
      {
        name: 'Relationships',
        icon: 'πŸ”€',
        purpose: 'Define how tables connect in the data model. Relationships drive how Power BI calculates and filters across tables.',
        when: 'Whenever you have multiple tables in your model β€” facts, dimensions, bridge tables, or lookup tables.',
        level: 'beginner',
        example: 'A developer creates a star schema with a Sales fact table and Date, Product, and Customer dimension tables, linking them via single-key many-to-one relationships.',
        best: 'Default to single-directional (one-to-many) relationships from dimension to fact. Avoid bidirectional filtering unless absolutely required.',
        mistake: 'Creating many-to-many relationships without understanding the cartesian product explosion this causes in DAX calculations.',
        accentColor: '#38a2ff'
      },
      {
        name: 'Star Schema',
        icon: '⭐',
        purpose: 'The foundational modeling pattern: one central fact table surrounded by dimension tables. The gold standard for Power BI models.',
        when: 'Every time you are designing or refactoring a Power BI data model. Star schema is the default approach.',
        level: 'intermediate',
        example: 'An analyst refactors a messy, flat import into a proper star schema with a normalized Sales fact table, and separate Date, Geography, Product, and Employee dimension tables.',
        best: 'Flatten your dimensions β€” avoid snowflaking in Power BI. One fact table, clean dimensions, clear grain. Keep it simple and performant.',
        mistake: 'Building a snowflake schema (normalized dimensions) in Power BI. It reduces performance and makes DAX far more complex than necessary.',
        accentColor: '#34d399'
      },
      {
        name: 'Composite Models',
        icon: '🧩',
        purpose: 'Allows mixing Import and DirectQuery tables in a single model, plus connecting to Power BI datasets and dataflows.',
        when: 'For large enterprise models where some data must stay in DirectQuery (live) and other reference data can be imported for speed.',
        level: 'advanced',
        example: 'An enterprise model imports small, slowly-changing dimension tables for speed but keeps the massive fact table in DirectQuery against Azure Synapse.',
        best: 'Use Import mode for small dimensions and DirectQuery for large facts. This hybrid gives you the performance benefits of both approaches.',
        mistake: 'Putting everything in DirectQuery and then wondering why the report is sluggish. Composite models exist precisely to solve this.',
        accentColor: '#a78bfa'
      }
    ],

    /* ── DAX & Analytics ── */
    dax: [
      {
        name: 'DAX Basics',
        icon: 'βˆ‘',
        purpose: 'Data Analysis Expressions β€” the formula language used to create custom calculations and aggregations in Power BI.',
        when: 'Whenever a calculated metric, KPI, or derived insight is needed beyond what the raw data provides.',
        level: 'beginner',
        example: 'A developer writes SUM(Sales[Revenue]) as a measure and uses AVERAGE() and COUNTROWS() to build a KPI scorecard for the executive dashboard.',
        best: 'Always create measures instead of calculated columns for aggregations. Measures are evaluated in context; calculated columns are row-level.',
        mistake: 'Using calculated columns where measures should be used. This wastes model memory and leads to incorrect results in many contexts.',
        accentColor: '#fb7185'
      },
      {
        name: 'Measures vs. Calculated Columns',
        icon: 'βš–οΈ',
        purpose: 'Understanding the fundamental difference between these two DAX constructs is critical for correct, efficient Power BI models.',
        when: 'Every time you decide whether to add logic as a new column or as an aggregation formula.',
        level: 'beginner',
        example: 'A developer correctly uses a measure (SUMPRODUCT) for "Revenue per Category" in a report, instead of adding a calculated column that would duplicate data across rows.',
        best: 'Rule of thumb: if it needs to aggregate, it is a measure. If it is a row-level attribute (like a text lookup), a calculated column may be appropriate.',
        mistake: 'Creating calculated columns with SUMIF-style logic. This is a classic mistake β€” that logic belongs in a measure and will give wrong results as a column.',
        accentColor: '#fb7185'
      },
      {
        name: 'Time Intelligence',
        icon: 'πŸ“…',
        purpose: 'A suite of DAX functions for date-based comparisons: year-over-year, month-to-date, rolling periods, and more.',
        when: 'Anytime a report needs temporal comparisons β€” which is almost every business report in existence.',
        level: 'intermediate',
        example: 'A finance dashboard uses SAMEPERIODLASTYEAR to calculate YoY revenue growth, DATESYTD for cumulative annual totals, and a custom rolling-12-month measure using DATESINPERIOD.',
        best: 'Always have a clean, contiguous Date table that covers your entire data range. Mark it as a date table. Time intelligence requires this.',
        mistake: 'Forgetting to mark the Date table, or having gaps in the date range. Time intelligence functions silently return wrong results in these cases.',
        accentColor: '#fbbf24'
      },
      {
        name: 'Variables (VAR)',
        icon: 'VAR',
        purpose: 'Let you store intermediate calculation results inside a DAX expression, improving readability and performance.',
        when: 'Whenever a DAX formula references the same sub-calculation multiple times, or when building complex nested expressions.',
        level: 'intermediate',
        example: 'A developer rewrites a nested 5-line CALCULATE into a clean VAR/RETURN block: VAR TotalSales = SUM(...); VAR PriorSales = CALCULATE(...); RETURN DIVIDE(TotalSales - PriorSales, PriorSales).',
        best: 'Use VAR for any sub-expression referenced more than once. It evaluates only once, cutting calculation time and making the formula maintainable.',
        mistake: 'Writing deeply nested DAX without variables. Not only is it unreadable, but repeated sub-expressions are calculated multiple times.',
        accentColor: '#7dd3fc'
      },
      {
        name: 'Advanced DAX Patterns',
        icon: '⚑',
        purpose: 'Sophisticated DAX techniques: iterators (SUMPRODUCT, AVERAGEIF via X functions), context transitions, and CALCULATE nuances.',
        when: 'Senior-level work: complex business logic, conditional aggregations, dynamic segmentation, and performance-critical formulas.',
        level: 'advanced',
        example: 'An engineer implements a "Top N Customers" dynamic measure using TOPN + CALCULATE with context transition, returning the revenue share of the top 10 customers per region.',
        best: 'Master CALCULATE and context transition. Understand row context vs. filter context deeply β€” 90% of advanced DAX problems stem from context confusion.',
        mistake: 'Using SUMPRODUCT as a workaround without understanding X functions (SUMX, AVERAGEX). X functions are cleaner, faster, and the correct tool.',
        accentColor: '#fb7185'
      },
      {
        name: 'DAX Studio',
        icon: 'πŸ”¬',
        purpose: 'A free, external tool for writing, testing, and profiling DAX formulas against a live Power BI model.',
        when: 'When debugging slow measures, testing complex DAX outside of Power BI Desktop, or benchmarking performance.',
        level: 'advanced',
        example: 'A developer uses DAX Studio\'s Server Timings to discover that a CALCULATE measure is taking 4 seconds. They refactor using aggregation and bring it to 0.05s.',
        best: 'Integrate DAX Studio into your daily workflow for all complex measure development. Use its "Server Timings" to profile and optimize.',
        mistake: 'Ignoring DAX Studio entirely and trying to debug complex measures only inside Power BI Desktop, which gives almost no performance feedback.',
        accentColor: '#a78bfa'
      },
      {
        name: 'Tabular Editor',
        icon: 'πŸ“',
        purpose: 'A lightweight external editor for Power BI models. Enables fast editing of measures, tables, and metadata without loading the full model.',
        when: 'When you need to rapidly edit measures, rename columns, update metadata, or manage large models with hundreds of objects.',
        level: 'advanced',
        example: 'An engineer uses Tabular Editor 3 to bulk-rename 50 measures, write a Best Practices Analyzer rule to enforce naming conventions, and push changes to an Azure Analysis Services model.',
        best: 'Use Tabular Editor alongside DAX Studio. It handles model structure and metadata; DAX Studio handles formula writing and profiling.',
        mistake: 'Not knowing Tabular Editor exists. It dramatically reduces the time spent editing models, especially for advanced or large enterprise projects.',
        accentColor: '#7dd3fc'
      }
    ],

    /* ── Visualization & UI ── */
    visualization: [
      {
        name: 'Built-in Visuals',
        icon: 'πŸ“ˆ',
        purpose: 'Power BI\'s native chart types: bar, line, pie, matrix, card, map, scatter, and more. The visual vocabulary of reporting.',
        when: 'For all standard data presentation needs. The majority of any report will use these native visuals.',
        level: 'beginner',
        example: 'A sales dashboard uses a clustered bar chart for regional sales, a line chart for monthly trends, a card visual for total revenue KPI, and a map for geographic distribution.',
        best: 'Choose the right chart for the story. Bar = comparison; Line = trend over time; Card = single KPI; Matrix = multi-dimensional detail. Avoid pie charts for >5 slices.',
        mistake: 'Using pie charts for datasets with many categories, or stacking too many series on a line chart. This hides the insight rather than revealing it.',
        accentColor: '#38a2ff'
      },
      {
        name: 'Custom Visuals',
        icon: '🎨',
        purpose: 'Third-party or organization-specific visuals built with D3.js or React. Extend Power BI beyond its native chart types.',
        when: 'When a native visual cannot represent the data story effectively β€” complex hierarchies, custom animations, branded layouts.',
        level: 'intermediate',
        example: 'A developer deploys a custom Sankey chart from AppSource to show customer journey flow, and a branded waterfall visual matching the company\'s design system.',
        best: 'Evaluate custom visuals for performance and security before deploying. Certified visuals have been vetted by Microsoft β€” prefer those.',
        mistake: 'Adding 10+ custom visuals to a single report. Each one adds load time and complexity. Use them sparingly and purposefully.',
        accentColor: '#a78bfa'
      },
      {
        name: 'Tooltips',
        icon: 'πŸ’¬',
        purpose: 'Custom tooltip pages that appear on hover over a visual, showing detailed drill-down information in a rich, designed layout.',
        when: 'When a single visual needs richer context on hover β€” detailed breakdowns, sparklines, or formatted narratives.',
        level: 'intermediate',
        example: 'A product dashboard shows a simple bar chart, but hovering over any bar triggers a custom tooltip page showing a mini-dashboard with sparklines, category breakdown, and trend for that product.',
        best: 'Design tooltip pages at a small, fixed size. Keep them focused β€” 2–3 visuals max. They should add context, not replace the main visual.',
        mistake: 'Overfilling tooltips with too much information, making them feel like a second report. Tooltips should be quick, digestible glances.',
        accentColor: '#34d399'
      },
      {
        name: 'Drill-through',
        icon: 'πŸ”',
        purpose: 'Enables users to right-click a data point and navigate to a detail page pre-filtered to that specific context.',
        when: 'When executive-level summary pages need to link to operational detail pages for deeper investigation.',
        level: 'intermediate',
        example: 'A CFO clicks drill-through on a region in the summary dashboard and lands on a detailed P&L page pre-filtered to "North America," showing all line items.',
        best: 'Keep drill-through targets clean and purposeful. Each drill-through page should answer a specific follow-up question the user would naturally ask.',
        mistake: 'Adding drill-through everywhere without clear purpose. Users get confused and disoriented if every element drills through to an unstructured page.',
        accentColor: '#fbbf24'
      },
      {
        name: 'Bookmarks',
        icon: 'πŸ”–',
        purpose: 'Save and restore specific report states β€” filter selections, visual visibility, and slicer positions β€” for navigation and storytelling.',
        when: 'For creating toggle buttons, step-by-step story flows, interactive infographics, and show/hide scenarios.',
        level: 'intermediate',
        example: 'A developer uses bookmarks with action buttons to create a "What-If" toggle: clicking "Scenario A" vs "Scenario B" instantly swaps the entire dashboard\'s data context.',
        best: 'Name bookmarks descriptively. When using for story navigation, order them logically. Pair with action shapes/buttons for a polished UX.',
        mistake: 'Using bookmarks ad-hoc without a clear plan. They can conflict with each other unpredictably if visibility and filter states aren\'t managed carefully.',
        accentColor: '#7dd3fc'
      },
      {
        name: 'UX Storytelling',
        icon: 'πŸ“–',
        purpose: 'The art of structuring Power BI reports so data tells a narrative: from situation, to complication, to resolution.',
        when: 'Every time you design a report. The best data visualizations are always grounded in a clear communication story.',
        level: 'advanced',
        example: 'Instead of dumping 15 charts on one page, an analyst designs a 3-page story: Page 1 β€” "What happened?" (KPIs & trends); Page 2 β€” "Why?" (root cause analysis); Page 3 β€” "What now?" (action items and forecasts).',
        best: 'Lead with the key insight, not the data. Use whitespace. Limit visuals per page to 4–5. Guide the eye with size, color hierarchy, and layout flow.',
        mistake: 'Treating every page as an independent dashboard. Without narrative flow, reports feel like data dumps rather than insights.',
        accentColor: '#fb7185'
      }
    ],

    /* ── Performance Optimization ── */
    performance: [
      {
        name: 'Performance Analyzer',
        icon: '⏱️',
        purpose: 'A built-in Power BI Desktop tool that records and logs the query execution time for every visual on a report page.',
        when: 'Whenever a report feels slow or a new visual seems to be dragging load time. First diagnostic step, always.',
        level: 'intermediate',
        example: 'A developer runs Performance Analyzer and discovers that a single matrix visual takes 8 seconds to load because its DAX query scans 200M rows without any aggregation.',
        best: 'Run Performance Analyzer on every report before publishing. It is free intelligence. Sort by longest DAX query to identify your targets.',
        mistake: 'Optimizing blindly by guessing. Performance Analyzer gives you exact timings β€” always use data, never assumptions.',
        accentColor: '#fbbf24'
      },
      {
        name: 'DAX Optimization',
        icon: 'βš™οΈ',
        purpose: 'Techniques for writing DAX that executes efficiently: avoiding row-level iteration, using proper context, and leveraging caching.',
        when: 'When Performance Analyzer flags a slow DAX query, or when you know a formula will run against large datasets.',
        level: 'advanced',
        example: 'An engineer replaces a slow SUMPRODUCT with a SUMX + CALCULATE pattern, cutting the query time from 6s to 0.3s on a 50M-row fact table.',
        best: 'Prefer CALCULATE over SUMPRODUCT for conditional aggregations. Use VAR to avoid recalculation. Avoid row-by-row logic on large tables.',
        mistake: 'Using COUNTIF or SUMPRODUCT on multi-million-row tables. These iterate row-by-row and are extremely slow at scale.',
        accentColor: '#fb7185'
      },
      {
        name: 'Aggregation Tables',
        icon: 'πŸ“¦',
        purpose: 'Pre-aggregated summary tables that Power BI automatically uses instead of scanning the full detail table for matching queries.',
        when: 'For large DirectQuery fact tables (10M+ rows) where direct scans are too slow. Aggregation tables are a key enterprise pattern.',
        level: 'advanced',
        example: 'An enterprise has a 2B-row transactions table in DirectQuery. An aggregation table summarizes daily totals by product and region, allowing 99% of dashboard queries to hit the small summary instead.',
        best: 'Design aggregation tables to match your most common query patterns. Monitor hit rates β€” Power BI logs when aggregation is used vs. bypassed.',
        mistake: 'Only creating aggregation tables after a performance crisis. They should be part of the architecture from the start for large-scale models.',
        accentColor: '#a78bfa'
      },
      {
        name: 'Incremental Refresh',
        icon: 'πŸ”„',
        purpose: 'Only refresh new or changed data partitions instead of reloading the entire dataset on every refresh cycle.',
        when: 'For large Import-mode tables with millions of rows where a full refresh takes too long or times out.',
        level: 'advanced',
        example: 'A sales model with 3 years of history sets up incremental refresh: only the last 3 days are refreshed, while historical data remains untouched β€” cutting refresh time from 4 hours to 8 minutes.',
        best: 'Pair incremental refresh with a proper date/time parameter in Power Query. Ensure your source database supports efficient date-range filtering.',
        mistake: 'Applying incremental refresh to data that changes historically (corrections, backdating). You will have stale data you don\'t know about.',
        accentColor: '#34d399'
      },
      {
        name: 'Model Size Optimization',
        icon: 'πŸ“',
        purpose: 'Strategies for reducing the in-memory footprint of a Power BI model: column pruning, data types, and removing unused objects.',
        when: 'When a model exceeds storage limits, loads slowly in Desktop, or is approaching the Power BI Pro/Premium dataset size caps.',
        level: 'intermediate',
        example: 'A developer audits a 2.5GB model using Vertipaq Analyzer and discovers 40% of the size is from three unused text columns. Removing them drops the model to 1.2GB.',
        best: 'Use Vertipaq Analyzer (free tool) to profile your model. Remove unused columns. Use appropriate data types β€” INT instead of TEXT for codes.',
        mistake: 'Keeping every column from the source "just in case." If Power BI is not using it, it should not be in the model β€” it wastes memory.',
        accentColor: '#38a2ff'
      }
    ],

    /* ── Deployment & Governance ── */
    deployment: [
      {
        name: 'Workspaces',
        icon: 'πŸ“',
        purpose: 'Organizational containers in Power BI Service for grouping reports, dashboards, datasets, and dataflows by team or purpose.',
        when: 'Every enterprise uses workspaces to manage access, organize content, and separate development from production.',
        level: 'beginner',
        example: 'A company creates workspaces: "Finance β€” Dev", "Finance β€” Prod", "Marketing Analytics", with different member roles and access levels for each.',
        best: 'Use Premium or Premium Per User workspaces for large-scale deployment. Assign roles (Viewer, Member, Contributor, Admin) based on least privilege.',
        mistake: 'Putting everything in "My workspace." This prevents collaboration, sharing, and proper governance. Always use shared workspaces in enterprise.',
        accentColor: '#38a2ff'
      },
      {
        name: 'Apps',
        icon: 'πŸ“¦',
        purpose: 'A curated collection of dashboards and reports packaged together for distribution to a broad set of consumers.',
        when: 'When a department needs to distribute a set of related reports to 100+ users in a controlled, branded way.',
        level: 'intermediate',
        example: 'The Finance team publishes a "Monthly Reporting App" containing 5 dashboards and 12 reports. All 500 stakeholders subscribe to the app and get updates automatically.',
        best: 'Use Apps as the primary distribution mechanism for end users. They provide version control for content and a clean user experience.',
        mistake: 'Sharing individual report links with 200 users. This is unmanageable. Apps provide centralized, versioned distribution at scale.',
        accentColor: '#34d399'
      },
      {
        name: 'Row-Level Security (RLS)',
        icon: 'πŸ”',
        purpose: 'Restricts which rows of data a user can see based on their identity, without needing separate reports per user.',
        when: 'Whenever different users need to see different subsets of data β€” regional managers seeing only their region, etc.',
        level: 'advanced',
        example: 'A global sales report uses RLS rules: when a regional VP logs in, they only see sales data for their assigned territories β€” same report, filtered data.',
        best: 'Define RLS roles in Power BI Desktop, test with "Test as" in Service. Keep rules simple. Complex dynamic RLS often performs poorly.',
        mistake: 'Creating 50 separate reports for 50 regions instead of using RLS. This is maintenance nightmare and defeats the purpose of shared reporting.',
        accentColor: '#fb7185'
      },
      {
        name: 'Sensitivity Labels',
        icon: '🏷️',
        purpose: 'Microsoft Information Protection labels that classify Power BI content as Confidential, Internal, Public, etc.',
        when: 'Enterprise compliance and governance β€” mandatory in regulated industries (finance, healthcare, government).',
        level: 'advanced',
        example: 'An admin applies "Confidential β€” Finance" sensitivity labels to all Q4 earnings reports. This triggers encryption and audit trails automatically.',
        best: 'Integrate sensitivity labels with your organization\'s broader Microsoft 365 data classification policy. Automate labeling where possible.',
        mistake: 'Treating sensitivity labels as optional "nice-to-have." In regulated industries, missing labels can mean compliance violations and fines.',
        accentColor: '#a78bfa'
      },
      {
        name: 'Audit Logs',
        icon: 'πŸ“‹',
        purpose: 'Track who accessed, modified, shared, or deleted Power BI content. Essential for security monitoring and compliance.',
        when: 'Governed enterprise environments where data access must be tracked, and for incident response when something goes wrong.',
        level: 'advanced',
        example: 'A security team investigates an unauthorized data export. Power BI audit logs show exactly which user exported which dataset at what time, enabling a full forensic trace.',
        best: 'Enable Power BI audit logging and integrate with your SIEM tool (Azure Sentinel, Splunk). Set up alerts for unusual export or sharing activities.',
        mistake: 'Ignoring audit logs until an incident occurs. By then, the logs may not be retained. Always have monitoring and alerting in place proactively.',
        accentColor: '#fbbf24'
      }
    ],

    /* ── Automation & DevOps ── */
    automation: [
      {
        name: 'Power Automate',
        icon: '⚑',
        purpose: 'Low-code workflow automation. Triggers actions when events occur β€” like sending alerts when a KPI breaches a threshold.',
        when: 'For automating notifications, data exports, approval workflows, and integrations between Power BI and other tools.',
        level: 'intermediate',
        example: 'A Power Automate flow monitors a Power BI dataset: when weekly sales drop more than 15% below forecast, it sends a Teams alert to the VP of Sales with the relevant dashboard link.',
        best: 'Use Power Automate\'s native Power BI connectors (Push data, Refresh dataset). Keep flows simple β€” complex logic should stay in DAX or the data layer.',
        mistake: 'Building complex business logic in Power Automate flows instead of in the data model. Automate the notifications and triggers, not the analytics.',
        accentColor: '#fb7185'
      },
      {
        name: 'Power BI REST API',
        icon: '🌐',
        purpose: 'Programmatic interface to manage Power BI resources: datasets, reports, workspaces, dashboards, and more.',
        when: 'For automation scripts, custom admin tools, embedding Power BI in custom applications, or bulk operations.',
        level: 'advanced',
        example: 'A developer writes a Python script using the Power BI REST API to automatically move reports from a Dev workspace to Prod, trigger a refresh, and send a completion notification.',
        best: 'Use Azure Active Directory for OAuth authentication. Explore the API via Microsoft\'s Power BI REST API documentation and use tools like Postman.',
        mistake: 'Hard-coding credentials in scripts that call the API. Always use OAuth token-based authentication with proper secret management.',
        accentColor: '#a78bfa'
      },
      {
        name: 'Deployment Pipelines',
        icon: 'πŸš€',
        purpose: 'Power BI\'s native tool for moving content through Dev β†’ Test β†’ Prod stages with comparison and selective deployment.',
        when: 'For any enterprise team that needs a structured, repeatable process for publishing Power BI content.',
        level: 'intermediate',
        example: 'A BI team uses Deployment Pipelines: developers build in the Dev workspace, QA reviews in Test, and the pipeline promotes approved content to Production with a single click.',
        best: 'Always use Deployment Pipelines for production content. The comparison feature is invaluable for catching unintended changes before they go live.',
        mistake: 'Manually copying reports between workspaces. This is error-prone, loses version history, and defeats governance. Use pipelines.',
        accentColor: '#34d399'
      },
      {
        name: 'Azure DevOps CI/CD',
        icon: 'πŸ”',
        purpose: 'Full-featured CI/CD pipelines that integrate Power BI development with source control, automated testing, and deployment.',
        when: 'For advanced, developer-focused teams that use PBIP format and Git-based version control for Power BI projects.',
        level: 'advanced',
        example: 'A senior developer sets up an Azure DevOps pipeline: on commit to the main branch of a PBIP repository, the pipeline automatically validates the model, runs DAX tests, and deploys to the Production workspace.',
        best: 'Combine with PBIP format and Git. Use the tabular model deployment tools (sqlops, msdeploy) to automate model deployment within the pipeline.',
        mistake: 'Trying to implement full CI/CD without first adopting PBIP and source control. The pipeline is only as good as the source it deploys from.',
        accentColor: '#7dd3fc'
      }
    ],

    /* ── Cloud & Microsoft Ecosystem ── */
    cloud: [
      {
        name: 'Azure Data Factory',
        icon: '🏭',
        purpose: 'Azure\'s managed ETL service. Orchestrates data movement and transformation from hundreds of sources into a centralized data lake or warehouse.',
        when: 'When Power BI needs data that has been preprocessed, cleansed, or transformed at the platform level before ingestion.',
        level: 'advanced',
        example: 'A data engineer uses ADF to ingest raw data from an Oracle ERP, transform it via Databricks, and load it into Azure SQL DW. Power BI then connects to the clean, aggregated output.',
        best: 'Use ADF for heavy-lifting ETL. Do not replicate ADF logic in Power Query β€” PQ is for light transformations within the BI layer.',
        mistake: 'Trying to do all ETL in Power Query for massive datasets. Power Query is excellent for small-to-medium transformations, but ADF is designed for enterprise-scale pipelines.',
        accentColor: '#38a2ff'
      },
      {
        name: 'Azure Synapse',
        icon: '⚑',
        purpose: 'An integrated analytics platform combining data warehousing, big data, and data integration in one unified environment.',
        when: 'For enterprises that need massive-scale OLAP queries (billions of rows) as a backend for Power BI dashboards.',
        level: 'advanced',
        example: 'A retail enterprise runs Power BI reports on top of Azure Synapse, querying 50 billion transaction rows via dedicated SQL pools. Synapse handles the heavy aggregation before Power BI visualizes it.',
        best: 'Use Synapse as the query layer for very large datasets. Push heavy aggregations to Synapse SQL; let Power BI focus on presentation.',
        mistake: 'Connecting Power BI directly to raw data lakes with billions of rows. Always have an analytical layer (like Synapse) between raw data and BI.',
        accentColor: '#a78bfa'
      },
      {
        name: 'Azure Data Lake',
        icon: 'πŸ”οΈ',
        purpose: 'Scalable, cost-effective storage for massive volumes of raw and structured data. The "single source of truth" in modern data platforms.',
        when: 'When the organization stores its master data in a lake format β€” often as the staging area before data warehouse processing.',
        level: 'advanced',
        example: 'All raw event data flows into Azure Data Lake Storage Gen2. An analytics pipeline processes it and writes clean tables that Power BI queries via Synapse or DirectLake.',
        best: 'Organize your lake in Bronze (raw) β†’ Silver (cleaned) β†’ Gold (aggregated) layers. Power BI should connect to Gold or Silver, never Bronze.',
        mistake: 'Pointing Power BI directly at raw Bronze-layer Parquet files. This is slow, messy, and bypasses all data quality and governance controls.',
        accentColor: '#34d399'
      },
      {
        name: 'Microsoft Fabric',
        icon: '🧡',
        purpose: 'Microsoft\'s next-generation unified analytics platform β€” combining data engineering, science, BI, and real-time analytics in one place.',
        when: 'The future of enterprise analytics with Microsoft. Fabric introduces DirectLake, lakehouses, and deep Power BI integration.',
        level: 'advanced',
        example: 'A forward-thinking enterprise migrates to Fabric: engineers build lakehouses, analysts use Power BI with DirectLake connections, and real-time streams feed streaming analytics dashboards.',
        best: 'Start learning Fabric now β€” it is Microsoft\'s strategic direction. DirectLake in Fabric gives Power BI the performance of Import with the freshness of DirectQuery.',
        mistake: 'Dismissing Fabric as "too new." Microsoft is investing heavily here. Early adopters will have a significant competitive advantage in hiring and capability.',
        accentColor: '#fbbf24'
      },
      {
        name: 'Excel + Power BI Integration',
        icon: 'πŸ“Š',
        purpose: 'Excel functions (CUBEMEMBER, CUBEVALUE) and connectors that pull live data from Power BI datasets into Excel for advanced analysis.',
        when: 'When analysts need to do ad-hoc calculations, what-if modeling, or detailed breakdowns that go beyond Power BI visuals.',
        level: 'intermediate',
        example: 'A finance analyst connects Excel to a Power BI dataset using CUBE functions, pulling live revenue measures into a complex Excel model for sensitivity analysis.',
        best: 'Use the "Analyze in Excel" feature for quick ad-hoc exploration. For live dashboards, stay in Power BI β€” Excel is for deep-dive analysis.',
        mistake: 'Replacing Power BI entirely with Excel for reporting. Use each tool for what it does best: Power BI for dashboards, Excel for deep analytical work.',
        accentColor: '#34d399'
      }
    ],

    /* ── Version Control & Collaboration ── */
    versioncontrol: [
      {
        name: 'Git / GitHub',
        icon: 'πŸ”€',
        purpose: 'Industry-standard version control. With PBIP format, Power BI projects can be tracked, branched, and merged like code.',
        when: 'For any team of 2+ developers working on Power BI projects. Essential for collaboration and history tracking.',
        level: 'intermediate',
        example: 'Two developers work on the same sales report: one builds the DAX measures on a feature branch, the other updates visuals on another branch. They merge via a pull request reviewed by a lead.',
        best: 'Adopt PBIP format + Git from the start. Use branching strategies (feature branches, main branch). Always PR before merging.',
        mistake: 'Passing .pbix files back and forth via email. There is no version history, no conflict resolution, and no way to collaborate effectively.',
        accentColor: '#38a2ff'
      },
      {
        name: 'Azure DevOps Repos',
        icon: 'πŸ“¦',
        purpose: 'Microsoft\'s cloud-hosted Git repository service, tightly integrated with Azure DevOps pipelines and Power BI.',
        when: 'For organizations already in the Microsoft ecosystem. Provides seamless integration with deployment pipelines and CI/CD.',
        level: 'intermediate',
        example: 'A BI team stores all PBIP projects in Azure DevOps Repos. Pull requests require code review. Merged commits trigger CI/CD pipelines that deploy to Power BI Service.',
        best: 'Use Azure DevOps Repos if you are in the M365 ecosystem β€” it integrates natively with deployment pipelines and Azure DevOps CI/CD.',
        mistake: 'Mixing Git hosts without a strategy (GitHub for some, DevOps for others). Pick one and standardize across the team.',
        accentColor: '#a78bfa'
      },
      {
        name: 'PBIP (Power BI Project Format)',
        icon: 'πŸ“‚',
        purpose: 'Microsoft\'s open, JSON-based project format that makes Power BI files readable and mergeable by version control tools.',
        when: 'The critical enabler for all Git-based Power BI workflows. Without PBIP, .pbix files are binary and cannot be diffed or merged.',
        level: 'intermediate',
        example: 'A developer saves their report as a .pbip project. The folder contains separate JSON files for each table, measure, and visual β€” each independently trackable in Git.',
        best: 'Switch to PBIP for all new projects. It is the foundation of modern Power BI development. Power BI Desktop fully supports saving and opening PBIP.',
        mistake: 'Continuing to use only .pbix files when working with a team. Binary files cannot be version-controlled meaningfully. PBIP solves this completely.',
        accentColor: '#34d399'
      }
    ],

    /* ── Hiring Tiers ── */
    hiringMust: [
      { name: 'Power BI Desktop',      freq: 98, detail: 'Every role requires it' },
      { name: 'DAX (Measures & TI)',    freq: 95, detail: 'Core analytical skill' },
      { name: 'Power Query / M',        freq: 90, detail: 'Data transformation' },
      { name: 'Star Schema Modeling',   freq: 88, detail: 'Model design foundation' },
      { name: 'SQL (T-SQL)',            freq: 92, detail: 'Data extraction' },
      { name: 'Power BI Service',       freq: 85, detail: 'Publishing & sharing' },
      { name: 'Relationships',          freq: 82, detail: 'Model connectivity' },
      { name: 'RLS (Security)',         freq: 75, detail: 'Enterprise access control' }
    ],
    hiringNice: [
      { name: 'DirectQuery & Composite', freq: 70, detail: 'Performance patterns' },
      { name: 'Custom Visuals',          freq: 60, detail: 'AppSource deployment' },
      { name: 'Bookmarks & UX Design',   freq: 58, detail: 'Report storytelling' },
      { name: 'Power Automate',          freq: 55, detail: 'Workflow automation' },
      { name: 'Deployment Pipelines',    freq: 62, detail: 'Release management' },
      { name: 'Azure SQL / Cloud DBs',   freq: 65, detail: 'Cloud data sources' },
      { name: 'Sensitivity Labels',      freq: 48, detail: 'Data governance' },
      { name: 'Performance Analyzer',    freq: 52, detail: 'Optimization skills' }
    ],
    hiringEdge: [
      { name: 'Microsoft Fabric',        freq: 72, detail: 'Next-gen platform' },
      { name: 'Azure DevOps CI/CD',      freq: 68, detail: 'DevOps for BI' },
      { name: 'PBIP + Git Workflows',    freq: 65, detail: 'Version control' },
      { name: 'REST API Integration',    freq: 60, detail: 'Programmatic BI' },
      { name: 'DAX Studio Profiling',    freq: 58, detail: 'Advanced optimization' },
      { name: 'Azure Synapse / ADF',     freq: 63, detail: 'Platform engineering' },
      { name: 'Tabular Editor',          freq: 50, detail: 'Model efficiency' },
      { name: 'M Language (Advanced)',   freq: 55, detail: 'Deep PQ mastery' }
    ],

    /* ── Hiring Insights (Company Signals) ── */
    insights: [
      { company: 'Microsoft', role: 'Senior BI Developer', tags: ['DAX', 'Fabric', 'Synapse', 'Star Schema', 'RLS'] },
      { company: 'Accenture', role: 'Power BI Consultant', tags: ['Power Query', 'SQL', 'DirectQuery', 'Governance', 'Pipelines'] },
      { company: 'Deloitte', role: 'Data & Analytics Lead', tags: ['Azure', 'DAX', 'Star Schema', 'CI/CD', 'Security'] },
      { company: 'PwC', role: 'BI Engineer', tags: ['Power BI Service', 'RLS', 'Sensitivity Labels', 'REST API', 'SQL'] },
      { company: 'Google Cloud', role: 'BI Solutions Architect', tags: ['Modeling', 'Optimization', 'Custom Visuals', 'Storytelling'] },
      { company: 'Amazon (AWS)', role: 'Analytics Engineer', tags: ['SQL', 'ETL', 'Power Query', 'Performance', 'Cloud DBs'] },
      { company: 'JPMorgan Chase', role: 'Enterprise BI Developer', tags: ['RLS', 'Audit Logs', 'Sensitivity', 'Governance', 'DAX'] },
      { company: 'Salesforce', role: 'Data Platform Engineer', tags: ['REST APIs', 'M Language', 'Power Automate', 'Integration'] },
      { company: 'SAP', role: 'Analytics Consultant', tags: ['DirectQuery', 'Composite', 'Aggregation', 'Star Schema', 'DAX'] },
      { company: 'Infosys', role: 'Power BI Specialist', tags: ['Power Query', 'DAX', 'Deployment Pipelines', 'Azure', 'SQL'] }
    ]
  };

  /* ═════════════════════════════════════════════════════════
     2. NAVIGATION ENGINE
     Handles section switching, progress bar, active states
     ═════════════════════════════════════════════════════════ */

  // Section order for progress calculation
  const SECTION_ORDER = [
    'overview','coretools','datasources','transformation',
    'dax','visualization','performance','deployment',
    'automation','cloud','versioncontrol','hiring'
  ];
  const TOTAL_SECTIONS = SECTION_ORDER.length;

  /**
   * Navigate to a target section by ID.
   * @param {string} targetId β€” the section element ID
   */
  function navigateTo(targetId) {
    // Validate target
    if (!SECTION_ORDER.includes(targetId)) return;

    // Hide all pages
    document.querySelectorAll('.page').forEach(function (page) {
      page.classList.remove('active');
    });

    // Deactivate all nav links
    document.querySelectorAll('.nav-link').forEach(function (link) {
      link.classList.remove('active');
    });

    // Show target section
    var targetSection = document.getElementById(targetId);
    if (targetSection) {
      // Small delay so CSS transition re-triggers properly
      requestAnimationFrame(function () {
        targetSection.classList.add('active');
      });
    }

    // Activate matching nav link
    var matchingLink = document.querySelector('.nav-link[data-target="' + targetId + '"]');
    if (matchingLink) matchingLink.classList.add('active');

    // Update progress bar
    var index = SECTION_ORDER.indexOf(targetId);
    var progressPercent = ((index + 1) / TOTAL_SECTIONS) * 100;
    document.getElementById('progressFill').style.width = progressPercent + '%';

    // Scroll inner section to top
    if (targetSection) targetSection.scrollTop = 0;
  }

  /* ═════════════════════════════════════════════════════════
     3. RENDERER β€” Dynamically populates cards & hiring section
     ═════════════════════════════════════════════════════════ */

  // Color accent map for card categories
  const ACCENT_COLORS = {
    coretools:        ['#38a2ff', '#7dd3fc', '#a78bfa'],
    datasources:     ['#38a2ff', '#7dd3fc', '#34d399', '#fb7185', '#34d399', '#a78bfa'],
    transformation: ['#fbbf24', '#fbbf24', '#38a2ff', '#34d399', '#a78bfa'],
    dax:            ['#fb7185', '#fb7185', '#fbbf24', '#7dd3fc', '#fb7185', '#a78bfa', '#7dd3fc'],
    visualization: ['#38a2ff', '#a78bfa', '#34d399', '#fbbf24', '#7dd3fc', '#fb7185'],
    performance:    ['#fbbf24', '#fb7185', '#a78bfa', '#34d399', '#38a2ff'],
    deployment:     ['#38a2ff', '#34d399', '#fb7185', '#a78bfa', '#fbbf24'],
    automation:     ['#fb7185', '#a78bfa', '#34d399', '#7dd3fc'],
    cloud:          ['#38a2ff', '#a78bfa', '#34d399', '#fbbf24', '#34d399'],
    versioncontrol:['#38a2ff', '#a78bfa', '#34d399']
  };

  /**
   * Generate HTML for a single tool card
   * @param {Object} tool β€” tool data object
   * @param {number} index β€” card index (for stagger delay)
   * @param {string} category β€” category key for accent lookup
   */
  function renderCard(tool, index, category) {
    var accentColor = tool.accentColor || (ACCENT_COLORS[category] && ACCENT_COLORS[category][index % ACCENT_COLORS[category].length]) || '#38a2ff';

    // Convert hex to rgba for dim
    var dimColor = hexToRgba(accentColor, 0.15);

    var delayMs = index * 55; // stagger

    return '<div class="tool-card" style="--card-accent:' + accentColor + '; --card-accent-dim:' + dimColor + '; animation-delay:' + delayMs + 'ms;" data-tool-cat="' + category + '" data-tool-idx="' + index + '">' +
      '<div class="card-top">' +
        '<div class="card-icon" style="background:' + dimColor + ';">' + tool.icon + '</div>' +
        '<span class="level-badge ' + tool.level + '"><span class="dot"></span>' + capitalize(tool.level) + '</span>' +
      '</div>' +
      '<div class="card-name">' + tool.name + '</div>' +
      '<div class="card-purpose">' + tool.purpose + '</div>' +
      '<div class="card-footer">View Details &nbsp;β†’</div>' +
    '</div>';
  }

  /**
   * Render all tool cards for a given category into its grid container
   * @param {string} categoryKey β€” e.g. 'coretools'
   */
  function renderCategory(categoryKey) {
    var tools = DATA[categoryKey];
    var gridId = categoryKey + 'Grid';
    var gridEl = document.getElementById(gridId);
    if (!tools || !gridEl) return;

    var html = '';
    tools.forEach(function (tool, i) {
      html += renderCard(tool, i, categoryKey);
    });
    gridEl.innerHTML = html;
  }

  /**
   * Render the Hiring section β€” tiers and insights
   */
  function renderHiring() {
    renderTier('tierMustItems',  DATA.hiringMust,  'must');
    renderTier('tierNiceItems',  DATA.hiringNice,  'nice');
    renderTier('tierEdgeItems',  DATA.hiringEdge,  'edge');
    renderInsights();
  }

  /**
   * Render a hiring tier's item list
   * @param {string} containerId
   * @param {Array}  items
   * @param {string} tierType β€” 'must' | 'nice' | 'edge'
   */
  function renderTier(containerId, items, tierType) {
    var el = document.getElementById(containerId);
    if (!el) return;

    // Sort by freq descending
    var sorted = items.slice().sort(function (a, b) { return b.freq - a.freq; });

    var html = '';
    sorted.forEach(function (item, i) {
      var barWidth = item.freq; // percentage
      html +=
        '<div class="tier-item">' +
          '<span class="tier-item-rank">' + (i + 1) + '</span>' +
          '<div class="tier-item-info">' +
            '<div class="tier-item-name">' + item.name + '</div>' +
            '<div class="tier-item-freq">' + item.detail + '</div>' +
          '</div>' +
          '<div class="freq-bar-wrap"><div class="freq-bar" style="width:' + barWidth + '%;"></div></div>' +
        '</div>';
    });
    el.innerHTML = html;
  }

  /**
   * Render hiring insights grid (company cards)
   */
  function renderInsights() {
    var el = document.getElementById('insightsGrid');
    if (!el) return;

    var html = '';
    DATA.insights.forEach(function (insight, i) {
      var delayMs = i * 60;
      var tagsHtml = insight.tags.map(function (t) {
        return '<span class="insight-tag">' + t + '</span>';
      }).join('');

      html +=
        '<div class="insight-card" style="animation-delay:' + delayMs + 'ms;">' +
          '<div class="insight-company">' + insight.company + '</div>' +
          '<div class="insight-role">' + insight.role + '</div>' +
          '<div class="insight-tags">' + tagsHtml + '</div>' +
        '</div>';
    });
    el.innerHTML = html;
  }

  /* ═════════════════════════════════════════════════════════
     4. MODAL ENGINE β€” Tool Detail Expansion
     ═════════════════════════════════════════════════════════ */

  /**
   * Open modal with full tool detail
   * @param {string} category β€” category key
   * @param {number} index β€” tool index within category
   */
  function openModal(category, index) {
    var tools = DATA[category];
    if (!tools || !tools[index]) return;
    var tool = tools[index];
    var accentColor = tool.accentColor || '#38a2ff';
    var dimColor = hexToRgba(accentColor, 0.18);

    var html =
      '<div class="modal-header">' +
        '<div class="modal-icon" style="background:' + dimColor + ';">' + tool.icon + '</div>' +
        '<div class="modal-title-block">' +
          '<h2>' + tool.name + '</h2>' +
          '<span class="level-badge ' + tool.level + '"><span class="dot"></span>' + capitalize(tool.level) + '</span>' +
        '</div>' +
      '</div>' +

      '<div class="modal-section">' +
        '<div class="modal-section-label">Purpose</div>' +
        '<p>' + tool.purpose + '</p>' +
      '</div>' +

      '<div class="modal-section">' +
        '<div class="modal-section-label">When Do Developers Use This?</div>' +
        '<p>' + tool.when + '</p>' +
      '</div>' +

      '<div class="modal-section">' +
        '<div class="modal-section-label">Real-World Enterprise Example</div>' +
        '<p>' + tool.example + '</p>' +
      '</div>' +

      '<div class="modal-section">' +
        '<div class="modal-box-highlight best">' +
          '<div class="bh-label">βœ“ Best Practice</div>' +
          '<div class="bh-text">' + tool.best + '</div>' +
        '</div>' +
      '</div>' +

      '<div class="modal-section">' +
        '<div class="modal-box-highlight mistake">' +
          '<div class="bh-label">βœ• Common Mistake</div>' +
          '<div class="bh-text">' + tool.mistake + '</div>' +
        '</div>' +
      '</div>';

    document.getElementById('modalContent').innerHTML = html;
    document.getElementById('modalOverlay').classList.add('open');
  }

  /**
   * Close the modal
   */
  function closeModal() {
    document.getElementById('modalOverlay').classList.remove('open');
  }

  /* ═════════════════════════════════════════════════════════
     5. UTILITY FUNCTIONS
     ═════════════════════════════════════════════════════════ */

  /**
   * Convert a hex color to rgba string
   * @param {string} hex β€” e.g. '#38a2ff'
   * @param {number} alpha β€” 0–1
   * @returns {string} rgba(r, g, b, alpha)
   */
  function hexToRgba(hex, alpha) {
    hex = hex.replace('#', '');
    if (hex.length === 3) {
      hex = hex[0]+hex[0] + hex[1]+hex[1] + hex[2]+hex[2];
    }
    var r = parseInt(hex.substring(0, 2), 16);
    var g = parseInt(hex.substring(2, 4), 16);
    var b = parseInt(hex.substring(4, 6), 16);
    return 'rgba(' + r + ',' + g + ',' + b + ',' + alpha + ')';
  }

  /**
   * Capitalize first letter
   * @param {string} str
   * @returns {string}
   */
  function capitalize(str) {
    return str.charAt(0).toUpperCase() + str.slice(1);
  }

  /* ═════════════════════════════════════════════════════════
     6. INITIALIZATION & EVENT BINDING
     ═════════════════════════════════════════════════════════ */

  document.addEventListener('DOMContentLoaded', function () {

    // ── Render all data-driven sections ──
    var categories = ['coretools','datasources','transformation','dax','visualization','performance','deployment','automation','cloud','versioncontrol'];
    categories.forEach(renderCategory);
    renderHiring();

    // ── Navigation link clicks ──
    document.getElementById('navLinks').addEventListener('click', function (e) {
      var link = e.target.closest('.nav-link');
      if (!link) return;
      e.preventDefault();
      navigateTo(link.getAttribute('data-target'));
    });

    // ── Hero CTA button ──
    var heroCta = document.querySelector('.hero-cta');
    if (heroCta) {
      heroCta.addEventListener('click', function () {
        navigateTo(heroCta.getAttribute('data-target'));
      });
    }

    // ── Tool card clicks β†’ open modal (event delegation) ──
    document.addEventListener('click', function (e) {
      var card = e.target.closest('.tool-card');
      if (!card) return;
      var cat   = card.getAttribute('data-tool-cat');
      var idx   = parseInt(card.getAttribute('data-tool-idx'), 10);
      if (cat && !isNaN(idx)) openModal(cat, idx);
    });

    // ── Modal close: button, overlay click, Escape key ──
    document.getElementById('modalClose').addEventListener('click', closeModal);

    document.getElementById('modalOverlay').addEventListener('click', function (e) {
      if (e.target === this) closeModal();
    });

    document.addEventListener('keydown', function (e) {
      if (e.key === 'Escape') closeModal();
    });

    // ── Initialize first section as active ──
    navigateTo('overview');
  });

})(); /* End IIFE */