# testing


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

## Test App Creation

A standardized way to create test apps in Jupyter notebooks:

------------------------------------------------------------------------

### create_test_app

``` python

def create_test_app(
    debug:bool=True, # Enable debug mode
)->tuple: # Tuple containing (app, rt) - FastHTML app instance and route decorator

```

*Create a standardized test app for Jupyter notebooks with Tailwind.*

## Test Page Wrapper

A wrapper for creating consistent test pages:

------------------------------------------------------------------------

### create_test_page

``` python

def create_test_page(
    title:str, # Page title
    content:VAR_POSITIONAL, # Page content elements
    container:bool=True, # Wrap in container
    custom_theme_names:Optional=None, # Custom themes for selector
)->functools.partial(<function ft_hx at 0x7f61d021d080>, 'div'): # Div containing complete page layout with navbar and content

```

*Create a standardized test page layout with optional theme selector.*

## Jupyter Notebook Utilities

Helper functions for working with FastHTML in Jupyter:

------------------------------------------------------------------------

### start_test_server

``` python

def start_test_server(
    app:FastHTML, # FastHTML app instance created by create_test_app or fast_app
    port:int=8000, # Port
)->JupyUvi: # JupyUvi server instance for Jupyter notebook testing

```

*Start a test server and return the JupyUvi instance.*

Usage: server = start_test_server(app) HTMX() \# Display the app

    # Later, in another cell:
    server.stop()

## Example Usage

Here’s how to use the testing utilities in a notebook:

``` python
from cjm_fasthtml_tailwind.core.base import combine_classes
from cjm_fasthtml_tailwind.utilities.spacing import *
from cjm_fasthtml_tailwind.utilities.sizing import *
from cjm_fasthtml_tailwind.utilities.layout import *
from cjm_fasthtml_tailwind.utilities.flexbox_and_grid import *
from cjm_fasthtml_tailwind.utilities.backgrounds import bg
from cjm_fasthtml_tailwind.utilities.typography import font_size, text_color
```

``` python
# Create a test app with default settings
app, rt = create_test_app()

padding_val = 2
gap_val = 2

# Define a test route
@rt
def index():
    return create_test_page(
        "Tailwind Test Page",
        Div(
            # Header spans full width
            Header("Dashboard", cls=combine_classes(col_span.full, p(padding_val), bg.blue._500, text_color.white)),
            
            # Sidebar
            Aside("Sidebar", cls=combine_classes(row_span(2), p(padding_val), bg.gray._200)),
            
            # Main content
            Main("Main Content", cls=combine_classes(col_span(2), p(padding_val))),
            
            # Stats cards
            Div("Stat 1", cls= combine_classes(p(padding_val), bg.green._100)),
            Div("Stat 2", cls= combine_classes(p(padding_val), bg.yellow._100)),
            
            cls=combine_classes(
                grid_display,
                grid_cols(3),
                grid_rows(3),
                gap(gap_val),
                h.screen
            )
        )
    )

# Start the server
server = start_test_server(app)
HTMX()
```

<script>
document.body.addEventListener('htmx:configRequest', (event) => {
    if(event.detail.path.includes('://')) return;
    htmx.config.selfRequestsOnly=false;
    event.detail.path = `${location.protocol}//${location.hostname}:8000${event.detail.path}`;
});
</script>

<iframe src="http://localhost:8000/" style="width: 100%; height: auto; border: none;" onload="{
        let frame = this;
        window.addEventListener('message', function(e) {
            if (e.source !== frame.contentWindow) return; // Only proceed if the message is from this iframe
            if (e.data.height) frame.style.height = (e.data.height+1) + 'px';
        }, false);
    }" allow="accelerometer; autoplay; camera; clipboard-read; clipboard-write; display-capture; encrypted-media; fullscreen; gamepad; geolocation; gyroscope; hid; identity-credentials-get; idle-detection; magnetometer; microphone; midi; payment; picture-in-picture; publickey-credentials-get; screen-wake-lock; serial; usb; web-share; xr-spatial-tracking"></iframe> 

``` python
# Stop the server when done
server.stop()
```

## Comprehensive Example - Modern Dashboard Layout

A more comprehensive example demonstrating various Tailwind utilities to
create a modern dashboard:

