cjm-fasthtml-card-stack 0.0.26
A fixed-viewport card stack component for FastHTML with keyboard navigation, scroll-to-nav, configurable focus position, and HTMX-driven OOB updates.
License: Apache-2.0
Requires Python: >=3.12
Keywords: nbdev jupyter notebook python

# 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_html_ids
    components_controls --> core_config
    components_progress --> core_html_ids
    components_states --> core_html_ids
    components_viewport --> core_models
    components_viewport --> core_html_ids
    components_viewport --> helpers_focus
    components_viewport --> core_config
    components_viewport --> components_states
    components_viewport --> core_constants
    helpers_focus --> core_html_ids
    js_auto_adjust --> core_models
    js_auto_adjust --> core_html_ids
    js_auto_adjust --> core_constants
    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_html_ids
    js_core --> core_config
    js_core --> js_controls
    js_core --> core_constants
    js_core --> js_navigation
    js_core --> core_models
    js_core --> core_button_ids
    js_core --> js_auto_adjust
    js_core --> js_viewport
    js_core --> js_touch
    js_core --> js_scroll
    js_navigation --> core_button_ids
    js_scroll --> core_constants
    js_scroll --> core_html_ids
    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_html_ids
    keyboard_actions --> core_config
    keyboard_actions --> core_button_ids
    keyboard_actions --> js_core
    keyboard_actions --> core_models
    routes_handlers --> core_models
    routes_handlers --> core_html_ids
    routes_handlers --> core_config
    routes_handlers --> components_progress
    routes_handlers --> helpers_focus
    routes_handlers --> components_viewport
    routes_router --> core_models
    routes_router --> core_html_ids
    routes_router --> core_config
    routes_router --> routes_handlers
```

*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_ring: str = _DEFAULT_FOCUS_RING  # Ring classes for focused card
    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_RING: str
_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
    is_auto_mode: bool = False,  # Whether auto-adjust mode is active
) -> 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