feat(embeds): detailed Steam store / news / app-widget embeds
Recognize store.steampowered.com content URLs and render each richly, within
the existing privacy-first facade. 2-agent reviewed (both SHIP).
- getSteamTarget / steamWidgetEmbedUrl (videoEmbed.ts, +tests): classify
/app/{id}, /news/app/{id}/view/{gid}, /(bundle|sub|dlc)/{id}; non-content
pages (home/search/wishlist) and other hosts fall through to the generic card.
- SteamCard now dispatches:
- app → OG capsule header + click-to-play facade → Steam's OFFICIAL store
widget iframe (store.steampowered.com/widget/{id}): live region-aware price,
discount %, Buy on Steam. Nothing loads from Steam until "Show price &
store" is pressed; gated by the inlineMediaEmbeds setting. App pages use the
wide card so the ~646px widget has room.
- news → rich announcement card (banner + headline + body preview + link) —
your example URL previously fell through to the plain generic card.
- bundle/sub/dlc → the OG store card.
Grounded in our CSP: the widget works via frame-src https: (no infra change),
images route through the homeserver (img-src excludes Steam), and there is NO
client-side Steam API call (connect-src + Steam CORS both block it) — which is
also the honest ceiling: no review scores/genres client-side, price/buy come
from the official widget.
Runtime QA still needed: the live widget iframe rendering (height/fit) can't be
verified headlessly.
Gates: tsc 0, eslint 0, prettier clean, 912 tests, build ok.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -419,6 +419,56 @@ export const BadgeSteam = style({
|
||||
color: '#c7d5e0',
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Steam card — full-width header/banner image + official store widget iframe
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const SteamBannerWrapper = style([
|
||||
DefaultReset,
|
||||
{
|
||||
position: 'relative',
|
||||
display: 'block',
|
||||
width: '100%',
|
||||
// Steam header capsules are 460×215 (~2.14:1); news banners vary but crop
|
||||
// fine to the same ratio.
|
||||
aspectRatio: '460 / 215',
|
||||
overflow: 'hidden',
|
||||
flexShrink: 0,
|
||||
backgroundColor: '#0e1520',
|
||||
cursor: 'pointer',
|
||||
|
||||
':hover': {
|
||||
filter: 'brightness(0.9)',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
export const SteamBannerImg = style([
|
||||
DefaultReset,
|
||||
{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
objectPosition: 'center',
|
||||
display: 'block',
|
||||
},
|
||||
]);
|
||||
|
||||
// The official Steam store widget is a compact banner (~646×190). Full-width,
|
||||
// fixed height so the iframe doesn't collapse to its intrinsic size.
|
||||
export const SteamWidget = style([
|
||||
DefaultReset,
|
||||
{
|
||||
width: '100%',
|
||||
height: toRem(190),
|
||||
border: 0,
|
||||
display: 'block',
|
||||
borderRadius: config.radii.R300,
|
||||
backgroundColor: '#1b2838',
|
||||
marginTop: config.space.S100,
|
||||
},
|
||||
]);
|
||||
|
||||
export const BadgeWikipedia = style({
|
||||
backgroundColor: color.SurfaceVariant.ContainerLine,
|
||||
color: color.SurfaceVariant.OnContainer,
|
||||
|
||||
Reference in New Issue
Block a user