open-design/assets/frames/macbook.html
Sid 8bcd96f5e5
fix(frames): resolve relative screen= against embedder URL (#2316)
Shared device frames serve at /frames/<name>.html and previously
assigned the raw ?screen= value to the inner iframe.src. A
project-relative value like screen=screens/foo.html resolved against
/frames/, producing /frames/screens/foo.html (404), instead of the
embedding project's /api/projects/:id/raw/screens/foo.html.

The five frame HTML files now resolve relative ?screen= values
against document.referrer when present (the embedding project
preview), falling back to location.href so standalone /frames/*
loads keep working. Absolute and root-relative paths are passed
through unchanged.

Adds an e2e Vitest spec that evaluates each frame's inline <script>
in a Node vm and asserts iframe.src under five scenarios per file
(25 cases total): project-relative against referrer, root-relative
pass-through, absolute pass-through, empty referrer fallback, and
missing ?screen= no-op.

Fixes #2234
2026-05-20 10:03:01 +08:00

139 lines
3.5 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<!--
Shared frame: MacBook Pro 14" with display + lid + bottom keyboard hint.
The lid renders a 1440×900 viewport scaled to 720×450 (50%) so the
whole laptop fits in a typical 880×600 layout cell. Pass the actual
desktop screen via ?screen= and it renders at full 1440×900 inside.
-->
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>MacBook frame</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; height: 100%; background: transparent; }
body {
display: grid;
place-items: center;
font: 14px/1.4 -apple-system, BlinkMacSystemFont, 'SF Pro Text', system-ui, sans-serif;
}
.laptop {
position: relative;
width: 880px;
}
/* lid */
.lid {
position: relative;
width: 100%;
aspect-ratio: 1440 / 900;
background: #1a1a1a;
border-radius: 18px 18px 4px 4px;
padding: 14px 14px 18px;
box-shadow:
0 0 0 1px rgba(255,255,255,0.05) inset,
0 8px 20px -8px rgba(0,0,0,0.4);
}
/* notch */
.lid::before {
content: '';
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
width: 156px;
height: 18px;
background: #1a1a1a;
border-radius: 0 0 12px 12px;
z-index: 5;
}
/* camera dot */
.lid::after {
content: '';
position: absolute;
top: 7px;
left: 50%;
transform: translateX(-50%);
width: 5px; height: 5px;
background: #0a0a0a;
border-radius: 50%;
box-shadow: 0 0 0 1px rgba(255,255,255,0.04);
z-index: 6;
}
.display {
width: 100%;
height: 100%;
background: #fafaf7;
border-radius: 4px;
overflow: hidden;
position: relative;
}
.display iframe {
width: 1440px;
height: 900px;
border: 0;
background: #fafaf7;
transform: scale(calc(852 / 1440)); /* 880 - 28 padding = 852 inner */
transform-origin: top left;
}
/* base / chin */
.base {
position: relative;
width: 100%;
height: 14px;
margin-top: -1px;
background:
linear-gradient(to bottom, #c0c0c4 0%, #9a9a9e 70%, #7a7a7e 100%);
border-radius: 0 0 6px 6px;
box-shadow:
0 0 0 1px rgba(255,255,255,0.4) inset,
0 6px 12px -4px rgba(0,0,0,0.25);
}
.base::before {
content: '';
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
width: 96px;
height: 4px;
background: rgba(0,0,0,0.18);
border-radius: 0 0 6px 6px;
}
</style>
</head>
<body>
<div class="laptop">
<div class="lid">
<div class="display">
<iframe
id="screen"
title="Inner screen"
sandbox="allow-scripts allow-same-origin"
loading="lazy"
src="about:blank"
></iframe>
</div>
</div>
<div class="base" aria-hidden></div>
</div>
<script>
(function () {
var qs = new URLSearchParams(location.search);
var raw = qs.get('screen');
var iframe = document.getElementById('screen');
if (!raw) return;
var isAbsolute = /^[a-zA-Z][a-zA-Z\d+.\-]*:/.test(raw) || raw.charAt(0) === '/';
iframe.src = isAbsolute
? raw
: new URL(raw, document.referrer || location.href).toString();
})();
</script>
</body>
</html>