diff --git a/src/web/stepper/src/SubstVisualizer.tsx b/src/web/stepper/src/SubstVisualizer.tsx index 8b81e24..694f9c5 100644 --- a/src/web/stepper/src/SubstVisualizer.tsx +++ b/src/web/stepper/src/SubstVisualizer.tsx @@ -397,6 +397,23 @@ function ProfileHoverTextPopover({ text }: { text: string }) { ); } +/** Popover body for an `image` part: the inline opaque-value thumbnail, enlarged to be legible. */ +function EnlargedThumbnailPopover({ src, alt }: { src: string; alt?: string }) { + return ( +
+
+ {alt ? ( + <> + + {` ${alt}`} + + ) : null} + {alt +
+
+ ); +} + /** * renderNode renders a serialized Stepper AST node to a React ReactNode. */ @@ -860,14 +877,23 @@ function renderNode( const src = readNodeProp(node, part.image); if (typeof src !== "string" || !src.startsWith("data:")) return null; const alt = part.altProp === undefined ? undefined : readNodeProp(node, part.altProp); + const altText = alt == null ? "" : String(alt); return ( - {alt + interactionKind="hover" + placement="bottom" + usePortal={popoverDepth === 0} + lazy + popoverClassName="stepper-popover" + content={} + > + {altText} + ); } return null; diff --git a/src/web/stepper/src/styles.ts b/src/web/stepper/src/styles.ts index 801e73f..52f495e 100644 --- a/src/web/stepper/src/styles.ts +++ b/src/web/stepper/src/styles.ts @@ -62,6 +62,19 @@ const STEPPER_CSS = ` border-radius: 3px; } +/* The same thumbnail enlarged, shown in the hover popover so an opaque value (e.g. a rune) is + * actually legible while the inline copy stays at text height. A fixed width (not max-width) forces + * a low-resolution thumbnail to scale up rather than render at its small intrinsic size. */ +.sa-substituter .stepper-opaque-thumbnail-large, +.stepper-popover .stepper-opaque-thumbnail-large { + display: block; + width: min(200px, 50vw); + height: auto; + margin-top: 6px; + border-radius: 3px; + image-rendering: auto; +} + .sa-substituter .stepper-mu-term, .stepper-popover .stepper-mu-term { font-weight: bold;