"""Pre-built landing page components for marketing websites and public-facing pages"""

# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/04_web_pages.ipynb.

# %% auto #0
__all__ = ['SECTION_STYLE', 'SECTION_MARGIN', 'STANDARD_FOOTER_COLUMNS', 'HeroSection', 'FeatureShowcase', 'FeaturesGrid',
           'PricingSection', 'FAQSection', 'PageFooter', 'LandingNavBar', 'LandingPageSimple', 'MarkdownSection',
           'ContentPage']

# %% ../nbs/04_web_pages.ipynb #7f3cabf7
import importlib
import markdown
from .components import *
from .core import *
from fasthtml.common import *
from .app_pages import *
from .foundations import *
from fastcore.utils import partial
from fasthtml.common import *
from fasthtml.jupyter import FastHTML, fast_app, JupyUvi, HTMX
from fastlite import *
import fasthtml.components as fc
from fasthtml.common import A, Button as FhButton, I, Span

# Hover animation CSS for cards/articles - adds lift + shadow + primary border on hover
# Only applies on devices that support hover (not touch devices)
_CARD_HOVER_CSS = """
.card-hover {
    transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
    border: 1px solid transparent;
}
@media (hover: hover) {
    .card-hover:hover {
        transform: translateY(-4px);
        box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
        border-color: var(--primary);
    }
}
"""

# Simple scale effect for pricing cards - enlarges 3% + primary border on hover
_CARD_SCALE_CSS = """
.card-scale {
    transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
    border: 1px solid transparent;
}
@media (hover: hover) {
    .card-scale:hover {
        transform: scale(1.03);
        box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
        border-color: var(--primary);
    }
}
"""

# Image zoom effect - image scales within overflow-hidden container
_IMAGE_SCALE_CSS = """
.image-scale {
    overflow: hidden;
    border-radius: inherit;
}
.image-scale img {
    transition: transform 0.3s ease;
}
@media (hover: hover) {
    .image-scale:hover img {
        transform: scale(1.05);
    }
}
"""

# %% ../nbs/04_web_pages.ipynb #ec869572
def HeroSection(
    title: str,              # Main hero title
    subtitle: str,           # Hero subtitle/description
    primary_cta_text: str,   # Primary CTA button text
    primary_cta_href: str,   # Primary CTA button href
    secondary_cta_text: str = None,  # Secondary CTA button text (optional)
    secondary_cta_href: str = None,  # Secondary CTA button href (optional)
    background: str = "primary-container",  # BeerCSS background class (without bg- prefix)
    cls: str = "",  # Additional classes
    extra_css: str | None = None,  # Optional custom CSS (minimal; opt-in)
    extra_js: str | None = None,  # Optional custom JS (minimal; opt-in)
):
    """Hero section with title, subtitle, and CTA buttons.
    
    BeerCSS-first styling. Takes up 75vh by default with gradient overlay.
    Designed for full-width layout - background extends edge-to-edge naturally.
    Content is centered using responsive class internally.
    Mobile-friendly: title scales down via clamp(), text stays centered.
    """
    # Hero styling: 75vh height + gradient overlay + responsive title sizing
    hero_css = """
    .hero-section {
        min-height: 75vh;
        display: flex;
        align-items: center;
        justify-content: center;
        position: relative;
        background: linear-gradient(135deg, 
            var(--primary-container) 0%, 
            var(--surface-container) 50%,
            var(--secondary-container) 100%);
    }
    .hero-section::before {
        content: '';
        position: absolute;
        inset: 0;
        background: radial-gradient(circle at 20% 80%, var(--primary) 0%, transparent 50%),
                    radial-gradient(circle at 80% 20%, var(--secondary) 0%, transparent 50%);
        opacity: 0.1;
        pointer-events: none;
    }
    .hero-content {
        position: relative;
        z-index: 1;
        text-align: center;
    }
    /* Responsive title: scales smoothly from 2rem (mobile) to 3.5rem (desktop) */
    .hero-section h1 {
        font-size: clamp(2rem, 5vw, 3.5rem);
        overflow-wrap: break-word;
        word-break: break-word;
    }
    /* Responsive subtitle: scales from 1rem (mobile) to 1.25rem (desktop) */
    .hero-section .hero-subtitle {
        font-size: clamp(1rem, 2.5vw, 1.25rem);
    }
    """
    
    hooks = [Style(hero_css)]
    if extra_css:
        hooks.append(Style(extra_css))
    if extra_js:
        hooks.append(Script(extra_js))
    
    ctas = [Button(primary_cta_text, href=primary_cta_href, variant="raised")]
    if secondary_cta_text and secondary_cta_href:
        ctas.append(Button(secondary_cta_text, href=secondary_cta_href, variant="outlined"))
    
    stack = DivVStacked(
        H1(title, cls="no-margin"),
        P(subtitle, cls="secondary-text large-text hero-subtitle"),
        Div(*ctas, cls="row middle-align center-align small-space wrap"),
        cls="center-align large-padding hero-content",
        responsive=False,  # Outer Div already handles responsive centering
        padding=False,      # large-padding already applied via cls
    )
    # Content centered with responsive class
    content = Div(stack, cls="responsive")
    return Section(
        *hooks,
        content,
        cls=f"hero-section {cls}".strip(),
    )

# %% ../nbs/04_web_pages.ipynb #9aac83c5
def FeatureShowcase(
    features: list,  # List of dicts: {title, description, image_src?, image_alt?, size?}
    title: str = None,  # Optional section title
    subtitle: str = None,  # Optional section subtitle
    cls: str = "",
):
    """Bento-box feature showcase with image-on-top cards.
    
    Displays features as cards with a large image on top (~75%) and
    text description on the bottom (~25%). Uses a bento grid layout:
    first 2 features are "big" (half-width on desktop), remaining are
    "small" (third-width on desktop). All stack full-width on mobile.
    
    Each feature can override its size via the 'size' key:
    - 'big': spans l6 (half width on desktop)
    - 'small': spans l4 (third width on desktop)
    - If omitted: first 2 default to 'big', rest to 'small'
    
    Auto-generates placeholder images from picsum.photos if no image_src provided.
    
    Args:
        features: List of feature dicts with keys:
            - title: Feature title (required)
            - description: Feature description (required)
            - image_src: URL/path to image (optional - auto-generates placeholder)
            - image_alt: Alt text for image (optional, defaults to title)
            - size: 'big' or 'small' (optional - auto-assigned by position)
        title: Section heading
        subtitle: Section subheading
        cls: Additional CSS classes
    
    Example:
        FeatureShowcase(
            title="Why Choose Us",
            features=[
                {'title': 'Fast', 'description': 'Lightning quick'},
                {'title': 'Secure', 'description': 'Built-in protection'},
                {'title': 'Scale', 'description': 'Grows with you'},
                {'title': 'Simple', 'description': 'Easy to use'},
                {'title': 'Open', 'description': 'Open source'},
            ],
        )
    """
    _SHOWCASE_CSS = """
    .showcase-card {
        overflow: hidden;
        transition: transform 0.3s ease, box-shadow 0.3s ease;
    }
    .showcase-card:hover {
        transform: translateY(-4px);
        box-shadow: var(--elevate3);
    }
    .showcase-img-wrap {
        aspect-ratio: 16 / 9;
        overflow: hidden;
        border-radius: var(--round) var(--round) 0 0;
    }
    .showcase-img-wrap img {
        width: 100%;
        height: 100%;
        object-fit: cover;
        transition: transform 0.4s ease;
    }
    .showcase-card:hover .showcase-img-wrap img {
        transform: scale(1.05);
    }
    """
    
    cards = []
    for idx, feature in enumerate(features):
        # Determine size: explicit > positional default
        size = feature.get('size')
        if size is None:
            size = 'big' if idx < 2 else 'small'
        
        # Grid span classes
        if size == 'big':
            span_cls = "s12 m6 l6"
        else:
            span_cls = "s12 m6 l4"
        
        # Image source - use provided or auto-generate placeholder
        if feature.get('image_src'):
            img_src = feature['image_src']
        else:
            seed = feature['title'].lower().replace(' ', '-')
            img_src = f"https://picsum.photos/seed/{seed}/600/340"
        
        # Build card: image on top, text on bottom
        card = Article(
            Div(
                Img(
                    src=img_src,
                    alt=feature.get('image_alt', feature['title']),
                    loading="lazy",
                ),
                cls="showcase-img-wrap",
            ),
            Div(
                H5(feature['title'], cls="no-margin small-margin"),
                P(feature['description'], cls="secondary-text no-margin"),
                cls="padding",
            ),
            cls="showcase-card surface-container round no-padding border",
        )
        cards.append(Div(card, cls=span_cls))
    
    # Build section
    content = [Style(_SHOWCASE_CSS)]
    if title:
        content.append(H2(title, cls="center-align"))
    if subtitle:
        content.append(P(subtitle, cls="center-align secondary-text large-text bottom-margin"))
    
    # Bento grid
    content.append(Div(*cards, cls="grid medium-space"))
    
    return Section(*content, cls=f"responsive {cls}".strip())

