Skip to content

Shed standalone page chrome when embedded in an iframe#47

Merged
jbeshir merged 1 commit into
mainfrom
embed-chromeless
Jun 22, 2026
Merged

Shed standalone page chrome when embedded in an iframe#47
jbeshir merged 1 commit into
mainfrom
embed-chromeless

Conversation

@jbeshir

@jbeshir jbeshir commented Jun 22, 2026

Copy link
Copy Markdown
Owner

When a widget is embedded via iframe, the iframe is the container — so the standalone presentation chrome (outer page background, centered max-width, floating-card shadow/border/radius) becomes a redundant double-frame: the host surface, then a band of the widget's own background, then a shadowed rounded card on top.

This makes each widget detect the framed case and shed that chrome so the card fills the frame.

How

Each main.tsx adds an embedded class to <html> when framed:

if (window.self !== window.top && new URLSearchParams(location.search).get('embed') !== '0') {
  document.documentElement.classList.add('embedded');
}

(window.self !== window.top is safe cross-origin; ?embed=0 forces the framed look back if a host wants it.)

An .embedded block in each styles.css then:

  • makes the body background transparent (kills the redundant inner band),
  • drops the outer container's max-width and padding (fills the frame),
  • removes the card's border, radius, and shadow (the iframe is the frame).

Why it stays readable

The card keeps its own opaque, theme-correct background, so contrast holds whatever the host page's background is — important because each widget's light/dark follows the viewer's OS, not the host page. (True host-blending transparency would need the host to drive the theme; deliberately out of scope.)

Per-widget notes

  • image-comparison-table also zeroes its .page padding/min-height so the fit-scale wrapper measures against the full frame, and keeps its rose top-accent strip as a small brand cue.
  • The auto-height postMessage already in pennsic/verb-tower pairs naturally: a chromeless card that reports its height is exactly what a host wants.

Verification

Rendered standalone vs embedded (with a simulated host backdrop) in light and dark for all four — standalone keeps the framed card; embedded fills the frame flush with the card's opaque surface, transparency confirmed by the host showing through where the card doesn't cover. scripts/validate-widgets.mjs passes (4/4).

🤖 Generated with Claude Code

Each widget now adds an 'embedded' class to the document element when it detects it is running framed (window.self !== window.top), with ?embed=0 as an escape hatch to force the framed look. An .embedded block in each styles.css then makes the body background transparent, drops the outer container's max-width/padding, and removes the card's border, radius, and shadow — so the card fills the frame instead of sitting as a shadowed, centred panel on a redundant inner background.

The card keeps its own opaque, theme-correct background, so text contrast holds regardless of the host page's background (the widget's light/dark still follows the viewer's OS, not the host). image-comparison-table also zeroes its .page padding/min-height so the fit-scale wrapper measures against the full frame, and keeps its rose top-accent strip as a small brand cue.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jbeshir jbeshir merged commit cdbf3ab into main Jun 22, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant