Private/Show-MultiColumnSlide.ps1

function Show-MultiColumnSlide {
    <#
    .SYNOPSIS
        Renders a slide with multiple columns of content.

    .DESCRIPTION
        Displays a slide split into 2, 3, 4, or more columns arranged side-by-side.
        Content is divided using the ||| delimiter (three pipe characters). Each column
        can contain text, code blocks, and markdown formatting.
        
        The rendering process:
        1. Detects optional ### heading and renders as centered figlet text
        2. Splits body content at each ||| delimiter
        3. Parses code blocks in each column independently
        4. Converts markdown formatting to Spectre markup
        5. Renders columns with equal width distribution
        
        Columns are automatically sized to evenly divide the terminal width. For example,
        2 columns get 50% each, 3 columns get 33% each, etc.
        
        The optional ### heading appears above all columns as centered figlet text.
        Heading color can be overridden using HTML color tags.

    .PARAMETER Slide
        The slide object containing content with ||| delimiters separating columns.

    .PARAMETER Settings
        The presentation settings hashtable containing:
        - foreground: Default text color
        - background: Slide background color
        - border: Border color
        - borderStyle: Border style
        - h3: Font for optional ### heading
        - h3Color: Optional color override for heading

    .PARAMETER CurrentSlide
        The current slide number for pagination display.

    .PARAMETER TotalSlides
        The total number of slides in the presentation for pagination.

    .EXAMPLE
        Show-MultiColumnSlide -Slide $slideObject -Settings $settings

        Renders a multi-column slide with content split by ||| delimiters.

    .EXAMPLE
        $slide = [PSCustomObject]@{
            Number = 4
            Content = @'
### Feature Comparison

**Basic**
- Feature A
- Feature B

|||

**Pro**
- Feature A
- Feature B
- Feature C

|||

**Enterprise**
- All features
- Priority support
- Custom integration
'@
        }
        Show-MultiColumnSlide -Slide $slide -Settings $settings

        Demonstrates 3-column slide with heading and bullet lists in each column.

    .EXAMPLE
        $slide = [PSCustomObject]@{
            Number = 7
            Content = @'
Before:

```powershell
Get-Process
```

|||

After:

```powershell
Get-Process | Where-Object CPU -gt 100
```
'@
        }
        Show-MultiColumnSlide -Slide $slide -Settings $settings

        Demonstrates 2-column slide comparing code examples side-by-side.

    .OUTPUTS
        None. Renders directly to the terminal console using PwshSpectreConsole.

    .NOTES
        Column Layout:
        - 2 columns: 50% width each
        - 3 columns: 33% width each
        - 4+ columns: Evenly divided (may be tight on narrow terminals)
        
        Column Delimiter:
        - Use exactly three pipe characters: |||
        - Must be on its own line
        - Whitespace around delimiter is trimmed
        
        Column Content:
        - Markdown formatting supported (bold, italic, code, strikethrough)
        - Code blocks with syntax highlighting
        - Bullet lists (both - and * styles)
        - Plain text paragraphs
        
        Heading Support:
        - Optional ### heading renders above columns
        - Heading uses h3 font (default: 'mini')
        - Heading centered across full slide width
        - Color override via <color>text</color> or <span style="color:name">text</span>
        
        Best Practices:
        - Keep column content balanced for visual appeal
        - Avoid excessive text in narrow columns
        - Test on target terminal width
        - Use 2-3 columns for best readability
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [PSCustomObject]$Slide,

        [Parameter(Mandatory = $true)]
        [hashtable]$Settings,

        [Parameter(Mandatory = $false)]
        [int]$CurrentSlide = 1,

        [Parameter(Mandatory = $false)]
        [int]$TotalSlides = 1
    )

    begin {
        Write-Verbose "Rendering multi-column slide #$($Slide.Number)"
    }

    process {
        try {
            # Get terminal dimensions
            $dimensions = Get-TerminalDimensions
            $windowWidth = $dimensions.Width
            $windowHeight = $dimensions.Height

            # Determine if slide has a header and extract content
            $hasHeader = $false
            $headerText = $null
            $bodyContent = $null

            if ($Slide.Content -match '^###\s+(.+?)(?:\r?\n|$)') {
                $hasHeader = $true
                $headerText = $Matches[1].Trim()
                Write-Verbose " Header: $headerText"
                
                # Check for color tags in heading text and extract color
                $headingColor = $null
                if ($headerText -match '<(\w+)>.*?</\1>') {
                    $headingColor = $Matches[1]
                    Write-Verbose " Extracted color from tag: $headingColor"
                } elseif ($headerText -match "<span\s+style=['""]color:(\w+)['""]>.*?</span>") {
                    $headingColor = $Matches[1]
                    Write-Verbose " Extracted color from span: $headingColor"
                }
                
                # Strip HTML tags from header text
                $headerText = $headerText -replace "<span\s+style=['""]color:\w+['""]>(.*?)</span>", '$1'
                $headerText = $headerText -replace '<(\w+)>(.*?)</\1>', '$2'
                
                # Extract content after header
                $bodyContent = $Slide.Content -replace '^###\s+.+?(\r?\n|$)', ''
                $bodyContent = $bodyContent.Trim()
            } else {
                # No header, use all content
                $bodyContent = $Slide.Content.Trim()
            }

            # Split content into columns at ||| delimiter
            $columnDelimiter = '\|\|\|'
            $columns = $bodyContent -split $columnDelimiter
            $columns = $columns | ForEach-Object { $_.Trim() }
            
            Write-Verbose " Detected $($columns.Count) columns"

            # Get border color and style
            $borderInfo = Get-BorderStyleFromSettings -Settings $Settings

            # Build the renderable content
            $renderables = [System.Collections.Generic.List[object]]::new()
            
            # Add header figlet if present
            if ($hasHeader) {
                # Convert color name to Spectre.Console.Color
                $colorName = if ($headingColor) { 
                    $headingColor 
                } elseif ($Settings.h3Color) { 
                    $Settings.h3Color 
                } else { 
                    $Settings.foreground 
                }
                $figletColor = Get-SpectreColorFromSettings -ColorName $colorName -SettingName 'Header'

                # Create figlet for header with optional font from settings
                $figletParams = @{
                    Text = $headerText
                    Color = $figletColor
                    Justification = 'Center'
                }
                # Default to 'mini' font if h3 is 'default', otherwise use specified font
                $fontName = if ($Settings.h3 -eq 'default') { 'mini' } else { $Settings.h3 }
                $fontPath = if (Test-Path $fontName) {
                    $fontName
                } else {
                    Join-Path $PSScriptRoot "../Fonts/$fontName.flf"
                }
                if (Test-Path $fontPath) {
                    $figletParams['FontPath'] = $fontPath
                    Write-Verbose " Using h3 font: $fontName"
                }
                $figlet = New-FigletText @figletParams
                $renderables.Add($figlet)
            }

            # Create column renderables
            $columnRenderables = [System.Collections.Generic.List[object]]::new()
            
            foreach ($columnContent in $columns) {
                if ($columnContent) {
                    # Parse code blocks in this column
                    $columnSegments = ConvertTo-CodeBlockSegments -Content $columnContent
                        
                    # Parse code blocks in this column
                    $columnSegments = ConvertTo-CodeBlockSegments -Content $columnContent
                    
                    # Build renderables for this column
                    $columnParts = [System.Collections.Generic.List[object]]::new()
                    
                    foreach ($segment in $columnSegments) {
                        if ($segment.Type -eq 'Code') {
                            # Render code block in a panel
                            $codeMarkup = [Spectre.Console.Markup]::Escape($segment.Content)
                            $codeText = [Spectre.Console.Markup]::new($codeMarkup)
                            
                            $codePanel = [Spectre.Console.Panel]::new($codeText)
                            $codePanel.Border = [Spectre.Console.BoxBorder]::Rounded
                            $codePanel.Padding = [Spectre.Console.Padding]::new(2, 1, 2, 1)
                            
                            if ($segment.Language) {
                                $codePanel.Header = [Spectre.Console.PanelHeader]::new($segment.Language)
                            }
                            
                            $columnParts.Add($codePanel)
                        } else {
                            # Render text
                            $textLines = $segment.Content -split "`r?`n" | ForEach-Object {
                                ConvertTo-SpectreMarkup -Text $_
                            }
                            $textMarkup = [Spectre.Console.Markup]::new(($textLines -join "`n"))
                            $columnParts.Add($textMarkup)
                        }
                    }
                    
                    # Combine column parts into Rows if multiple, otherwise use single part
                    if ($columnParts.Count -gt 1) {
                        $columnRenderable = [Spectre.Console.Rows]::new([object[]]$columnParts.ToArray())
                    } else {
                        $columnRenderable = $columnParts[0]
                    }
                    
                    $columnRenderables.Add($columnRenderable)
                } else {
                    # Empty column
                    $columnRenderables.Add([Spectre.Console.Text]::new(""))
                }
            }

            # Create columns layout using Grid for equal-width columns
            # Grid provides explicit control over column widths
            $columnCount = $columnRenderables.Count
            
            Write-Verbose " Creating $columnCount equal-width columns using Grid"
            
            # Create a Grid with equal-width columns
            $grid = [Spectre.Console.Grid]::new()
            
            # Add columns to the grid (one GridColumn per content column)
            for ($i = 0; $i -lt $columnCount; $i++) {
                $gridColumn = [Spectre.Console.GridColumn]::new()
                $gridColumn.NoWrap = $false
                $gridColumn.Padding = [Spectre.Console.Padding]::new(2, 0, 2, 0)  # Horizontal padding
                $grid.AddColumn($gridColumn) | Out-Null
            }
            
            # Add content as a single row with all columns
            $grid.AddRow($columnRenderables.ToArray()) | Out-Null
            
            # Measure grid height BEFORE wrapping in alignment
            # This ensures accurate measurement without Format-SpectreAligned interference
            $renderablesForMeasurement = [System.Collections.Generic.List[object]]::new($renderables)
            $renderablesForMeasurement.Add($grid)
            $rowsForMeasurement = [Spectre.Console.Rows]::new([object[]]$renderablesForMeasurement.ToArray())
            
            # Measure the actual height of the rendered content
            # Account for horizontal padding (4 left + 4 right = 8 total)
            $availableWidth = $windowWidth - 8
            $contentSize = Get-SpectreRenderableSize -Renderable $rowsForMeasurement -ContainerWidth $availableWidth
            $actualContentHeight = $contentSize.Height
            
            # Now center the grid for display
            $centeredGrid = Format-SpectreAligned -Data $grid -HorizontalAlignment Center
            $renderables.Add($centeredGrid)

            # Combine renderables into a Rows layout for rendering
            $rows = [Spectre.Console.Rows]::new([object[]]$renderables.ToArray())
            
            # Calculate padding
            # Add safety buffer if content contains code blocks (measurement might be slightly off)
            $hasCodeBlocks = $columnRenderables | ForEach-Object { $_ } | Where-Object { $_ -is [Spectre.Console.Panel] }
            $heightBuffer = if ($hasCodeBlocks) { 2 } else { 0 }
            
            $borderHeight = 2
            $remainingSpace = $windowHeight - $actualContentHeight - $borderHeight - $heightBuffer
            $topPadding = [math]::Max(0, [math]::Ceiling($remainingSpace / 2.0))
            $bottomPadding = [math]::Max(0, $remainingSpace - $topPadding)
            
            Write-Verbose " Content height: $actualContentHeight, top padding: $topPadding, bottom padding: $bottomPadding"
            
            # Create panel with internal padding
            $panel = [Spectre.Console.Panel]::new($rows)
            $panel.Expand = $true
            $panel.Padding = [Spectre.Console.Padding]::new(4, $topPadding, 4, $bottomPadding)
            
            # Add border style and color
            if ($borderInfo.Style) {
                $panel.Border = [Spectre.Console.BoxBorder]::$($borderInfo.Style)
            }
            if ($borderInfo.Color) {
                $panel.BorderStyle = [Spectre.Console.Style]::new($borderInfo.Color)
            }
            
            # Add pagination header if enabled
            if ($Settings.pagination -eq $true) {
                $paginationParams = @{
                    CurrentSlide = $CurrentSlide
                    TotalSlides = $TotalSlides
                    Style = $Settings.paginationStyle
                }
                if ($borderInfo.Color) {
                    $paginationParams['Color'] = $borderInfo.Color
                }
                $paginationText = Get-PaginationText @paginationParams
                $panel.Header = [Spectre.Console.PanelHeader]::new($paginationText)
                $panel.Header.Justification = [Spectre.Console.Justify]::Right
            }
            
            # Render panel
            Out-SpectreHost $panel
        } catch {
            $errorRecord = [System.Management.Automation.ErrorRecord]::new(
                $_.Exception,
                'MultiColumnSlideRenderFailed',
                [System.Management.Automation.ErrorCategory]::InvalidOperation,
                $Slide
            )
            $PSCmdlet.ThrowTerminatingError($errorRecord)
        }
    }

    end {
        Write-Verbose "Multi-column slide rendered"
    }
}