``` python
# Import all utilities we'll use
from fasthtml.common import *
from cjm_fasthtml_tailwind.utilities.accessibility import not_sr_only, sr_only
from cjm_fasthtml_tailwind.utilities.flexbox_and_grid import (
    auto_cols, auto_rows, basis, col, col_end, col_span, col_start, 
    content, flex, flex_between, flex_center, flex_col_center, 
    flex_direction, flex_wrap, gap, grid_center, grid_cols, grid_flow, 
    grid_rows, grow, items, justify, justify_items, justify_self, 
    order, place_content, place_items, place_self, responsive_grid, 
    row, row_end, row_span, row_start, self_align, shrink
)
from cjm_fasthtml_tailwind.utilities.layout import (
    aspect, bottom, box, box_decoration, break_util, center_absolute, 
    clear, columns, display_tw, end, float_tw, full_bleed, inset, 
    isolation, left, object_fit, object_position, 
    overflow, overscroll, position, right, stack_context, 
    start, sticky_top, top, visibility, z
)
from cjm_fasthtml_tailwind.utilities.sizing import (
    container, full_screen, full_size, h, max_h, max_w, min_h, 
    min_w, size, size_util, square, w
)
from cjm_fasthtml_tailwind.utilities.spacing import (
    m, margin, me, ms, p, pad, pe, ps, space
)
from cjm_fasthtml_tailwind.core.base import combine_classes
from cjm_fasthtml_tailwind.core.resources import get_tailwind_headers
```

