bitools / app.js
Mohammedallyl's picture
Upload 3 files
f4feab0 verified
/* ═══════════════════════════════════════════════════════════
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 */