# %% ../nbs/04_web_pages.ipynb #297f69dc
def FeaturesGrid(
    features: list,  # List of dicts: {icon, title, description, size?}
    title: str = None,  # Optional section title
    subtitle: str = None,  # Optional section subtitle
    cls: str = ""  # Additional classes
):
    """Bento-style feature grid using BeerCSS 12-column grid.
    
    Creates visual hierarchy with configurable card sizes for a modern bento box layout.
    Grid auto-fills with BeerCSS responsive column classes.
    
    Each feature dict can include a 'size' key:
    - 'big': spans half width (l6) - highlighted/featured items
    - 'small' (default): spans quarter width (l3)
    
    Responsive behavior (BeerCSS breakpoints):
    - Mobile (s): full width, stacked (s12)
    - Tablet (m): 2 columns (m6)
    - Desktop (l): 4-column bento (l3 small, l6 big)
    
    Example:
        FeaturesGrid([
            {"icon": "star", "title": "Featured", "description": "...", "size": "big"},
            {"icon": "bolt", "title": "Fast", "description": "..."},
            {"icon": "shield", "title": "Secure", "description": "..."},
        ])
    """
    cards = []
    for f in features:
        size = f.get("size", "small")
        
        # BeerCSS grid spans - responsive column classes
        if size == "big":
            span_cls = "s12 m6 l6"  # Half width on desktop
            icon_size = "extra"  # Larger icon for big cards
        else:
            span_cls = "s12 m6 l3"  # Quarter width on desktop
            icon_size = "medium"
        
        card = Card(
            Div(Icon(f["icon"], size=icon_size, cls="primary-text"), cls="center-align"),
            H5(f["title"], cls="center-align small-margin"),
            P(f["description"], cls="secondary-text center-align"),
            cls="padding card-hover fill",  # fill ensures card fills cell height
        )
        
        # Wrap in grid cell div with BeerCSS column classes
        cards.append(Div(card, cls=span_cls))
    
    content = []
    if title:
        content.append(H1(title, cls="center-align bottom-margin"))
    if subtitle:
        content.append(P(subtitle, cls="center-align secondary-text large-margin large-text"))
    
    # BeerCSS grid container with spacing
    content.append(Div(*cards, cls="grid medium-space"))
    
    return Section(
        Style(_CARD_HOVER_CSS),
        *content,
        cls=f"responsive padding {cls}".strip()
    )

# %% ../nbs/04_web_pages.ipynb #ea5213e3
def _pricing_toggle_js():
    """Returns Script + Style for pricing toggle functionality."""
    css = """
    @keyframes subtle-pulse {
        0%, 100% { transform: scale(1); }
        50% { transform: scale(1.05); }
    }
    .savings-chip { display: none; }
    .savings-chip.active { 
        display: inline-flex; 
        animation: subtle-pulse 0.3s ease-out;
    }
    .pricing-monthly { display: block; }
    .pricing-yearly { display: none; }
    .pricing-yearly.active { display: block; }
    .pricing-monthly.active { display: block; }
    .pricing-yearly:not(.active) { display: none; }
    .pricing-monthly:not(.active) { display: none; }
    """
    
    js = """
    function togglePricing(period) {
        const monthly = document.querySelectorAll('.pricing-monthly');
        const yearly = document.querySelectorAll('.pricing-yearly');
        const chip = document.querySelector('.savings-chip');
        const btnMonthly = document.querySelector('.toggle-monthly');
        const btnYearly = document.querySelector('.toggle-yearly');
        
        if (period === 'yearly') {
            monthly.forEach(el => el.classList.remove('active'));
            yearly.forEach(el => el.classList.add('active'));
            chip?.classList.add('active');
            btnMonthly?.classList.remove('fill');
            btnMonthly?.classList.add('border');
            btnYearly?.classList.add('fill');
            btnYearly?.classList.remove('border');
        } else {
            yearly.forEach(el => el.classList.remove('active'));
            monthly.forEach(el => el.classList.add('active'));
            chip?.classList.remove('active');
            btnYearly?.classList.remove('fill');
            btnYearly?.classList.add('border');
            btnMonthly?.classList.add('fill');
            btnMonthly?.classList.remove('border');
        }
    }
    """
    return [Style(css), Script(js)]


def _calc_savings_pct(monthly_price: float, yearly_price: float) -> int:
    """Calculate savings percentage for yearly vs monthly billing."""
    if monthly_price <= 0:
        return 0
    annual_monthly = monthly_price * 12
    savings = round(100 - (yearly_price / annual_monthly) * 100)
    return max(0, savings)


def _pricing_card(
    plan: dict,  # {name, monthly_price, yearly_price, features, cta_text, cta_href, highlight?}
    span_cls: str = "",  # Grid span classes (optional)
):
    """Render a single pricing card with dual price display."""
    name = plan["name"]
    monthly = plan["monthly_price"]
    yearly = plan["yearly_price"]
    features = plan["features"]
    cta_text = plan["cta_text"]
    cta_href = plan["cta_href"]
    highlight = plan.get("highlight", False)
    
    # Feature list with checkmarks (centered)
    feature_items = [
        Li(Icon("check", cls="primary-text"), " ", f)
        for f in features
    ]
    feature_list = Div(
        Ul(*feature_items, style="list-style: none; padding-left: 0; text-align: left; display: inline-block;"),
        cls="center-align",
    )
    
    # Format prices
    monthly_fmt = f"${monthly:.2f}" if isinstance(monthly, (int, float)) else monthly
    yearly_fmt = f"${yearly:.2f}" if isinstance(yearly, (int, float)) else yearly
    
    # Price display: monthly shown by default, yearly hidden
    price_display = Div(
        Div(
            H2(monthly_fmt, cls="center-align no-margin"),
            P("per month", cls="center-align secondary-text"),
            cls="pricing-monthly active",
        ),
        Div(
            H2(yearly_fmt, cls="center-align no-margin"),
            P("per year", cls="center-align secondary-text"),
            cls="pricing-yearly",
        ),
    )
    
    # Card styling - simple scale effect on hover
    card_cls = "padding round card-scale"
    if highlight:
        card_cls += " primary-container"
    
    card = Card(
        H3(name, cls="center-align"),
        price_display,
        feature_list,
        Button(cta_text, href=cta_href, cls="responsive"),
        cls=card_cls,
    )
    
    # Only wrap in Div with span class if provided (for grid layout)
    if span_cls:
        return Div(card, cls=span_cls)
    return card


