# cjm-fasthtml-card-stack


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

## Install

``` bash
pip install cjm_fasthtml_card_stack
```

## Project Structure

    nbs/
    ├── components/ (4)
    │   ├── controls.ipynb  # Width slider, scale slider, and card count selector components.
    │   ├── progress.ipynb  # Progress indicator showing the current position within the card stack.
    │   ├── states.ipynb    # Loading, empty, and placeholder card components for the card stack viewport.
    │   └── viewport.ipynb  # Card stack viewport with 3-section CSS Grid layout, slot rendering,
    ├── core/ (5)
    │   ├── button_ids.ipynb  # Prefix-based IDs for hidden keyboard action buttons.
    │   ├── config.ipynb      # Configuration dataclasses for card stack initialization and visual styling.
    │   ├── constants.ipynb   # CSS class constants, type aliases, and default values for the card stack library.
    │   ├── html_ids.ipynb    # Prefix-based HTML ID generator for card stack DOM elements.
    │   └── models.ipynb      # Core dataclasses for card stack state, render context, and URL routing.
    ├── helpers/ (1)
    │   └── focus.ipynb  # Focus position resolution, viewport window calculation, and OOB focus sync.
    ├── js/ (7)
    │   ├── auto_adjust.ipynb  # JavaScript generator for automatic visible card count adjustment.
    │   ├── controls.ipynb     # JavaScript generators for width, scale, and card count management.
    │   ├── core.ipynb         # Master composer for card stack JavaScript. Combines viewport height,
    │   ├── navigation.ipynb   # JavaScript generator for page jump and first/last navigation helpers.
    │   ├── scroll.ipynb       # JavaScript generator for scroll-to-nav conversion.
    │   ├── touch.ipynb        # JavaScript generator for touch-to-nav conversion: swipe, drag,
    │   └── viewport.ipynb     # JavaScript generator for dynamic viewport height calculation.
    ├── keyboard/ (1)
    │   └── actions.ipynb  # Keyboard navigation focus zone and action factories for the card stack.
    └── routes/ (2)
        ├── handlers.ipynb  # Response builder functions for card stack operations (Tier 1 API).
        └── router.ipynb    # Convenience router factory that wires up standard card stack routes (Tier 2 API).

Total: 20 notebooks across 6 directories

## Module Dependencies

``` mermaid
graph LR
    components_controls[components.controls<br/>Controls]
    components_progress[components.progress<br/>Progress]
    components_states[components.states<br/>States]
    components_viewport[components.viewport<br/>Viewport]
    core_button_ids[core.button_ids<br/>Button IDs]
    core_config[core.config<br/>Config]
    core_constants[core.constants<br/>Constants]
    core_html_ids[core.html_ids<br/>HTML IDs]
    core_models[core.models<br/>Models]
    helpers_focus[helpers.focus<br/>Focus]
    js_auto_adjust[js.auto_adjust<br/>JS: Auto Adjust]
    js_controls[js.controls<br/>JS: Controls]
    js_core[js.core<br/>JS: Core]
    js_navigation[js.navigation<br/>JS: Page Navigation]
    js_scroll[js.scroll<br/>JS: Scroll Navigation]
    js_touch[js.touch<br/>JS: Touch Navigation]
    js_viewport[js.viewport<br/>JS: Viewport Height]
    keyboard_actions[keyboard.actions<br/>Actions]
    routes_handlers[routes.handlers<br/>Handlers]
    routes_router[routes.router<br/>Router]

    components_controls --> core_config
    components_controls --> core_html_ids
    components_progress --> core_html_ids
    components_states --> core_html_ids
    components_viewport --> helpers_focus
    components_viewport --> core_models
    components_viewport --> components_states
    components_viewport --> core_config
    components_viewport --> core_html_ids
    components_viewport --> core_constants
    helpers_focus --> core_html_ids
    js_auto_adjust --> core_constants
    js_auto_adjust --> core_models
    js_auto_adjust --> core_html_ids
    js_auto_adjust --> core_config
    js_controls --> core_constants
    js_controls --> core_models
    js_controls --> core_html_ids
    js_controls --> core_config
    js_core --> core_constants
    js_core --> js_controls
    js_core --> js_navigation
    js_core --> core_models
    js_core --> core_config
    js_core --> js_viewport
    js_core --> js_scroll
    js_core --> core_html_ids
    js_core --> core_button_ids
    js_core --> js_touch
    js_core --> js_auto_adjust
    js_navigation --> core_button_ids
    js_scroll --> core_html_ids
    js_scroll --> core_constants
    js_scroll --> core_button_ids
    js_touch --> core_constants
    js_touch --> core_html_ids
    js_touch --> core_button_ids
    js_viewport --> core_html_ids
    keyboard_actions --> core_config
    keyboard_actions --> core_models
    keyboard_actions --> core_html_ids
    keyboard_actions --> core_button_ids
    keyboard_actions --> js_core
    routes_handlers --> helpers_focus
    routes_handlers --> components_viewport
    routes_handlers --> core_models
    routes_handlers --> components_progress
    routes_handlers --> core_config
    routes_handlers --> core_html_ids
    routes_router --> routes_handlers
    routes_router --> core_models
    routes_router --> core_config
    routes_router --> core_html_ids
```