``` python
# Create a new test app
from cjm_fasthtml_tailwind.utilities.typography import font_size, font_weight, font_family, text_color
from cjm_fasthtml_tailwind.utilities.backgrounds import bg
from cjm_fasthtml_tailwind.utilities.borders import rounded, border
from cjm_fasthtml_tailwind.utilities.effects import shadow
from cjm_fasthtml_tailwind.utilities.transitions_and_animation import transition

app, rt = create_test_app()

@rt
def index():
    # Navbar component
    navbar = Nav(
        Div(
            # Logo section
            Div(
                H1("Dashboard", cls=combine_classes(font_size.xl, font_weight.bold, text_color.white)),
                cls=combine_classes(flex_display, items.center)
            ),
            
            # Navigation links
            Div(
                A("Home", href="#", cls=combine_classes(p.x(4), p.y(2), text_color.white, bg.blue._700.hover, rounded.full)),
                A("Analytics", href="#", cls=combine_classes(p.x(4), p.y(2), text_color.white, bg.blue._700.hover, rounded.full)),
                A("Reports", href="#", cls=combine_classes(p.x(4), p.y(2), text_color.white, bg.blue._700.hover, rounded.full)),
                A("Settings", href="#", cls=combine_classes(p.x(4), p.y(2), text_color.white, bg.blue._700.hover, rounded.full)),
                cls=combine_classes(flex_display, gap(2))
            ),
            
            # User profile section
            Div(
                Div(
                    Span("John Doe", cls=text_color.white),
                    Div(cls=combine_classes(square(10), bg.white, rounded.full)),
                    cls=combine_classes(flex_display, items.center, gap(3))
                ),
                cls=combine_classes(m.l.auto)
            ),
            
            cls=combine_classes(
                flex_between(),
                container(),
                m.x.auto,
                p.x(4)
            )
        ),
        cls=combine_classes(
            w.full,
            bg.blue._600,
            sticky_top(),
            z(50)
        )
    )
    
    # Main layout with sidebar and content
    main_layout = Div(
        # Sidebar
        Aside(
            # Sidebar header
            H2("Menu", cls=combine_classes(p(4), font_size.lg, font_weight.semibold, border.b())),
            
            # Menu items
            Ul(
                Li(
                    A(
                        I(cls=combine_classes("fas fa-chart-line", m.r(0.5))),
                        "Dashboard",
                        href="#",
                        cls=combine_classes(
                            flex_display,
                            items.center,
                            p(4),
                            bg.gray._100.hover,
                            transition.colors
                        )
                    )
                ),
                Li(
                    A(
                        I(cls=combine_classes("fas fa-users", m.r(0.5))),
                        "Users",
                        href="#",
                        cls=combine_classes(
                            flex_display,
                            items.center,
                            p(4),
                            bg.gray._100.hover,
                            transition.colors
                        )
                    )
                ),
                Li(
                    A(
                        I(cls=combine_classes("fas fa-cog", m.r(0.5))),
                        "Settings",
                        href="#",
                        cls=combine_classes(
                            flex_display,
                            items.center,
                            p(4),
                            bg.gray._100.hover,
                            transition.colors
                        )
                    )
                ),
                cls=space.y(1)
            ),
            
            cls=combine_classes(
                w(64),
                min_h.screen,
                bg.white,
                border.r(),
                position.sticky,
                top(16),  # Account for navbar height
                overflow.y.auto
            )
        ),
        
        # Main content area
        Main(
            # Page header
            Header(
                H1("Analytics Dashboard", cls=combine_classes(font_size._3xl, font_weight.bold)),
                P("Last updated: " + "December 2024", cls=text_color.gray._600),
                cls=combine_classes(p(6), bg.white, rounded.lg, shadow(), m.b(6))
            ),
            
            # Stats grid
            Div(
                # Stat card 1
                Div(
                    Div(
                        H3("Total Users", cls=combine_classes(text_color.gray._600, font_size.sm)),
                        P("12,345", cls=combine_classes(font_size._3xl, font_weight.bold, text_color.blue._600)),
                        P("↑ 12.5%", cls=combine_classes(text_color.green._500, font_size.sm)),
                        cls=pad(x=6, y=4)
                    ),
                    cls=combine_classes(bg.white, rounded.lg, shadow(), overflow.hidden)
                ),
                
                # Stat card 2
                Div(
                    Div(
                        H3("Revenue", cls=combine_classes(text_color.gray._600, font_size.sm)),
                        P("$45,678", cls=combine_classes(font_size._3xl, font_weight.bold, text_color.green._600)),
                        P("↑ 8.2%", cls=combine_classes(text_color.green._500, font_size.sm)),
                        cls=pad(x=6, y=4)
                    ),
                    cls=combine_classes(bg.white, rounded.lg, shadow(), overflow.hidden)
                ),
                
                # Stat card 3
                Div(
                    Div(
                        H3("Active Sessions", cls=combine_classes(text_color.gray._600, font_size.sm)),
                        P("892", cls=combine_classes(font_size._3xl, font_weight.bold, text_color.purple._600)),
                        P("↓ 3.1%", cls=combine_classes(text_color.red._500, font_size.sm)),
                        cls=pad(x=6, y=4)
                    ),
                    cls=combine_classes(bg.white, rounded.lg, shadow(), overflow.hidden)
                ),
                
                # Stat card 4
                Div(
                    Div(
                        H3("Conversion Rate", cls=combine_classes(text_color.gray._600, font_size.sm)),
                        P("3.42%", cls=combine_classes(font_size._3xl, font_weight.bold, text_color.orange._600)),
                        P("↑ 0.8%", cls=combine_classes(text_color.green._500, font_size.sm)),
                        cls=pad(x=6, y=4)
                    ),
                    cls=combine_classes(bg.white, rounded.lg, shadow(), overflow.hidden)
                ),
                
                cls=responsive_grid(
                    mobile=1,
                    tablet=2,
                    desktop=4,
                    gap_size=6
                )
            ),
            
            # Charts section
            Div(
                # Main chart
                Div(
                    H2("Revenue Overview", cls=combine_classes(font_size.xl, font_weight.semibold, m.b(4))),
                    Div(
                        "Chart placeholder - Revenue trends over time",
                        cls=combine_classes(
                            h(64),
                            bg.gray._100,
                            rounded(),
                            flex_center()
                        )
                    ),
                    cls=combine_classes(
                        col_span(2),
                        bg.white,
                        rounded.lg,
                        shadow(),
                        p(6)
                    )
                ),
                
                # Side panel
                Div(
                    H2("Top Products", cls=combine_classes(font_size.xl, font_weight.semibold, m.b(4))),
                    Ul(
                        Li("Product A - $12,345", cls=pad(y=2)),
                        Li("Product B - $10,234", cls=pad(y=2)),
                        Li("Product C - $8,912", cls=pad(y=2)),
                        Li("Product D - $7,234", cls=pad(y=2)),
                        Li("Product E - $5,123", cls=pad(y=2)),
                        cls=space.y(2)
                    ),
                    cls=combine_classes(
                        bg.white,
                        rounded.lg,
                        shadow(),
                        p(6)
                    )
                ),
                
                cls=combine_classes(
                    grid_display,
                    grid_cols(3),
                    gap(6),
                    m.t(6)
                )
            ),
            
            # Table section
            Div(
                H2("Recent Transactions", cls=combine_classes(font_size.xl, font_weight.semibold, m.b(4))),
                Div(
                    Table(
                        Thead(
                            Tr(
                                Th("ID", cls=pad(x=6, y=3)),
                                Th("Customer", cls=pad(x=6, y=3)),
                                Th("Amount", cls=pad(x=6, y=3)),
                                Th("Status", cls=pad(x=6, y=3)),
                                Th("Date", cls=pad(x=6, y=3)),
                                cls=bg.gray._50
                            )
                        ),
                        Tbody(
                            Tr(
                                Td("#001", cls=pad(x=6, y=4)),
                                Td("John Smith", cls=pad(x=6, y=4)),
                                Td("$234.50", cls=pad(x=6, y=4)),
                                Td(
                                    Span("Completed", cls=combine_classes(p.x(2), p.y(1), bg.green._100, text_color.green._800, rounded.full, font_size.sm)),
                                    cls=pad(x=6, y=4)
                                ),
                                Td("Dec 1, 2024", cls=pad(x=6, y=4)),
                                cls=border.t()
                            ),
                            Tr(
                                Td("#002", cls=pad(x=6, y=4)),
                                Td("Jane Doe", cls=pad(x=6, y=4)),
                                Td("$567.80", cls=pad(x=6, y=4)),
                                Td(
                                    Span("Pending", cls=combine_classes(p.x(2), p.y(1), bg.yellow._100, text_color.yellow._800, rounded.full, font_size.sm)),
                                    cls=pad(x=6, y=4)
                                ),
                                Td("Dec 1, 2024", cls=pad(x=6, y=4)),
                                cls=border.t()
                            ),
                            Tr(
                                Td("#003", cls=pad(x=6, y=4)),
                                Td("Bob Johnson", cls=pad(x=6, y=4)),
                                Td("$123.45", cls=pad(x=6, y=4)),
                                Td(
                                    Span("Failed", cls=combine_classes(p.x(2), p.y(1), bg.red._100, text_color.red._800, rounded.full, font_size.sm)),
                                    cls=pad(x=6, y=4)
                                ),
                                Td("Dec 1, 2024", cls=pad(x=6, y=4)),
                                cls=border.t()
                            )
                        ),
                        cls=str(min_w.full)
                    ),
                    cls=overflow.x.auto
                ),
                cls=combine_classes(
                    bg.white,
                    rounded.lg,
                    shadow(),
                    p(6),
                    m.t(6)
                )
            ),
            
            cls=combine_classes(
                flex(1),
                p(6),
                bg.gray._50,
                min_h.screen
            )
        ),
        
        cls=combine_classes(flex_display, min_h.screen)
    )
    
    return Div(
        navbar,
        main_layout,
        cls=font_family.sans
    )

# Start the server
server = start_test_server(app, port=8001)
HTMX(port=8001)
```