def PricingSection(
    title: str,              # Section title
    plans: list,             # List of plan dicts: {name, monthly_price, yearly_price, features, cta_text, cta_href, highlight?}
    cls: str = "",           # Additional classes
):
    """Multi-tier pricing section with monthly/yearly toggle.
    
    Supports 1-3 pricing tiers with automatic responsive layout.
    User provides prices; component handles toggle, savings display, and grid.
    
    Args:
        title: Section heading (e.g., "Simple Pricing")
        plans: List of plan dicts, each containing:
            - name: Plan name (e.g., "Starter", "Pro", "Enterprise")
            - monthly_price: Monthly price as float (e.g., 9.99)
            - yearly_price: Yearly price as float (e.g., 99.99)
            - features: List of feature strings
            - cta_text: Button text (e.g., "Get Started")
            - cta_href: Button link
            - highlight: Optional bool to emphasize this tier (default False)
        cls: Additional CSS classes
    
    Example:
        PricingSection(
            title="Choose Your Plan",
            plans=[
                {"name": "Starter", "monthly_price": 9.99, "yearly_price": 99.99,
                 "features": ["5 users", "Basic support"], "cta_text": "Start Free", "cta_href": "/signup"},
                {"name": "Pro", "monthly_price": 29.99, "yearly_price": 299.99,
                 "features": ["Unlimited users", "Priority support"], "cta_text": "Get Pro", "cta_href": "/signup", "highlight": True},
            ]
        )
    """
    # Calculate max savings across all plans for the chip
    max_savings = 0
    for plan in plans:
        savings = _calc_savings_pct(plan["monthly_price"], plan["yearly_price"])
        max_savings = max(max_savings, savings)
    
    # Toggle bar: Monthly | Yearly + Savings chip
    savings_chip = Span(
        f"Save {max_savings}%", 
        cls="chip small green white-text savings-chip",
    ) if max_savings > 0 else ""
    
    toggle = Div(
        Nav(
            FhButton(
                "Monthly",
                cls="toggle-monthly left-round fill small",
                onclick="togglePricing('monthly')",
            ),
            FhButton(
                "Yearly",
                cls="toggle-yearly right-round border small",
                onclick="togglePricing('yearly')",
            ),
            cls="",
        ),
        savings_chip,
        cls="row center-align middle-align small-space bottom-margin",
    )
    
    # Determine layout based on number of plans
    num_plans = len(plans)
    
    if num_plans == 1:
        # Single card: use flexbox centering, no grid
        card = _pricing_card(plans[0], span_cls="")
        grid = Div(
            Div(card, cls="medium-width"),
            cls="row center-align",
        )
    elif num_plans == 2:
        # Two cards: use grid with s12 m6
        cards = [_pricing_card(plan, span_cls="s12 m6") for plan in plans]
        grid = Div(*cards, cls="grid medium-space")
    else:
        # Three or more cards: use grid with s12 m6 l4
        cards = [_pricing_card(plan, span_cls="s12 m6 l4") for plan in plans]
        grid = Div(*cards, cls="grid medium-space")
    
    # Assemble section - use responsive for centered max-width layout
    return Section(
        Style(_CARD_SCALE_CSS),
        *_pricing_toggle_js(),
        H2(title, cls="center-align bottom-margin"),
        toggle,
        grid,
        cls=f"responsive padding {cls}".strip(),
    )

# %% ../nbs/04_web_pages.ipynb #35bdc231
def FAQSection(title: str, faqs: list, cls: str = ""):
    """FAQ section using FAQItem from components.
    
    Wraps multiple FAQItem components with a title and consistent spacing.
    Uses FAQItem from fh_matui.components for the actual collapsible Q&A.
    
    Args:
        title: Section heading
        faqs: List of dicts with 'question' and 'answer' keys
        cls: Additional CSS classes
    """
    from fh_matui.components import FAQItem
    
    items = [
        Div(FAQItem(faq['question'], faq['answer']), cls="small-margin")
        for faq in faqs
    ]
    
    return Section(
        H2(title, cls="center-align bottom-margin"),
        Div(*items, cls="column"),
        cls=f"responsive column {cls}".strip(),
    )