*53 cross-module dependencies detected*

## CLI Reference

No CLI commands found in this project.

## Module Overview

Detailed documentation for each module in the project:

### Actions (`actions.ipynb`)

> Keyboard navigation focus zone and action factories for the card
> stack.

#### Import

``` python
from cjm_fasthtml_card_stack.keyboard.actions import (
    create_card_stack_focus_zone,
    create_card_stack_nav_actions,
    build_card_stack_url_map,
    render_card_stack_action_buttons
)
```

#### Functions

``` python
def create_card_stack_focus_zone(
    ids: CardStackHtmlIds,  # HTML IDs for this card stack instance
    on_focus_change: Optional[str] = None,  # JS callback name on focus change
    hidden_input_prefix: Optional[str] = None,  # Prefix for keyboard nav hidden inputs
    data_attributes: Tuple[str, ...] = (),  # Data attributes to track on focused items
) -> FocusZone:  # Configured focus zone for the card stack
    "Create a focus zone for a card stack viewport."
```

``` python
def create_card_stack_nav_actions(
    zone_id: str,  # Focus zone ID to restrict actions to
    button_ids: CardStackButtonIds,  # Button IDs for HTMX triggers
    config: CardStackConfig,  # Config (for prefix-unique callback names)
    disable_in_modes: Tuple[str, ...] = (),  # Mode names that disable navigation
) -> Tuple[KeyAction, ...]:  # Standard card stack navigation actions
    "Create standard keyboard navigation actions for a card stack."
```

``` python
def build_card_stack_url_map(
    button_ids: CardStackButtonIds,  # Button IDs for this card stack instance
    urls: CardStackUrls,  # URL bundle for routing
) -> Dict[str, str]:  # Mapping of button ID -> route URL
    """
    Build url_map for render_keyboard_system with all card stack navigation buttons.
    
    Returns a dict mapping button IDs to URLs for all navigation actions:
    nav_up, nav_down, nav_first, nav_last, nav_page_up, nav_page_down.
    
    Merge with consumer's own action URLs when building the keyboard system:
        url_map = {**build_card_stack_url_map(btn_ids, urls), **my_action_urls}
    """
```

``` python
def render_card_stack_action_buttons(
    button_ids: CardStackButtonIds,  # Button IDs for this card stack instance
    urls: CardStackUrls,  # URL bundle for routing
    ids: CardStackHtmlIds,  # HTML IDs (for hx-include of focused_index_input)
) -> 'FT':  # Div containing hidden action buttons
    """
    Render hidden HTMX buttons for JS-callback-triggered navigation actions.
    
    Creates buttons for: page_up, page_down, first, last.
    These are clicked programmatically by the card stack's JS functions.
    Must be included in the DOM alongside the keyboard system's own buttons.
    """
```

### JS: Auto Adjust (`auto_adjust.ipynb`)

> JavaScript generator for automatic visible card count adjustment.

#### Import

``` python
from cjm_fasthtml_card_stack.js.auto_adjust import *
```

#### Functions

``` python
def _generate_auto_adjust_js(
    ids: CardStackHtmlIds,  # HTML IDs for this instance
    config: CardStackConfig,  # Config for auto mode check
    urls: CardStackUrls,  # URL bundle (update_viewport)
    focus_position: Optional[int] = None,  # Focus slot offset (None=center, -1=bottom, 0=top)
) -> str:  # JS code fragment for auto visible count adjustment
    "Generate JS for automatic visible count adjustment based on overflow detection."
```

### Button IDs (`button_ids.ipynb`)

> Prefix-based IDs for hidden keyboard action buttons.

#### Import

``` python
from cjm_fasthtml_card_stack.core.button_ids import (
    CardStackButtonIds
)
```

#### Classes

``` python
@dataclass
class CardStackButtonIds:
    "Prefix-based IDs for hidden keyboard action buttons."
    
    prefix: str  # ID prefix for this card stack instance
    
    def nav_up(self) -> str:  # Navigate to previous item
            """Navigate up button."""
            return f"{self.prefix}-btn-nav-up"
    
        @property
        def nav_down(self) -> str:  # Navigate to next item
        "Navigate up button."
    
    def nav_down(self) -> str:  # Navigate to next item
            """Navigate down button."""
            return f"{self.prefix}-btn-nav-down"
    
        @property
        def nav_first(self) -> str:  # Navigate to first item
        "Navigate down button."
    
    def nav_first(self) -> str:  # Navigate to first item
            """Navigate to first item button."""
            return f"{self.prefix}-btn-nav-first"
    
        @property
        def nav_last(self) -> str:  # Navigate to last item
        "Navigate to first item button."
    
    def nav_last(self) -> str:  # Navigate to last item
            """Navigate to last item button."""
            return f"{self.prefix}-btn-nav-last"
    
        @property
        def nav_page_up(self) -> str:  # Page jump up
        "Navigate to last item button."
    
    def nav_page_up(self) -> str:  # Page jump up
            """Page up button."""
            return f"{self.prefix}-btn-nav-page-up"
    
        @property
        def nav_page_down(self) -> str:  # Page jump down
        "Page up button."
    
    def nav_page_down(self) -> str:  # Page jump down
            """Page down button."""
            return f"{self.prefix}-btn-nav-page-down"
    
        # --- Viewport control buttons ---
    
        @property
        def width_narrow(self) -> str:  # Decrease viewport width
        "Page down button."
    
    def width_narrow(self) -> str:  # Decrease viewport width
            """Narrow viewport button."""
            return f"{self.prefix}-btn-width-narrow"
    
        @property
        def width_widen(self) -> str:  # Increase viewport width
        "Narrow viewport button."
    
    def width_widen(self) -> str:  # Increase viewport width
            """Widen viewport button."""
            return f"{self.prefix}-btn-width-widen"
    
        @property
        def scale_decrease(self) -> str:  # Decrease content scale
        "Widen viewport button."
    
    def scale_decrease(self) -> str:  # Decrease content scale
            """Decrease scale button."""
            return f"{self.prefix}-btn-scale-decrease"
    
        @property
        def scale_increase(self) -> str:  # Increase content scale
        "Decrease scale button."
    
    def scale_increase(self) -> str:  # Increase content scale
        "Increase scale button."
```

### Config (`config.ipynb`)

> Configuration dataclasses for card stack initialization and visual
> styling.

#### Import

``` python
from cjm_fasthtml_card_stack.core.config import (
    CardStackStyleConfig,
    CardStackConfig
)
```

#### Functions

``` python
def _auto_prefix() -> str:  # Unique prefix string (e.g., "cs0", "cs1")
    """Generate an auto-incrementing unique prefix."""
    global _prefix_counter
    p = f"cs{_prefix_counter}"
    _prefix_counter += 1
    return p

def _reset_prefix_counter() -> None
    "Generate an auto-incrementing unique prefix."
```

``` python
def _reset_prefix_counter() -> None
    "Reset the prefix counter (for testing only)."
```

#### Classes

``` python
@dataclass
class CardStackStyleConfig:
    "Visual styling for a card stack instance."
    
    section_gap: str = '1rem'  # Gap between cards in each section
    slot_padding: str = '0.25rem'  # Padding around context card content
    viewport_padding_x: str = '0.5rem'  # Horizontal outer container padding
    viewport_padding_y: str = '0.5rem'  # Vertical outer container padding
    focus_padding_x: str = '0.5rem'  # Horizontal focused section padding
    focus_padding_b: str = '1rem'  # Bottom focused section padding
    focus_shadow: str = _DEFAULT_FOCUS_SHADOW  # Shadow classes for focused card
    focus_border_radius: str = _DEFAULT_FOCUS_BORDER_RADIUS  # Border radius class for focused card
    
    def css_vars_style(
            self,
            prefix: str,  # Card stack instance prefix
        ) -> str:  # Inline style string with CSS custom property declarations
        "Generate CSS custom property declarations as an inline style string."
```

``` python
@dataclass
class CardStackConfig:
    "Initialization-time settings for a card stack instance."
    
    prefix: str = field(...)  # HTML ID prefix (auto-generated if omitted)
    visible_count_options: Tuple[int, ...] = (1, 3, 5, 7, 9)  # Choices for card count dropdown
    auto_visible_count: bool = True  # Whether "Auto" option appears in card count dropdown
    card_width_min: int = 30  # Width slider minimum (rem)
    card_width_max: int = 120  # Width slider maximum (rem)
    card_width_step: int = 5  # Width slider step (rem)
    card_scale_min: int = 50  # Scale slider minimum (%)
    card_scale_max: int = 200  # Scale slider maximum (%)
    card_scale_step: int = 10  # Scale slider step (%)
    click_to_focus: bool = False  # Whether context cards get transparent click overlay
    disable_scroll_in_modes: Tuple[str, ...] = ()  # Mode names where scroll-to-nav is suppressed
    style: CardStackStyleConfig = field(...)  # Visual styling config
```

#### Variables

``` python
_prefix_counter: int = 0
_DEFAULT_FOCUS_SHADOW: str
_DEFAULT_FOCUS_BORDER_RADIUS: str
```

### Constants (`constants.ipynb`)

> CSS class constants, type aliases, and default values for the card
> stack library.

#### Import

``` python
from cjm_fasthtml_card_stack.core.constants import (
    CardRole,
    SCROLL_THRESHOLD,
    NAVIGATION_COOLDOWN,
    TRACKPAD_COOLDOWN,
    TOUCH_SWIPE_THRESHOLD,
    TOUCH_MOMENTUM_MIN_VELOCITY,
    TOUCH_MOMENTUM_FRICTION,
    TOUCH_PINCH_THRESHOLD,
    TOUCH_VELOCITY_SAMPLES,
    DEFAULT_VISIBLE_COUNT,
    DEFAULT_CARD_WIDTH,
    DEFAULT_CARD_SCALE,
    width_storage_key,
    scale_storage_key,
    card_count_storage_key,
    auto_count_storage_key
)
```

#### Functions

``` python
def width_storage_key(
    prefix: str  # Card stack instance prefix
) -> str:  # localStorage key for card width
    "Generate localStorage key for card width."
```

``` python
def scale_storage_key(
    prefix: str  # Card stack instance prefix
) -> str:  # localStorage key for card scale
    "Generate localStorage key for card scale."
```

``` python
def card_count_storage_key(
    prefix: str  # Card stack instance prefix
) -> str:  # localStorage key for card count
    "Generate localStorage key for card count."
```

``` python
def auto_count_storage_key(
    prefix: str  # Card stack instance prefix
) -> str:  # localStorage key for auto card count mode
    "Generate localStorage key for auto card count mode."
```

#### Variables

``` python
SCROLL_THRESHOLD: int = 1
NAVIGATION_COOLDOWN: int = 100
TRACKPAD_COOLDOWN: int = 250
TOUCH_SWIPE_THRESHOLD: int = 30
TOUCH_MOMENTUM_MIN_VELOCITY: float = 0.5
TOUCH_MOMENTUM_FRICTION: float = 0.95
TOUCH_PINCH_THRESHOLD: int = 30
TOUCH_VELOCITY_SAMPLES: int = 5
DEFAULT_VISIBLE_COUNT: int = 3
DEFAULT_CARD_WIDTH: int = 80
DEFAULT_CARD_SCALE: int = 100
```

### Controls (`controls.ipynb`)

> Width slider, scale slider, and card count selector components.

#### Import

``` python
from cjm_fasthtml_card_stack.components.controls import (
    render_width_slider,
    render_scale_slider,
    render_card_count_select
)
```

#### Functions

``` python
def render_width_slider(
    config: CardStackConfig,  # Card stack configuration
    ids: CardStackHtmlIds,  # HTML IDs for this instance
    card_width: int = 80,  # Current card width in rem
) -> Any:  # Width slider component
    "Render the card stack width slider control."
```

``` python
def render_scale_slider(
    config: CardStackConfig,  # Card stack configuration
    ids: CardStackHtmlIds,  # HTML IDs for this instance
    card_scale: int = 100,  # Current scale percentage
) -> Any:  # Scale slider component
    "Render the card stack scale slider control."
```

``` python
def render_card_count_select(
    config: CardStackConfig,  # Card stack configuration
    ids: CardStackHtmlIds,  # HTML IDs for this instance
    current_count: int = 3,  # Currently selected card count
) -> Any:  # Card count dropdown component
    "Render the card count dropdown selector."
```

### JS: Controls (`controls.ipynb`)

> JavaScript generators for width, scale, and card count management.

#### Import

``` python
from cjm_fasthtml_card_stack.js.controls import *
```

#### Functions

``` python
def _generate_width_mgmt_js(
    ids: CardStackHtmlIds,  # HTML IDs for this instance
    config: CardStackConfig,  # Config with slider bounds
    urls: CardStackUrls,  # URL bundle (save_width)
) -> str:  # JS code fragment for width management
    "Generate JS for width slider management."
```

``` python
def _generate_scale_mgmt_js(
    ids: CardStackHtmlIds,  # HTML IDs for this instance
    config: CardStackConfig,  # Config with slider bounds
    urls: CardStackUrls,  # URL bundle (save_scale)
) -> str:  # JS code fragment for scale management
    "Generate JS for scale slider management."
```

``` python
def _generate_card_count_mgmt_js(
    ids: CardStackHtmlIds,  # HTML IDs for this instance
    config: CardStackConfig,  # Config with count options
    urls: CardStackUrls,  # URL bundle (update_viewport)
) -> str:  # JS code fragment for card count management
    "Generate JS for card count selector management."
```

### JS: Core (`core.ipynb`)

> Master composer for card stack JavaScript. Combines viewport height,

#### Import

``` python
from cjm_fasthtml_card_stack.js.core import (
    global_callback_name,
    generate_card_stack_js
)
```

#### Functions

``` python
def _generate_coordinator_js(
    ids: CardStackHtmlIds,  # HTML IDs for this instance
    config: CardStackConfig,  # Config for prefix-unique listener guards
    focus_position: Optional[int] = None,  # Focus slot offset (None=center, -1=bottom, 0=top)
) -> str:  # JS code fragment for master coordinator
    "Generate JS for the master coordinator and HTMX listener."
```

