## hover_card (`poetry_hover_card`)

A card that reveals preview content when its trigger is hovered.

Class: Poetry::Ui::HoverCard::Component - BEM block `poetry-ui-hover_card`.
Slot REQUIRED: with_trigger (the enriched link) - a call without it raises.
- `align:` (symbol) - one of start|center|end, default "center" - Panel alignment along the chosen side.
- `align_offset:` (integer) - default 0 - Shift in pixels along the alignment axis.
- `avoid_collisions:` (boolean) - default true - Flips and shifts the panel to stay inside the viewport.
- `close_delay:` (integer) - default 300 - Close-grace window in ms over the trigger+content pair.
- `content_class:` (string) - The panel's class merge seam - e.g. content_class: "w-80" widens the card.
- `defer:` (string) - Defer the card body to a lazy turbo-frame. The panel is hidden until hover, so the fetch fires on first open for free; the component block (if any) becomes the frame's placeholder.
- `open:` (boolean) - default false - Renders the card already open on page load.
- `open_delay:` (integer) - default 600 - Hover-intent delay in ms before the card opens.
- `side:` (symbol) - one of top|right|bottom|left, default "bottom" - Which side of the anchor the panel opens on.
- `side_offset:` (integer) - default 4 - Gap in pixels between the anchor and the panel.
Slots: trigger (The enriched LINK: a real navigable <a> - THE no-JS fallback. tag: passthrough exists but change it knowingly (an <a> is the contract's fallback story). NO aria-haspopup/expanded/describedby - the card is invisible to the accessibility tree on purpose. Built as a lazy anatomy part (rendered at render time, not at with_trigger time). variant:/size: route through Button::Component - Button's href-implies-anchor keeps the trigger a REAL <a> wearing button styling, so the reachable-elsewhere contract holds.; with_trigger yields NOTHING to the block - no |param|, write content directly).
- PART `hover-card` - Root wrapper around the trigger link and the panel
- PART `hover-card-trigger` - The enriched link itself - simultaneously the no-JS fallback, the touch path, and the keyboard path | states: data-popup-open (bare while the card is open; absent while closed (absence IS the closed state))
- PART `hover-card-content` - The role-less preview panel (invisible to AT on purpose) - positioning, animation, and the open state ride here | states: data-open (card is open (the controller flips the pair at runtime)); data-closed (card is closed (the server-rendered state; hidden rides along)); data-side=top|right|bottom|left (always - the side (initial placement, re-resolved live by popper after flip)); data-align=start|center|end (always - the alignment (re-resolved live by popper)) | vars: --transform-origin (the anchor-facing origin popper writes for scale-in animation); --available-width (viewport space left for the panel (popper, post-flip)); --available-height (viewport space left for the panel (popper, post-flip)); --anchor-width (the anchor's measured width (popper)); --anchor-height (the anchor's measured height (popper))
- WIRING root: `poetry--core--hover-card` registers; values open, open_delay, close_delay | `poetry--core--popper` registers; values side, align, side_offset, align_offset, avoid_collisions
- WIRING trigger: `poetry--core--hover-card` actions pointerEnter on pointerenter, pointerLeave on pointerleave, focusOpen on focus, blurClose on blur, touchGuard on touchstart | `poetry--core--popper` targets anchor
- WIRING content: `poetry--core--popper` targets content
- RULE: with_trigger(compose: true) { |wiring| ... } composes YOUR control as the trigger: the block is yielded the trigger wiring (the Stimulus behavior the overlay needs; poppers add id/aria and their trigger slot, modals hand only the open action) - splat it onto a wiring-free control (poetry_sidebar_menu_button, a plain tag); without compose: the classic composed Button renders.
- RULE: Use poetry_hover_card - never hand-roll hover-div previews.
- RULE: THE REACHABLE-ELSEWHERE RULE (non-negotiable): every piece of information in a hover card MUST exist at the trigger link's destination (or another keyboard/touch-reachable surface). The card is pointer-only enrichment - keyboard and touch users never see inside it.
- RULE: The trigger must be a REAL link with a real href - it is the fallback, the touch path, and the keyboard path all at once. For a button LOOK, pass variant:/size: (renders through Button, still an <a> via href:) - never swap the tag to :button.
- RULE: NO interactive elements inside the card - they get tabindex=-1 stripped and become pointer-only traps. Actions belong in a Popover or at the destination.
- RULE: Don't add aria-expanded/haspopup to the trigger - advertising an unreachable surface is worse than silence.
- RULE: Never use HoverCard for hints (Tooltip) or for content users act on (Popover).
- RULE: Prefer defer: for expensive previews - a lazy turbo-frame that fetches on first open.
