/* * LinkPreview recipe — Radix HoverCard equivalent. The trigger is an * `` (navigational anchor); on hover (after `openDelay`, default * 700ms) the Content panel reveals next to the anchor with a SafePolygon * bridging trigger and content so the pointer can travel without * dismissing. * * Soma owns the floating math, dismissal, open/close timing and * `data-state` / `data-side`. Eidos paints: * [data-link-preview-trigger] → anchor styling (underline + hover/open color) * [data-link-preview-content] → floating panel chrome (border, bg, padding) * [data-link-preview-arrow] → arrow fill matching the panel bg * * Unlike Popover/Dialog, the Trigger is intentionally NOT a Button — * link previews are anchors, so the recipe styles the `` directly * (underline + accent color, not bordered button chrome). */ /* ── Trigger ─────────────────────────────────────────────────────────── */ [data-link-preview-trigger] { color: var(--color-primary-text, var(--color-content-primary)); text-decoration-line: underline; text-decoration-thickness: 1px; text-underline-offset: 0.18em; text-decoration-color: color-mix(in srgb, currentColor 35%, transparent); text-decoration-skip-ink: auto; border-radius: var(--radius-sm); outline: none; transition: color var(--duration-fast) var(--ease-default), text-decoration-color var(--duration-fast) var(--ease-default); } [data-link-preview-trigger]:hover, [data-link-preview-trigger][data-state='open'] { color: var(--color-primary-solid, var(--color-content-primary)); text-decoration-color: currentColor; } [data-link-preview-trigger]:focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring-color); outline-offset: var(--focus-ring-offset); } /* ── Content ─────────────────────────────────────────────────────────── */ /* * Size only affects PHYSICAL dimensions (padding, min-width, font-size). * Typography family / weight / line-height stay constant across sizes — * size and intrinsic type are independent concerns. */ [data-link-preview-content] { --_link-preview-py: var(--space-3); --_link-preview-px: var(--space-4); --_link-preview-font-size: var(--font-size-sm); --_link-preview-min-width: 14rem; --_link-preview-max-width: min(28rem, calc(100vw - var(--space-4))); display: grid; gap: var(--space-2); min-inline-size: var(--_link-preview-min-width); max-inline-size: var(--_link-preview-max-width); padding: var(--_link-preview-py) var(--_link-preview-px); border: 1px solid var(--color-border-default); border-radius: var(--radius-md); background: var(--color-surface-overlay); color: var(--color-content-primary); box-shadow: var(--shadow-overlay); font-family: var(--font-ui); font-size: var(--_link-preview-font-size); font-weight: 400; line-height: var(--leading-body); outline: none; } [data-link-preview-content][data-size='xs'] { --_link-preview-py: var(--space-2); --_link-preview-px: var(--space-3); --_link-preview-font-size: var(--font-size-xs); --_link-preview-min-width: 11rem; } [data-link-preview-content][data-size='sm'] { --_link-preview-py: var(--space-2); --_link-preview-px: var(--space-3); --_link-preview-font-size: var(--font-size-sm); --_link-preview-min-width: 12rem; } [data-link-preview-content][data-size='md'] { --_link-preview-py: var(--space-3); --_link-preview-px: var(--space-4); --_link-preview-font-size: var(--font-size-sm); --_link-preview-min-width: 14rem; } [data-link-preview-content][data-size='lg'] { --_link-preview-py: var(--space-4); --_link-preview-px: var(--space-5); --_link-preview-font-size: var(--font-size-md); --_link-preview-min-width: 18rem; } [data-link-preview-content][data-size='xl'] { --_link-preview-py: var(--space-5); --_link-preview-px: var(--space-6); --_link-preview-font-size: var(--font-size-md); --_link-preview-min-width: 22rem; } /* The floating wrapper is `position: fixed` but un-stacked. Lift it * above the page chrome so the panel actually paints over content. */ [data-floating-wrapper]:has(> [data-link-preview-content]) { z-index: var(--popover-content-z); } /* ── Arrow ──────────────────────────────────────────────────────────── */ [data-link-preview-arrow] { fill: var(--color-surface-overlay); } /* ── State transitions per anchored side ────────────────────────────── */ /* * Soma's Floating layer writes `data-side` (top/right/bottom/left) on * Content based on the resolved placement. The preview slides into * place FROM the trigger edge. */ [data-link-preview-content][data-state='open'][data-side='top'] { animation: link-preview-in-top var(--duration-normal) var(--ease-out) both; } [data-link-preview-content][data-state='closed'][data-side='top'] { animation: link-preview-out-top var(--duration-fast) var(--ease-default) both; } [data-link-preview-content][data-state='open'][data-side='bottom'] { animation: link-preview-in-bottom var(--duration-normal) var(--ease-out) both; } [data-link-preview-content][data-state='closed'][data-side='bottom'] { animation: link-preview-out-bottom var(--duration-fast) var(--ease-default) both; } [data-link-preview-content][data-state='open'][data-side='right'] { animation: link-preview-in-right var(--duration-normal) var(--ease-out) both; } [data-link-preview-content][data-state='closed'][data-side='right'] { animation: link-preview-out-right var(--duration-fast) var(--ease-default) both; } [data-link-preview-content][data-state='open'][data-side='left'] { animation: link-preview-in-left var(--duration-normal) var(--ease-out) both; } [data-link-preview-content][data-state='closed'][data-side='left'] { animation: link-preview-out-left var(--duration-fast) var(--ease-default) both; } @keyframes link-preview-in-top { from { opacity: 0; transform: translateY(4px) scale(0.985); } } @keyframes link-preview-out-top { to { opacity: 0; transform: translateY(4px) scale(0.985); } } @keyframes link-preview-in-bottom { from { opacity: 0; transform: translateY(-4px) scale(0.985); } } @keyframes link-preview-out-bottom { to { opacity: 0; transform: translateY(-4px) scale(0.985); } } @keyframes link-preview-in-right { from { opacity: 0; transform: translateX(-4px) scale(0.985); } } @keyframes link-preview-out-right { to { opacity: 0; transform: translateX(-4px) scale(0.985); } } @keyframes link-preview-in-left { from { opacity: 0; transform: translateX(4px) scale(0.985); } } @keyframes link-preview-out-left { to { opacity: 0; transform: translateX(4px) scale(0.985); } } @media (prefers-reduced-motion: reduce) { [data-link-preview-content] { animation: none !important; } }