``` python
def global_callback_name(
    prefix: str,  # Card stack instance prefix
    callback: str,  # Base callback name (e.g., "jumpPageUp")
) -> str:  # Global function name (e.g., "cs0_jumpPageUp")
    "Generate a prefix-unique global callback name for keyboard navigation."
```

``` python
def _generate_global_callbacks_js(
    config: CardStackConfig,  # Config with prefix
) -> str:  # JS code fragment registering global wrappers
    "Register global wrappers for keyboard navigation system."
```

``` python
def generate_card_stack_js(
    "Compose all card stack JS into a single namespaced IIFE."
```

#### Variables

``` python
_GLOBAL_CALLBACKS
```

### Focus (`focus.ipynb`)

> Focus position resolution, viewport window calculation, and OOB focus
> sync.

#### Import

``` python
from cjm_fasthtml_card_stack.helpers.focus import (
    resolve_focus_slot,
    calculate_viewport_window,
    render_focus_oob
)
```

#### Functions

``` python
def resolve_focus_slot(
    focus_position: Optional[int],  # Slot offset (None=center, -1=bottom, 0=top)
    visible_count: int,  # Number of visible card slots
) -> int:  # Resolved 0-indexed slot position
    "Resolve focus_position to an actual slot index within the viewport."
```

``` python
def calculate_viewport_window(
    focused_index: int,  # Index of the focused item
    total_items: int,  # Total number of items
    visible_count: int,  # Number of visible card slots
    focus_position: Optional[int] = None,  # Focus slot (None=center)
) -> List[int]:  # Item indices for each slot (negative or >= total_items for placeholders)
    "Calculate which item indices should be visible in each viewport slot."
```

``` python
def render_focus_oob(
    focused_index: int,  # The item index to focus
    ids: CardStackHtmlIds,  # HTML IDs for this card stack instance
    form_input_name: str = "focused_index",  # Field name for the form input
) -> Tuple[Hidden, ...]:  # Hidden inputs with OOB swap
    "Render OOB hidden inputs to synchronize focus after HTMX swap."
```

### Handlers (`handlers.ipynb`)

> Response builder functions for card stack operations (Tier 1 API).

#### Import

``` python
from cjm_fasthtml_card_stack.routes.handlers import (
    build_slots_response,
    build_nav_response,
    card_stack_navigate,
    card_stack_navigate_to_index,
    card_stack_update_viewport,
    card_stack_save_width,
    card_stack_save_scale
)
```

#### Functions

``` python
def build_slots_response(
    card_items: List[Any],  # All data items
    state: CardStackState,  # Current card stack state
    config: CardStackConfig,  # Card stack configuration
    ids: CardStackHtmlIds,  # HTML IDs for this instance
    urls: CardStackUrls,  # URL bundle for navigation
    render_card: Callable,  # Card renderer callback
) -> List[Any]:  # OOB slot elements (3 viewport sections)
    "Build OOB slot updates for the viewport sections only."
```

``` python
def build_nav_response(
    card_items: List[Any],  # All data items
    state: CardStackState,  # Current card stack state
    config: CardStackConfig,  # Card stack configuration
    ids: CardStackHtmlIds,  # HTML IDs for this instance
    urls: CardStackUrls,  # URL bundle for navigation
    render_card: Callable,  # Card renderer callback
    progress_label: str = "Item",  # Label for progress indicator
) -> Tuple:  # OOB elements (slots + progress + focus)
    "Build full OOB response for navigation: slots + progress + focus inputs."
```

``` python
def card_stack_navigate(
    direction: str,  # "up", "down", "first", "last", "page_up", "page_down"
    card_items: List[Any],  # All data items
    state: CardStackState,  # Current card stack state (mutated in place)
    config: CardStackConfig,  # Card stack configuration
    ids: CardStackHtmlIds,  # HTML IDs for this instance
    urls: CardStackUrls,  # URL bundle for navigation
    render_card: Callable,  # Card renderer callback
    progress_label: str = "Item",  # Label for progress indicator
) -> Tuple:  # OOB elements (slots + progress + focus)
    "Navigate to a different item. Mutates state.focused_index in place."
```

``` python
def card_stack_navigate_to_index(
    target_index: int,  # Target item index to navigate to
    card_items: List[Any],  # All data items
    state: CardStackState,  # Current card stack state (mutated in place)
    config: CardStackConfig,  # Card stack configuration
    ids: CardStackHtmlIds,  # HTML IDs for this instance
    urls: CardStackUrls,  # URL bundle for navigation
    render_card: Callable,  # Card renderer callback
    progress_label: str = "Item",  # Label for progress indicator
) -> Tuple:  # OOB elements (slots + progress + focus)
    "Navigate to a specific item index. Mutates state.focused_index in place."
```

``` python
def card_stack_update_viewport(
    visible_count: int,  # New number of visible cards
    card_items: List[Any],  # All data items
    state: CardStackState,  # Current card stack state (mutated in place)
    config: CardStackConfig,  # Card stack configuration
    ids: CardStackHtmlIds,  # HTML IDs for this instance
    urls: CardStackUrls,  # URL bundle for navigation
    render_card: Callable,  # Card renderer callback
) -> Tuple:  # OOB section elements (3 viewport sections)
    "Update viewport with new card count via OOB section swaps. Mutates state.visible_count in place."
```