# %% ../nbs/04_web_pages.ipynb #a6a029c6
# CSS for decorative sine wave border on footer
_FOOTER_WAVE_CSS = """
.footer-wave {
    position: relative;
}
.footer-wave::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 12px;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 12' preserveAspectRatio='none'%3E%3Cpath d='M0,6 Q25,0 50,6 T100,6 T150,6 T200,6 T250,6 T300,6 T350,6 T400,6 T450,6 T500,6 T550,6 T600,6 T650,6 T700,6 T750,6 T800,6 T850,6 T900,6 T950,6 T1000,6 T1050,6 T1100,6 T1150,6 T1200,6' fill='none' stroke='%23888' stroke-width='1'/%3E%3C/svg%3E");
    background-size: 100% 100%;
    background-repeat: no-repeat;
    opacity: 0.5;
}
"""

def PageFooter(
    columns: list,         # List of dicts with 'title' and 'links' (list of dicts with 'text' and 'href')
    copyright: str,        # Copyright text
    social_links: list = None,  # List of dicts with 'icon' and 'href'
    logo: str = None,      # Optional logo text
    wave_border: bool = True,   # Show decorative wave border at top
    cls: str = ""          # Additional classes
):
    """Footer with multiple columns, social links, and copyright.
    
    Features a decorative sine wave border at the top for elegant visual separation.
    Blends with page background (no container fill) for seamless appearance.
    
    Mobile-responsive: columns stack vertically on mobile (s12), 
    2-col on tablet (m6), full row on desktop using BeerCSS grid.
    """
    footer_cols = []
    
    # Calculate responsive grid spans based on total column count
    # Logo counts as a column if present
    total_cols = len(columns) + (1 if logo else 0)
    # Desktop span: divide 12 evenly, fallback to l3
    l_span = max(1, 12 // total_cols) if total_cols > 0 else 12
    
    # Logo column if provided
    if logo:
        footer_cols.append(
            Div(
                H6(logo, cls='no-margin bold'),
                cls=f's12 m6 l{l_span} padding'
            )
        )
    
    # Link columns - vertically stacked
    for col in columns:
        links = [A(link['text'], href=link['href'], cls='grey-text') for link in col.get('links', [])]
        footer_cols.append(
            Div(
                H6(col['title'], cls='no-margin bold'),
                DivVStacked(*links, cls='small-space', responsive=False, padding=False),
                cls=f's12 m6 l{l_span} padding'
            )
        )
    
    # BeerCSS grid: stacks on mobile (s12), 2-col tablet (m6), full row desktop (l_span)
    footer_row = Div(*footer_cols, cls="grid")
    
    # Bottom section: copyright + social links
    # Grid-based so they stack on mobile too
    bottom_left = Div(
        P(copyright, cls='small-text grey-text no-margin'),
        cls='s12 m6'
    )
    
    bottom_right = Div(cls='s12 m6')
    if social_links:
        social_icons = [A(Icon(s['icon']), href=s['href'], cls='grey-text') for s in social_links]
        bottom_right = Div(
            Div(*social_icons, cls='row small-space'),
            cls='s12 m6 right-align'
        )
    
    bottom_section = Div(bottom_left, bottom_right, cls="grid")
    
    # Footer blends with page (no surface-container), wave provides visual separation
    footer_cls = f'footer-wave {cls}'.strip() if wave_border else cls
    
    # Include wave CSS if enabled
    wave_style = Style(_FOOTER_WAVE_CSS) if wave_border else ""
    
    return ft_hx('footer')(
        wave_style,
        Div(
            footer_row,
            Hr(),
            bottom_section,
            cls="responsive padding",  # Content centered
        ),
        cls=footer_cls,
        style="padding-top: 1.5rem;" if wave_border else "",  # Space for wave
    )

# %% ../nbs/04_web_pages.ipynb #be3a76d2
_LANDING_NAV_CSS = """
@media (max-width: 599px) {
    .nav-desktop { display: none !important; }
}
@media (min-width: 600px) {
    .nav-mobile { display: none !important; }
}
"""

def LandingNavBar(
    brand_name: str,        # Brand name for the navbar
    links: list = None,     # List of dicts with 'text' and 'href'
    actions: list = None,   # List of action buttons (Buttons/Links)
    sticky: bool = True,    # Whether navbar sticks to top
    drawer_id: str = "landing-nav-drawer",  # ID for mobile drawer
    cls: str = ""           # Additional classes
):
    """Landing page navigation bar with frosted glass effect and mobile drawer.
    
    Desktop (>=600px): horizontal bar with brand, links, and action buttons.
    Mobile (<600px): brand + hamburger button opens a slide-in drawer
    via BeerCSS data-ui toggle (zero custom JavaScript).
    
    Reuses NavContainer from components for the mobile drawer — same dialog
    pattern used in app navigation sidebars.
    
    Args:
        brand_name: Brand name/logo text
        links: Navigation links [{'text': 'Features', 'href': '#features'}, ...]
        actions: Action elements (Buttons, etc.) for CTA
        sticky: Whether navbar sticks to top while scrolling (default: True)
        drawer_id: HTML id for the mobile drawer dialog (default: 'landing-nav-drawer')
        cls: Additional CSS classes
    """
    from fh_matui.components import NavContainer
    
    # Hide the drawer dialog on desktop — BeerCSS makes dialog.left a
    # persistent sidebar on large screens; we override that here.
    drawer_hide_css = f"@media (min-width: 600px) {{ #{drawer_id} {{ display: none !important; }} }}"
    
    # === Desktop layout (hidden on mobile) ===
    desktop_items = []
    if links:
        for link in links:
            desktop_items.append(A(link['text'], href=link['href'], cls="padding"))
    if actions:
        desktop_items.extend(actions)
    desktop_section = Div(*desktop_items, cls="nav-desktop") if desktop_items else ""
    
    # === Mobile hamburger button (hidden on desktop) ===
    hamburger = FhButton(
        I("menu"),
        cls="transparent circle large nav-mobile",
        data_ui=f"#{drawer_id}",
    )
    
    # === Header bar (always visible) ===
    header_items = [
        A(H5(brand_name, cls="no-margin bold"), href="/"),
        Div(cls="max"),  # spacer
        desktop_section,
        hamburger,
    ]
    inner = Div(*header_items, cls="responsive row middle-align")
    
    sticky_cls = "fixed top left right" if sticky else ""
    toolbar_cls = f"blur large-blur surface-container-low {sticky_cls} {cls}".strip()
    header = Header(inner, cls=toolbar_cls, style="z-index: 10;")
    
    # === Mobile drawer (BeerCSS dialog, toggled via data-ui) ===
    # On mobile, dialog.left is hidden by default and slides in when
    # data-ui toggles the active class. On desktop, our CSS hides it.
    drawer_items = []
    if links:
        for link in links:
            drawer_items.append(
                Li(
                    A(link['text'], href=link['href']),
                    cls="wave round",
                    data_ui=f"#{drawer_id}",
                )
            )
    
    drawer = NavContainer(
        *drawer_items,
        title=brand_name,
        position='left',
        close_button=True,
        id=drawer_id,
        cls='',  # Not active by default — data-ui toggles it
    )
    
    # Append action buttons after the list inside the drawer
    if actions:
        action_wrappers = [Hr()]
        for action in actions:
            action_wrappers.append(Div(action, cls="padding", data_ui=f"#{drawer_id}"))
        drawer.children.extend(action_wrappers)
    
    # display:contents eliminates this wrapper from layout so the navbar
    # doesn't create a separate block/section in the page flow.
    return Div(
        Style(_LANDING_NAV_CSS + drawer_hide_css),
        header, drawer,
        style="display: contents"
    )

# %% ../nbs/04_web_pages.ipynb #a45e13b8
# Section spacing: padding for internal space, margin for breathing room between sections
SECTION_STYLE = "large-padding"  # Internal padding
SECTION_MARGIN = "extra-margin bottom-margin"  # Vertical spacing between sections

# Standard footer columns - consistent across all pages (3 items each for balanced width)
STANDARD_FOOTER_COLUMNS = [
    {"title": "Features", "links": [
        {"text": "Tracking", "href": "#features"},
        {"text": "Budgeting", "href": "#features"},
        {"text": "Planning", "href": "#features"},
    ]},
    {"title": "Support", "links": [
        {"text": "Help Center", "href": "/help"},
        {"text": "Contact", "href": "/contact"},
        {"text": "FAQ", "href": "#faq"},
    ]},
    {"title": "Legal", "links": [
        {"text": "Terms of Use", "href": "/terms"},
        {"text": "Privacy Policy", "href": "/privacy"},
        {"text": "Security", "href": "/security"},
    ]},
]

# CSS for smooth scrolling and sticky navbar offset (5rem = ~4rem navbar + 1rem breathing room)
_LANDING_PAGE_CSS = """
html {
    scroll-behavior: smooth;
}
/* Offset anchor targets to account for sticky navbar */
#benefits, #features, #pricing, #faq {
    scroll-margin-top: 5rem;
}
/* Centered mini-wave separator between sections */
.section-wave {
    display: flex;
    justify-content: center;
    padding: 1.5rem 0;
}
.section-wave::before {
    content: '';
    width: 150px;
    height: 12px;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 150 12' preserveAspectRatio='none'%3E%3Cpath d='M0,6 Q12.5,0 25,6 T50,6 T75,6 T100,6 T125,6 T150,6' fill='none' stroke='%23888' stroke-width='1.5'/%3E%3C/svg%3E");
    background-size: 100% 100%;
    background-repeat: no-repeat;
    opacity: 0.4;
}
"""

# Internal helper - centered wave separator between sections
def _SectionWave(): return Div(cls="section-wave")

def LandingPageSimple(
    # Required
    brand_name: str,
    hero_title: str,
    hero_subtitle: str,
    hero_primary_cta: dict,           # {'text': 'Get Started', 'href': '/signup'}
    footer_copyright: str,            # Required copyright text
    # Optional Hero
    hero_secondary_cta: dict = None,  # Optional secondary CTA
    # Optional Benefits (FeatureShowcase - alternating rows)
    benefits: list = None,            # List of dicts: {title, description, icon?, image_src?}
    benefits_title: str = None,
    benefits_subtitle: str = None,
    # Optional Features (FeaturesGrid - icon cards)
    features: list = None,            # List of dicts: {icon, title, description}
    features_title: str = None,
    features_subtitle: str = None,
    # Optional Pricing
    pricing_plans: list = None,       # List of plan dicts for PricingSection
    pricing_title: str = "Pricing",
    # Optional FAQ
    faqs: list = None,                # List of FAQ dicts
    faq_title: str = "Frequently Asked Questions",
    # Optional Footer extras
    footer_columns: list = None,
    footer_social_links: list = None,
    # Nav customization
    nav_links: list = None,           # Override default nav links
    nav_actions: list = None,         # CTA buttons in navbar
    cls: str = "",
):
    """Landing page component - supply your content, get a complete page.
    
    This is a reusable template component. Pass your brand info, hero content,
    features, pricing, and FAQs - it assembles a complete marketing landing page.
    
    Includes smooth scrolling and proper anchor offset to prevent sticky navbar
    from hiding section headings when navigating via anchor links.
    
    Layout approach: Main uses "max" for full-width backgrounds.
    Each section handles its own content centering with "responsive" wrapper.
    This allows gradients/backgrounds to extend edge-to-edge naturally.
    
    Section order: Hero → Benefits → Features → Pricing → FAQ → Footer
    
    Required: brand_name, hero_title, hero_subtitle, hero_primary_cta, footer_copyright
    Optional: Benefits, Features, Pricing, FAQ (pass None to skip any section)
    """
    # Static nav links - convention: all sections available
    default_nav_links = [
        {"text": "Benefits", "href": "#benefits"},
        {"text": "Features", "href": "#features"},
        {"text": "Pricing", "href": "#pricing"},
        {"text": "FAQ", "href": "#faq"},
    ]
    navbar = LandingNavBar(
        brand_name=brand_name,
        links=nav_links or default_nav_links,
        actions=nav_actions,
        sticky=True,
    )
    
    main_sections = []
    
    # 1. Hero (required) - full-width, content centered internally
    hero = HeroSection(
        title=hero_title,
        subtitle=hero_subtitle,
        primary_cta_text=hero_primary_cta['text'],
        primary_cta_href=hero_primary_cta['href'],
        secondary_cta_text=hero_secondary_cta.get('text') if hero_secondary_cta else None,
        secondary_cta_href=hero_secondary_cta.get('href') if hero_secondary_cta else None,
        cls=SECTION_MARGIN,
    )
    main_sections.append(hero)
    
    # 2. Benefits - FeatureShowcase (optional) - has responsive internally
    if benefits:
        main_sections.append(_SectionWave())
        benefits_section = FeatureShowcase(
            features=benefits,
            title=benefits_title,
            subtitle=benefits_subtitle,
            cls=SECTION_STYLE,
        )
        main_sections.append(Div(benefits_section, id="benefits", cls=SECTION_MARGIN))
    
    # 3. Features - FeaturesGrid (optional) - has responsive internally
    if features:
        main_sections.append(_SectionWave())
        features_section = FeaturesGrid(
            features=features,
            title=features_title,
            subtitle=features_subtitle,
            cls=SECTION_STYLE,
        )
        main_sections.append(Div(features_section, id="features", cls=SECTION_MARGIN))
    
    # 4. Pricing (optional) - has responsive internally
    if pricing_plans:
        main_sections.append(_SectionWave())
        pricing_section = PricingSection(
            title=pricing_title,
            plans=pricing_plans,
            cls=SECTION_STYLE,
        )
        main_sections.append(Div(pricing_section, id="pricing", cls=SECTION_MARGIN))
    
    # 5. FAQ (optional) - has responsive internally
    if faqs:
        main_sections.append(_SectionWave())
        faq_section = FAQSection(
            title=faq_title,
            faqs=faqs,
            cls=SECTION_STYLE,
        )
        main_sections.append(Div(faq_section, id="faq", cls=SECTION_MARGIN))
    
    # Footer (required) - use standard columns if none provided
    footer_el = PageFooter(
        columns=footer_columns or STANDARD_FOOTER_COLUMNS,
        copyright=footer_copyright,
        social_links=footer_social_links,
        logo=brand_name,
        cls="large-padding",
    )
    
    # Main uses "max" for full-width - sections extend edge-to-edge
    # Each section handles its own content centering with responsive
    main_content = Main(*main_sections, cls="max")
    
    # Include CSS for smooth scrolling, anchor offset, and card hover animations
    return Div(
        Style(_LANDING_PAGE_CSS + _CARD_HOVER_CSS),
        navbar, 
        main_content, 
        footer_el, 
        cls=f"column {cls}".strip()
    )

# %% ../nbs/04_web_pages.ipynb #5aa5632d
def MarkdownSection(
    content: str,           # Markdown text to render
    title: str = None,      # Optional section title (rendered as H3 for appropriate size)
    cls: str = "",          # Additional classes
):
    """Renders markdown content server-side for SEO compatibility.
    
    Uses python-markdown to convert markdown to HTML on the server.
    Search engines see fully rendered HTML (no JavaScript required).
    Content is centered using BeerCSS large-width + row center-align pattern.
    
    Great for text-heavy pages like Privacy Policy, Terms, About, Blog posts, etc.
    
    Args:
        content: Markdown text string (can include headers, lists, links, code blocks, tables)
        title: Optional page title (rendered as H3 for blog-appropriate sizing)
        cls: Additional CSS classes
    
    Example:
        MarkdownSection(
            title="Privacy Policy",
            content='''
## Introduction
We value your privacy...

## Data Collection
- We collect minimal data
- We never sell your data
            '''
        )
    """
    import markdown
    
    # Server-side render markdown to HTML (SEO-friendly)
    html_content = markdown.markdown(
        content, 
        extensions=['tables', 'fenced_code', 'nl2br']
    )
    
    elements = []
    if title:
        # Use H3 for page title - appropriately sized for content pages
        elements.append(H3(title, cls="bold"))
    
    # NotStr tells FastHTML to render raw HTML without escaping
    elements.append(NotStr(html_content))
    
    # Grid-based centering: 12-column grid with content in center
    # - Mobile (s): full width (s12)
    # - Tablet (m): 1-10-1 split (content gets 10/12 = ~83%)
    # - Desktop (l): 2-8-2 split (content gets 8/12 = ~67%)
    # card-hover adds subtle lift + shadow on hover
    content_card = Article(*elements, cls=f"surface-container round large-padding card-hover {cls}".strip())
    
    return Div(
        Style(_CARD_HOVER_CSS),
        Div(cls="s0 m1 l2"),  # Left spacer (hidden on mobile)
        Div(content_card, cls="s12 m10 l8"),  # Center content
        Div(cls="s0 m1 l2"),  # Right spacer (hidden on mobile)
        cls="grid",
    )

# %% ../nbs/04_web_pages.ipynb #250d86cb
def ContentPage(
    brand_name: str,                  # Brand name for navbar
    footer_copyright: str,            # Copyright text for footer
    *sections,                        # Content sections (MarkdownSection, custom Divs, etc.)
    nav_links: list = None,           # Override default nav links
    nav_actions: list = None,         # CTA buttons in navbar
    footer_columns: list = None,      # Footer link columns (uses STANDARD_FOOTER_COLUMNS by default)
    footer_social_links: list = None, # Footer social icons
    cls: str = "",
):
    """Generic content page with navbar, flexible content area, and footer.
    
    A shell template for text-heavy pages like Privacy Policy, Terms of Service,
    Security, About, Blog posts, etc. Developer passes any number of content sections.
    
    Layout: Navbar (sticky) -> Content sections (centered with 'responsive') -> Footer
    
    Uses STANDARD_FOOTER_COLUMNS by default for consistent footer across all pages.
    
    Args:
        brand_name: Brand name for navbar
        footer_copyright: Copyright text
        *sections: One or more content sections (MarkdownSection, custom Divs, etc.)
        nav_links: Navigation links [{'text': 'Home', 'href': '/'}, ...]
        nav_actions: Action buttons for navbar
        footer_columns: Footer link columns (defaults to STANDARD_FOOTER_COLUMNS)
        footer_social_links: Social media icons for footer
        cls: Additional CSS classes
    
    Example:
        ContentPage(
            brand_name="MyBrand",
            footer_copyright="2026 MyBrand",
            MarkdownSection(title="Privacy Policy", content="...markdown..."),
            nav_links=[{'text': 'Home', 'href': '/'}],
        )
    """
    # Default nav links for content pages - just Home, no anchor links
    default_nav_links = [
        {"text": "Home", "href": "/"},
    ]
    
    navbar = LandingNavBar(
        brand_name=brand_name,
        links=nav_links or default_nav_links,
        actions=nav_actions,
        sticky=True,
    )
    
    footer_el = PageFooter(
        columns=footer_columns or STANDARD_FOOTER_COLUMNS,
        copyright=footer_copyright,
        social_links=footer_social_links,
        logo=brand_name,
        cls="large-padding",
    )
    
    # Main content area - sections handle their own centering
    main_content = Main(*sections, cls="large-padding")
    
    return Div(navbar, main_content, footer_el, cls=f"column {cls}".strip())