<script>
document.body.addEventListener('htmx:configRequest', (event) => {
    if(event.detail.path.includes('://')) return;
    htmx.config.selfRequestsOnly=false;
    event.detail.path = `${location.protocol}//${location.hostname}:8001${event.detail.path}`;
});
</script>

<iframe src="http://localhost:8001/" style="width: 100%; height: auto; border: none;" onload="{
        let frame = this;
        window.addEventListener('message', function(e) {
            if (e.source !== frame.contentWindow) return; // Only proceed if the message is from this iframe
            if (e.data.height) frame.style.height = (e.data.height+1) + 'px';
        }, false);
    }" allow="accelerometer; autoplay; camera; clipboard-read; clipboard-write; display-capture; encrypted-media; fullscreen; gamepad; geolocation; gyroscope; hid; identity-credentials-get; idle-detection; magnetometer; microphone; midi; payment; picture-in-picture; publickey-credentials-get; screen-wake-lock; serial; usb; web-share; xr-spatial-tracking"></iframe> 

``` python
# Stop the server when done
server.stop()
```

## Comprehensive Example - Card-Based Layout

Another example showing a card-based layout with various utility
combinations:

``` python
# Create another test app for the card-based layout
from cjm_fasthtml_tailwind.utilities.backgrounds import bg_linear, from_color, to_color
from cjm_fasthtml_tailwind.utilities.typography import font_size, font_family, font_weight, text_color, text_align, line_through
from cjm_fasthtml_tailwind.utilities.effects import shadow, ring, ring_color, shadow_color
from cjm_fasthtml_tailwind.utilities.transitions_and_animation import transition, ease, duration, animate
from cjm_fasthtml_tailwind.utilities.borders import outline, outline_offset, outline_style

app2, rt2 = create_test_app()

@rt2
def index():
    # Header with centered content
    header = Header(
        Div(
            H1("Product Showcase", cls=combine_classes(font_size._4xl, font_weight.bold, text_color.white)),
            P("Discover our amazing products", cls=combine_classes(font_size.xl, text_color.gray._200)),
            cls=combine_classes(
                container(),
                m.x.auto,
                pad(x=4, y=16),
                text_align.center
            )
        ),
        cls=combine_classes(bg_linear.to_r, from_color.blue._600, to_color.purple._600)
    )
    
    # Filter section
    filter_section = Section(
        Div(
            # Filter title
            H2("Filter by Category", cls=combine_classes(font_size.lg, font_weight.semibold, m.b(4))),
            
            # Filter buttons
            Div(
                Button("All", cls=combine_classes(
                    p.x(6), p.y(2),
                    bg.blue._600, text_color.white, rounded.full,
                    bg.blue._700.hover, transition.colors
                )),
                Button("Electronics", cls=combine_classes(
                    p.x(6), p.y(2),
                    bg.gray._200, text_color.gray._700, rounded.full,
                    bg.gray._300.hover, transition.colors
                )),
                Button("Clothing", cls=combine_classes(
                    p.x(6), p.y(2),
                    bg.gray._200, text_color.gray._700, rounded.full,
                    bg.gray._300.hover, transition.colors
                )),
                Button("Books", cls=combine_classes(
                    p.x(6), p.y(2),
                    bg.gray._200, text_color.gray._700, rounded.full,
                    bg.gray._300.hover, transition.colors
                )),
                cls=combine_classes(
                    flex_display,
                    flex_wrap.wrap,
                    gap(3),
                    justify.center
                )
            ),
            cls=combine_classes(
                container(),
                m.x.auto,
                pad(x=4, y=8)
            )
        ),
        cls=combine_classes(bg.gray._50, border.b())
    )
    
    # Product cards grid
    products_grid = Main(
        Div(
            # Product Card 1
            Article(
                # Image placeholder with aspect ratio
                Div(
                    Img(src="https://via.placeholder.com/400x300", 
                        alt="Product 1",
                        cls=combine_classes(w.full, h.full, object_fit.cover)),
                    cls=combine_classes(aspect.video, overflow.hidden, bg.gray._200)
                ),
                
                # Card content
                Div(
                    # Product title and price
                    Div(
                        H3("Premium Headphones", cls=combine_classes(font_size.lg, font_weight.semibold)),
                        P("$199.99", cls=combine_classes(font_size.xl, font_weight.bold, text_color.blue._600)),
                        cls=combine_classes(flex_display, justify.between, items.start, m.b(2))
                    ),
                    
                    # Product description
                    P("High-quality wireless headphones with noise cancellation", 
                      cls=combine_classes(text_color.gray._600, font_size.sm, m.b(4))),
                    
                    # Rating
                    Div(
                        Span("⭐⭐⭐⭐⭐", cls=text_color.yellow._400),
                        Span("(4.8)", cls=combine_classes(text_color.gray._500, font_size.sm, m.l(2))),
                        cls=combine_classes(flex_display, items.center, m.b(4))
                    ),
                    
                    # Action buttons
                    Div(
                        Button("Add to Cart", cls=combine_classes(
                            flex(1),
                            pad(x=4, y=2),
                            bg.blue._600, text_color.white, rounded(),
                            bg.blue._700.hover, transition.colors
                        )),
                        Button("♥", cls=combine_classes(
                            square(10),
                            bg.gray._100, rounded(),
                            bg.red._100.hover, text_color.red._600.hover, transition.colors,
                            m.l(2)
                        )),
                        cls=combine_classes(flex_display, items.center)
                    ),
                    
                    cls=p(6)
                ),
                
                cls=combine_classes(
                    bg.white, rounded.lg, shadow.md,
                    overflow.hidden,
                    shadow.xl.hover, transition.shadow,
                    flex_display,
                    flex_direction.col
                )
            ),
            
            # Product Card 2 - Featured
            Article(
                # Featured badge
                Div(
                    Span("Featured", cls=combine_classes(p.x(3), p.y(1), bg.yellow._400, text_color.yellow._900, font_size.sm, font_weight.semibold, rounded.full)),
                    cls=combine_classes(position.absolute, top(4), right(4), z(10))
                ),
                
                # Image with overlay
                Div(
                    Img(src="https://via.placeholder.com/400x300", 
                        alt="Product 2",
                        cls=combine_classes(w.full, h.full, object_fit.cover)),
                    # Gradient overlay
                    Div(cls=combine_classes(
                        position.absolute,
                        inset(0),
                        bg_linear.to_t, from_color.black.opacity(50), to_color.transparent
                    )),
                    cls=combine_classes(
                        aspect.video, 
                        overflow.hidden, 
                        bg.gray._200,
                        position.relative
                    )
                ),
                
                # Card content
                Div(
                    H3("Smart Watch Pro", cls=combine_classes(font_size.lg, font_weight.semibold)),
                    P("$349.99", cls=combine_classes(font_size.xl, font_weight.bold, text_color.blue._600, m.b(2))),
                    P("Advanced fitness tracking with GPS and heart rate monitor", 
                      cls=combine_classes(text_color.gray._600, font_size.sm, m.b(4))),
                    
                    # Features list
                    Ul(
                        Li("✓ Water resistant", cls=combine_classes(font_size.sm, text_color.gray._600)),
                        Li("✓ 7-day battery life", cls=combine_classes(font_size.sm, text_color.gray._600)),
                        Li("✓ Multiple sport modes", cls=combine_classes(font_size.sm, text_color.gray._600)),
                        cls=space.y(1)
                    ),
                    
                    # CTA button
                    Button("View Details", cls=combine_classes(
                        w.full,
                        pad(x=4, y=3),
                        m.t(4),
                        bg_linear.to_r, from_color.purple._600, to_color.blue._600,
                        text_color.white, rounded(),
                        from_color.purple._700.hover, to_color.blue._700.hover, transition.all
                    )),
                    
                    cls=p(6)
                ),
                
                cls=combine_classes(
                    bg.white, rounded.lg, shadow.lg,
                    overflow.hidden,
                    position.relative,
                    shadow._2xl.hover, transition.shadow, shadow_color.amber._400.hover, 
                    ease._in, duration._500,
                    flex_display,
                    flex_direction.col
                )
            ),
            
            # Product Card 3 - Sale item
            Article(
                # Sale badge
                Div(
                    Span("-30%", cls=combine_classes(p.x(3), p.y(1), bg.red._500, text_color.white, font_size.sm, font_weight.bold, rounded.full)),
                    cls=combine_classes(position.absolute, top(4), left(4), z(10))
                ),
                
                Div(
                    Img(src="https://via.placeholder.com/400x300", 
                        alt="Product 3",
                        cls=combine_classes(w.full, h.full, object_fit.cover)),
                    cls=combine_classes(aspect.video, overflow.hidden, bg.gray._200, position.relative)
                ),
                
                Div(
                    H3("Laptop Backpack", cls=combine_classes(font_size.lg, font_weight.semibold)),
                    Div(
                        P("$69.99", cls=combine_classes(font_size.xl, font_weight.bold, text_color.red._600)),
                        P("$99.99", cls=combine_classes(font_size.sm, text_color.gray._400, line_through)),
                        cls=combine_classes(flex_display, items.baseline, gap(2), m.b(2))
                    ),
                    P("Durable backpack with laptop compartment and USB charging port", 
                      cls=combine_classes(text_color.gray._600, font_size.sm, m.b(4))),
                    
                    # Stock indicator
                    Div(
                        Div(cls=combine_classes(size(2), bg.green._500, rounded.full)),
                        Span("In Stock", cls=combine_classes(font_size.sm, text_color.gray._600)),
                        cls=combine_classes(flex_display, items.center, gap(2), m.b(4))
                    ),
                    
                    Button("Quick Buy", cls=combine_classes(
                        w.full,
                        pad(x=4, y=2),
                        bg.red._600, text_color.white, rounded(),
                        bg.red._700.hover, transition.colors
                    )),
                    
                    cls=p(6)
                ),
                
                cls=combine_classes(
                    bg.white, rounded.lg, shadow.md,
                    overflow.hidden,
                    position.relative,
                    shadow.xl.hover, transition.shadow,
                    flex_display,
                    flex_direction.col
                )
            ),
            
            # More product cards...
            *[
                Article(
                    Div(
                        Img(src="https://via.placeholder.com/400x300", 
                            alt=f"Product {i}",
                            cls=combine_classes(w.full, h.full, object_fit.cover)),
                        cls=combine_classes(aspect.video, overflow.hidden, bg.gray._200)
                    ),
                    Div(
                        H3(f"Product {i}", cls=combine_classes(font_size.lg, font_weight.semibold)),
                        P(f"${49.99 + i * 10}", cls=combine_classes(font_size.xl, font_weight.bold, text_color.blue._600, m.b(2))),
                        P("Lorem ipsum dolor sit amet, consectetur adipiscing elit.", 
                          cls=combine_classes(text_color.gray._600, font_size.sm, m.b(4))),
                        Button("Add to Cart", cls=combine_classes(
                            w.full,
                            pad(x=4, y=2),
                            bg.blue._600, text_color.white, rounded(),
                            bg.blue._700.hover, transition.colors
                        )),
                        cls=p(6)
                    ),
                    cls=combine_classes(
                        bg.white, rounded.lg, shadow.md,
                        overflow.hidden,
                        shadow.xl.hover, transition.shadow,
                        flex_display,
                        flex_direction.col
                    )
                ) for i in range(4, 7)
            ],
            
            cls=responsive_grid(
                mobile=1,
                tablet=2,
                desktop=3,
                gap_size=8
            )
        ),
        cls=combine_classes(
            container(),
            m.x.auto,
            pad(x=4, y=12)
        )
    )
    
    # Newsletter section
    newsletter = Section(
        Div(
            # Content wrapper
            Div(
                H2("Stay Updated", cls=combine_classes(font_size._2xl, font_weight.bold, text_color.white, m.b(2))),
                P("Get the latest products and exclusive offers", cls=combine_classes(text_color.gray._200, m.b(6))),
                
                # Form
                Form(
                    Div(
                        Input(
                            type="email",
                            placeholder="Enter your email",
                            cls=combine_classes(
                                flex(1),
                                p(3),
                                bg.white, rounded.l.lg,
                                outline_style.none.focus, ring(2).focus, ring_color.blue._400.focus
                            )
                        ),
                        Button(
                            "Subscribe",
                            type="submit",
                            cls=combine_classes(
                                p.x(8), p(3),
                                bg.yellow._400, text_color.gray._900, font_weight.semibold, rounded.r.lg,
                                bg.yellow._300.hover, transition.colors
                            )
                        ),
                        cls=combine_classes(flex_display, max_w.md, m.x.auto)
                    ),
                    cls=str(w.full)
                ),
                
                cls=combine_classes(
                    max_w._2xl,
                    m.x.auto,
                    text_align.center
                )
            ),
            cls=combine_classes(
                container(),
                m.x.auto,
                pad(x=4, y=16)
            )
        ),
        cls=combine_classes(bg_linear.to_r, from_color.blue._600, to_color.purple._600)
    )
    
    # Footer
    footer = Footer(
        Div(
            P("© 2024 Product Showcase. All rights reserved.", 
              cls=combine_classes(text_color.gray._600, text_align.center)),
            cls=combine_classes(
                container(),
                m.x.auto,
                pad(x=4, y=8)
            )
        ),
        cls=combine_classes(bg.gray._100, border.t())
    )
    
    return Div(
        header,
        filter_section,
        products_grid,
        newsletter,
        footer,
        cls=combine_classes(min_h.screen, bg.gray._50)
    )

# Start the server
server2 = start_test_server(app2, port=8002)
HTMX(port=8002)
```

<script>
document.body.addEventListener('htmx:configRequest', (event) => {
    if(event.detail.path.includes('://')) return;
    htmx.config.selfRequestsOnly=false;
    event.detail.path = `${location.protocol}//${location.hostname}:8002${event.detail.path}`;
});
</script>

<iframe src="http://localhost:8002/" style="width: 100%; height: auto; border: none;" onload="{
        let frame = this;
        window.addEventListener('message', function(e) {
            if (e.source !== frame.contentWindow) return; // Only proceed if the message is from this iframe
            if (e.data.height) frame.style.height = (e.data.height+1) + 'px';
        }, false);
    }" allow="accelerometer; autoplay; camera; clipboard-read; clipboard-write; display-capture; encrypted-media; fullscreen; gamepad; geolocation; gyroscope; hid; identity-credentials-get; idle-detection; magnetometer; microphone; midi; payment; picture-in-picture; publickey-credentials-get; screen-wake-lock; serial; usb; web-share; xr-spatial-tracking"></iframe> 

``` python
# Stop the server when done
server2.stop()
```

## Export