``` python
def card_stack_save_width(
    state: CardStackState,  # Current card stack state (mutated in place)
    card_width: int,  # Card stack width in rem
    config: CardStackConfig,  # Card stack configuration (for clamping bounds)
) -> None:  # No response (swap=none on client)
    "Save card stack width. Mutates state.card_width in place."
```

``` python
def card_stack_save_scale(
    state: CardStackState,  # Current card stack state (mutated in place)
    card_scale: int,  # Card stack scale percentage
    config: CardStackConfig,  # Card stack configuration (for clamping bounds)
) -> None:  # No response (swap=none on client)
    "Save card stack scale. Mutates state.card_scale in place."
```

### HTML IDs (`html_ids.ipynb`)

> Prefix-based HTML ID generator for card stack DOM elements.

#### Import

``` python
from cjm_fasthtml_card_stack.core.html_ids import (
    CardStackHtmlIds
)
```

#### Classes

``` python
@dataclass
class CardStackHtmlIds:
    "Prefix-based HTML ID generator for card stack DOM elements."
    
    prefix: str  # ID prefix for this card stack instance
    
    def card_stack(self) -> str:  # Full-width scroll capture container
            """Outer card stack container."""
            return f"{self.prefix}-card-stack"
    
        @property
        def card_stack_inner(self) -> str:  # Width-constrained CSS Grid container
        "Outer card stack container."
    
    def card_stack_inner(self) -> str:  # Width-constrained CSS Grid container
            """Inner grid container for 3-section layout."""
            return f"{self.prefix}-card-stack-inner"
    
        @property
        def card_stack_empty(self) -> str:  # Empty state placeholder
        "Inner grid container for 3-section layout."
    
    def card_stack_empty(self) -> str:  # Empty state placeholder
            """Empty state container."""
            return f"{self.prefix}-card-stack-empty"
    
        # --- Viewport sections ---
    
        @property
        def viewport_section_before(self) -> str:  # Cards before focused (1fr, justify-end)
        "Empty state container."
    
    def viewport_section_before(self) -> str:  # Cards before focused (1fr, justify-end)
            """Viewport section for context cards before focused card."""
            return f"{self.prefix}-viewport-section-before"
    
        @property
        def viewport_section_focused(self) -> str:  # Focused card (auto)
        "Viewport section for context cards before focused card."
    
    def viewport_section_focused(self) -> str:  # Focused card (auto)
            """Viewport section for the focused card."""
            return f"{self.prefix}-viewport-section-focused"
    
        @property
        def viewport_section_after(self) -> str:  # Cards after focused (1fr, justify-start)
        "Viewport section for the focused card."
    
    def viewport_section_after(self) -> str:  # Cards after focused (1fr, justify-start)
            """Viewport section for context cards after focused card."""
            return f"{self.prefix}-viewport-section-after"
    
        # --- Dynamic slot IDs ---
    
        def viewport_slot(
            self,
            item_index: int  # Item index (negative or >= total for placeholders)
        ) -> str:  # Slot element ID tied to virtual item position
        "Viewport section for context cards after focused card."
    
    def viewport_slot(
            self,
            item_index: int  # Item index (negative or >= total for placeholders)
        ) -> str:  # Slot element ID tied to virtual item position
        "ID for a viewport slot. Works for real items and placeholders."
    
    def card_count_select(self) -> str:  # Card count dropdown
            """Card count selector dropdown."""
            return f"{self.prefix}-card-count-select"
    
        @property
        def width_slider(self) -> str:  # Width range slider
        "Card count selector dropdown."
    
    def width_slider(self) -> str:  # Width range slider
            """Card stack width slider."""
            return f"{self.prefix}-width-slider"
    
        @property
        def scale_slider(self) -> str:  # Scale range slider
        "Card stack width slider."
    
    def scale_slider(self) -> str:  # Scale range slider
            """Card stack scale slider."""
            return f"{self.prefix}-scale-slider"
    
        # --- Status elements ---
    
        @property
        def progress(self) -> str:  # Progress indicator
        "Card stack scale slider."
    
    def progress(self) -> str:  # Progress indicator
            """Progress indicator element."""
            return f"{self.prefix}-progress"
    
        @property
        def loading(self) -> str:  # Loading state container
        "Progress indicator element."
    
    def loading(self) -> str:  # Loading state container
            """Loading state container."""
            return f"{self.prefix}-loading"
    
        # --- Hidden inputs ---
    
        @property
        def focused_index_input(self) -> str:  # Hidden input for keyboard nav focus recovery
        "Loading state container."
    
    def focused_index_input(self) -> str:  # Hidden input for keyboard nav focus recovery
        "Hidden input storing the focused index for HTMX submissions."
```

### Models (`models.ipynb`)

> Core dataclasses for card stack state, render context, and URL
> routing.

#### Import

``` python
from cjm_fasthtml_card_stack.core.models import (
    CardStackState,
    CardRenderContext,
    CardStackUrls
)
```

#### Classes

``` python
@dataclass
class CardStackState:
    "Viewport state for a card stack instance."
    
    focused_index: int = 0  # Index of focused item in the items list
    visible_count: int = 3  # Number of card slots visible in viewport
    card_width: int = 80  # Max width of card stack inner container in rem
    card_scale: int = 100  # Content scale percentage (50-200)
    active_mode: Optional[str]  # Current interaction mode name (consumer-defined)
    focus_position: Optional[int]  # Slot offset for focused card (None=center, -1=bottom)
```

``` python
@dataclass
class CardRenderContext:
    "Context passed to the consumer's render_card callback."
    
    card_role: str  # "focused" or "context"
    index: int  # Item's position in the full items list
    total_items: int  # Total item count
    is_first: bool  # Whether this is the first item
    is_last: bool  # Whether this is the last item
    active_mode: Optional[str]  # Current interaction mode
    card_scale: int  # Scale percentage (50-200)
    distance_from_focus: int  # Signed slot offset from focused card (0=focused)
```

``` python
@dataclass
class CardStackUrls:
    "URL bundle for card stack navigation and viewport operations."
    
    nav_up: str = ''  # Navigate to previous item
    nav_down: str = ''  # Navigate to next item
    nav_first: str = ''  # Navigate to first item
    nav_last: str = ''  # Navigate to last item
    nav_page_up: str = ''  # Page jump up
    nav_page_down: str = ''  # Page jump down
    nav_to_index: str = ''  # Navigate to specific index (click-to-focus)
    update_viewport: str = ''  # Change visible_count (full viewport re-render)
    save_width: str = ''  # Persist card_width
    save_scale: str = ''  # Persist card_scale
```

### JS: Page Navigation (`navigation.ipynb`)

> JavaScript generator for page jump and first/last navigation helpers.

#### Import

``` python
from cjm_fasthtml_card_stack.js.navigation import (
    generate_page_nav_js
)
```

#### Functions

``` python
def generate_page_nav_js(
    button_ids: CardStackButtonIds,  # Button IDs for navigation triggers
) -> str:  # JavaScript code fragment for page navigation
    "Generate JS for page-based and first/last navigation functions."
```

### Progress (`progress.ipynb`)

> Progress indicator showing the current position within the card stack.

#### Import

``` python
from cjm_fasthtml_card_stack.components.progress import (
    render_progress_indicator
)
```

#### Functions

``` python
def render_progress_indicator(
    focused_index: int,  # Currently focused item index (0-based)
    total_items: int,  # Total number of items
    ids: CardStackHtmlIds,  # HTML IDs for this card stack instance
    label: str = "Item",  # Label prefix (e.g., "Item", "Segment", "Card")
    oob: bool = False,  # Whether to render as OOB swap
) -> Any:  # Progress indicator component
    "Render position indicator showing current item in the collection."
```

### Router (`router.ipynb`)

> Convenience router factory that wires up standard card stack routes
> (Tier 2 API).

#### Import

``` python
from cjm_fasthtml_card_stack.routes.router import (
    init_card_stack_router
)
```

#### Functions

``` python
def init_card_stack_router(
    config: CardStackConfig,  # Card stack configuration
    state_getter: Callable[[], CardStackState],  # Function to get current state
    state_setter: Callable[[CardStackState], None],  # Function to save state
    get_items: Callable[[], List[Any]],  # Function to get current items list
    render_card: Callable,  # Card renderer callback: (item, CardRenderContext) -> FT
    route_prefix: str = "/card-stack",  # Route prefix for all card stack routes
    progress_label: str = "Item",  # Label for progress indicator
) -> Tuple[APIRouter, CardStackUrls]:  # (router, urls) tuple
    "Initialize an APIRouter with all standard card stack routes."
```

### JS: Scroll Navigation (`scroll.ipynb`)

> JavaScript generator for scroll-to-nav conversion.

#### Import

``` python
from cjm_fasthtml_card_stack.js.scroll import (
    generate_scroll_nav_js
)
```

#### Functions

``` python
def generate_scroll_nav_js(
    ids: CardStackHtmlIds,  # HTML IDs for this card stack instance
    button_ids: CardStackButtonIds,  # Button IDs for navigation triggers
    disable_in_modes: Tuple[str, ...] = (),  # Mode names where scroll nav is suppressed
) -> str:  # JavaScript code fragment for scroll navigation
    "Generate JS for scroll wheel to navigation conversion."
```

### States (`states.ipynb`)

> Loading, empty, and placeholder card components for the card stack
> viewport.

#### Import

``` python
from cjm_fasthtml_card_stack.components.states import (
    render_placeholder_card,
    render_loading_state,
    render_empty_state
)
```

#### Functions

``` python
def render_placeholder_card(
    placeholder_type: Literal["start", "end"],  # Which edge of the list
) -> Any:  # Placeholder card component
    "Render a placeholder card for viewport edges."
```

``` python
def render_loading_state(
    ids: CardStackHtmlIds,  # HTML IDs for this card stack instance
    message: str = "Loading...",  # Loading message text
) -> Any:  # Loading component
    "Render loading state with spinner and message."
```

``` python
def render_empty_state(
    ids: CardStackHtmlIds,  # HTML IDs for this card stack instance
    title: str = "No items available",  # Main empty state message
    subtitle: str = "",  # Optional subtitle text
) -> Any:  # Empty state component
    "Render empty state when no items exist."
```

### JS: Touch Navigation (`touch.ipynb`)

> JavaScript generator for touch-to-nav conversion: swipe, drag,

#### Import

``` python
from cjm_fasthtml_card_stack.js.touch import (
    generate_touch_nav_js
)
```

#### Functions

``` python
def generate_touch_nav_js(
    ids: CardStackHtmlIds,  # HTML IDs for this card stack instance
    button_ids: CardStackButtonIds,  # Button IDs for navigation triggers
    disable_in_modes: Tuple[str, ...] = (),  # Mode names where touch nav is suppressed
) -> str:  # JavaScript code fragment for touch navigation
    "Generate JS for touch gesture to navigation conversion."
```

### Viewport (`viewport.ipynb`)

> Card stack viewport with 3-section CSS Grid layout, slot rendering,

#### Import

``` python
from cjm_fasthtml_card_stack.components.viewport import (
    render_slot_card,
    render_all_slots_oob,
    render_viewport
)
```

#### Functions

``` python
def _render_mode_sync_script(
    active_mode: Optional[str] = None,  # Active keyboard mode name (None = navigation)
) -> Any:  # Script element that syncs keyboard mode state
    "Generate script to sync keyboard navigation mode with rendered UI state."
```

``` python
def _render_click_overlay(
    item_index: int,  # Index of the item this slot represents
    urls: CardStackUrls,  # URL bundle for navigation
) -> Any:  # Transparent click overlay element
    "Render transparent click-to-focus overlay for a context card slot."
```

``` python
def render_slot_card(
    slot_index: int,  # Index of this slot in the viewport (0-based)
    focus_slot: int,  # Which slot is the focused position
    card_items: List[Any],  # Full items list
    item_index: int,  # Item index (negative or >= len for placeholder)
    render_card: Callable,  # Callback: (item, CardRenderContext) -> FT
    state: CardStackState,  # Current card stack state
    config: CardStackConfig,  # Card stack configuration
    ids: CardStackHtmlIds,  # HTML IDs for this instance
    urls: CardStackUrls,  # URL bundle for navigation
    oob: bool = False,  # Whether to render as OOB swap
) -> Any:  # Slot content wrapper
    "Render a single card for a viewport slot."
```

``` python
def render_all_slots_oob(
    card_items: List[Any],  # All data items
    state: CardStackState,  # Current card stack state
    config: CardStackConfig,  # Card stack configuration
    ids: CardStackHtmlIds,  # HTML IDs for this instance
    urls: CardStackUrls,  # URL bundle for navigation
    render_card: Callable,  # Card renderer callback
) -> List[Any]:  # List of OOB elements (3 sections)
    "Render all viewport sections with OOB swap for granular updates."
```

``` python
def _grid_template_rows(
    focus_position: Optional[int] = None,  # Focus slot offset (None=center, -1=bottom, 0=top)
) -> str:  # CSS grid-template-rows value
    "Compute CSS grid-template-rows based on focus position intent."
```

``` python
def render_viewport(
    card_items: List[Any],  # All data items
    state: CardStackState,  # Current card stack state
    config: CardStackConfig,  # Card stack configuration
    ids: CardStackHtmlIds,  # HTML IDs for this instance
    urls: CardStackUrls,  # URL bundle for navigation
    render_card: Callable,  # Card renderer callback
    form_input_name: str = "focused_index",  # Name for the focused index hidden input
) -> Any:  # Viewport component with 3-section layout
    "Render the card stack viewport with 3-section CSS Grid layout."
```

### JS: Viewport Height (`viewport.ipynb`)

> JavaScript generator for dynamic viewport height calculation.

#### Import

``` python
from cjm_fasthtml_card_stack.js.viewport import (
    generate_viewport_height_js
)
```

#### Functions

``` python
def generate_viewport_height_js(
    """
    Generate JS for dynamic viewport height calculation.
    
    Uses position-based measurement to determine available space. Temporarily
    collapses the card stack to measure its natural top position, then
    calculates available height based on actual visual positions.
    
    This approach handles margin collapsing, flex gap, grid gap, and any
    other CSS layout mechanism automatically.
    
    Debug mode: Set `window.cardStackDebug = true` in browser console
    to log all intermediate calculation values.
    """
```
