385 lines
360 KiB
HTML
385 lines
360 KiB
HTML
<!DOCTYPE html>
|
|||
|
|
<html>
|
||
|
|
<head>
|
||
|
|
<meta charset="utf-8">
|
||
|
|
<title>Bundled Page</title>
|
||
|
|
<style>
|
||
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
|
|
body { background: #25252a; display: flex; align-items: center; justify-content: center; min-height: 100vh; font-family: -apple-system, BlinkMacSystemFont, sans-serif; }
|
||
|
|
#__bundler_loading { position: fixed; bottom: 20px; right: 20px; font: 13px/1.4 -apple-system, BlinkMacSystemFont, sans-serif; color: #666; background: #fff; padding: 8px 14px; border-radius: 8px; box-shadow: 0 1px 4px rgba(0,0,0,0.12); z-index: 10000; }
|
||
|
|
#__bundler_thumbnail { position: fixed; inset: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; background: #25252a; z-index: 9999; }
|
||
|
|
#__bundler_thumbnail svg { width: 100%; height: 100%; object-fit: contain; }
|
||
|
|
#__bundler_placeholder { color: #999; font-size: 14px; }
|
||
|
|
</style>
|
||
|
|
<noscript>
|
||
|
|
<style>#__bundler_loading { display: none; }</style>
|
||
|
|
<div style="position:fixed;bottom:12px;left:12px;font:13px/1.4 -apple-system,BlinkMacSystemFont,sans-serif;color:#999;background:rgba(255,255,255,0.9);padding:6px 12px;border-radius:6px;box-shadow:0 1px 4px rgba(0,0,0,0.08);z-index:10000;">
|
||
|
|
This page requires JavaScript to display.
|
||
|
|
</div>
|
||
|
|
</noscript>
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
<div id="__bundler_thumbnail"><svg viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg"><rect x="16" y="16" width="32" height="32" rx="7" fill="#a99af0"></rect><rect x="21.5" y="21" width="21" height="22" rx="5" fill="#2a2431"></rect><rect x="24.25" y="24" width="15.5" height="12.5" rx="3" fill="#a99af0"></rect><circle cx="28.75" cy="30.25" r="1.9" fill="#2a2431"></circle><path d="M32.5 30.75 Q35 28 37.5 30.75" fill="none" stroke="#2a2431" stroke-width="1.8" stroke-linecap="round"></path></svg></div>
|
||
|
|
<div id="__bundler_loading">Unpacking...</div>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
|
||
|
|
document.addEventListener('DOMContentLoaded', async function() {
|
||
|
|
const loading = document.getElementById('__bundler_loading');
|
||
|
|
function setStatus(msg) { if (loading) loading.textContent = msg; }
|
||
|
|
|
||
|
|
const FONT_MIME = /^(font[/]|application[/](x-)?font-|application[/]vnd\.ms-fontobject)/i;
|
||
|
|
const MIME_TOKEN = /^[\w.+-]+[/][\w.+-]+$/;
|
||
|
|
function toBase64(bytes) {
|
||
|
|
let bin = '';
|
||
|
|
for (let i = 0; i < bytes.length; i += 0x8000) {
|
||
|
|
bin += String.fromCharCode.apply(null, bytes.subarray(i, i + 0x8000));
|
||
|
|
}
|
||
|
|
return btoa(bin);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Error sink persists across replaceWith since it's on window, not the DOM.
|
||
|
|
window.addEventListener('error', function(e) {
|
||
|
|
// Failed resource loads (CSP-blocked links, scripts, images) fire plain
|
||
|
|
// events at the element — warn only; real JS errors carry message/error.
|
||
|
|
if (!e.message && !e.error && e.target && e.target !== window) {
|
||
|
|
console.warn('[bundle] resource failed to load:',
|
||
|
|
e.target.tagName, String(e.target.src || e.target.href || ''));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var p = document.body || document.documentElement;
|
||
|
|
var d = document.getElementById('__bundler_err') || p.appendChild(document.createElement('div'));
|
||
|
|
d.id = '__bundler_err';
|
||
|
|
d.style.cssText = 'position:fixed;bottom:12px;left:12px;right:12px;font:12px/1.4 ui-monospace,monospace;background:#2a1215;color:#ff8a80;padding:10px 14px;border-radius:8px;border:1px solid #5c2b2e;z-index:99999;white-space:pre-wrap;max-height:40vh;overflow:auto';
|
||
|
|
d.textContent = (d.textContent ? d.textContent + String.fromCharCode(10) : '') +
|
||
|
|
'[bundle] ' + (e.message || e.type) +
|
||
|
|
(e.filename ? ' (' + e.filename.slice(0, 60) + ':' + e.lineno + ')' : '');
|
||
|
|
}, true);
|
||
|
|
|
||
|
|
try {
|
||
|
|
const manifestEl = document.querySelector('script[type="__bundler/manifest"]');
|
||
|
|
const templateEl = document.querySelector('script[type="__bundler/template"]');
|
||
|
|
if (!manifestEl || !templateEl) {
|
||
|
|
setStatus('Error: missing bundle data');
|
||
|
|
console.error('[bundler] Missing script tags — manifestEl:', !!manifestEl, 'templateEl:', !!templateEl);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const manifest = JSON.parse(manifestEl.textContent);
|
||
|
|
let template = JSON.parse(templateEl.textContent);
|
||
|
|
|
||
|
|
// Nested page bundles (iframe targets). Each ships ONCE, in the ROOT
|
||
|
|
// document's manifest; frame srcs carry an about:blank#<uuid> marker
|
||
|
|
// instead of a substitutable uuid. Every document mints the blobs for
|
||
|
|
// its OWN frames — from local text at the root, or text obtained from
|
||
|
|
// its DIRECT parent over the relay protocol below — so each frame
|
||
|
|
// navigation stays same-origin with its initiator and a deduped page
|
||
|
|
// loads at any depth on any host, including a file:// download's
|
||
|
|
// opaque origins (where a blob minted by one document cannot be
|
||
|
|
// navigated to by another).
|
||
|
|
const pageOrderEl = document.querySelector('script[type="__bundler/page_order"]');
|
||
|
|
const pageOrder = pageOrderEl ? JSON.parse(pageOrderEl.textContent) : [];
|
||
|
|
const pageSet = new Set(pageOrder);
|
||
|
|
const pageTexts = {};
|
||
|
|
|
||
|
|
// ── Nested-page text protocol (parent-chain relay) ──
|
||
|
|
// Page bundles ship once, in the ROOT document's manifest. Every
|
||
|
|
// document mints the blobs for its OWN frames, obtaining page text
|
||
|
|
// through a strict parent-chain relay: a document only ever SENDS to
|
||
|
|
// its direct parent (which, for every document the publisher's own
|
||
|
|
// runtime minted, is another publisher document) and only ACCEPTS
|
||
|
|
// text from that direct parent; requests from below are honored only
|
||
|
|
// for the document's own child frames, and the ROOT never sends
|
||
|
|
// upward at all. That last rule is load-bearing: the artifact host's
|
||
|
|
// frame-ancestors allows claude.ai (and friends) to frame an
|
||
|
|
// artifact, so window.top can be a FOREIGN document — a top-addressed
|
||
|
|
// protocol would hand page uuids to that ancestor and accept forged
|
||
|
|
// "page text" from it, minting attacker HTML as an artifact-origin
|
||
|
|
// blob document. The chain never touches the ancestor: requests stop
|
||
|
|
// at the root, replies are origin-addressed per hop (never '*' when
|
||
|
|
// an origin exists), and both directions are origin-gated (on https
|
||
|
|
// a foreign sender's origin can never equal this document's; in
|
||
|
|
// opaque contexts — file:// — everything serializes as 'null', where
|
||
|
|
// an embedded EXTERNAL frame still fails the gate by carrying its
|
||
|
|
// own real origin, and a sandboxed child of such a frame is excluded
|
||
|
|
// by the own-child-frames check plus uuid unguessability). Shape is
|
||
|
|
// deny-by-default: one request form, one reply form, no error
|
||
|
|
// replies.
|
||
|
|
// window.origin is the SERIALIZED SECURITY origin ('null' in opaque
|
||
|
|
// contexts such as a sandboxed preview iframe) — location.origin is
|
||
|
|
// computed from the URL and stays 'https://…' even when the document
|
||
|
|
// is sandboxed, which would misgate every opaque chain.
|
||
|
|
const OWN_TARGET = /^https?:[/][/]/.test(window.origin || '')
|
||
|
|
? window.origin
|
||
|
|
: '*';
|
||
|
|
function trustedOrigin(e) {
|
||
|
|
if (e.origin === window.origin) return true; // https chains; opaque==opaque
|
||
|
|
// file:// trees: Chrome serializes the file document's origin as
|
||
|
|
// 'file://' while the blob: documents it mints serialize as 'null',
|
||
|
|
// so the two legitimate shapes can never string-match — accept the
|
||
|
|
// pair in both directions. https origins never serialize as either,
|
||
|
|
// and sender identity (own child frame / direct parent) is checked
|
||
|
|
// separately.
|
||
|
|
return (
|
||
|
|
(e.origin === 'null' &&
|
||
|
|
(window.origin === 'file://' ||
|
||
|
|
window.location.protocol === 'file:')) ||
|
||
|
|
(/^file:/.test(e.origin) && window.origin === 'null')
|
||
|
|
);
|
||
|
|
}
|
||
|
|
// Non-empty only in the root document (nested bundles ship an empty
|
||
|
|
// island), so it doubles as the root marker: the root answers from
|
||
|
|
// local text and treats a miss as authoritative — never relaying to
|
||
|
|
// its (possibly foreign) parent.
|
||
|
|
const isPageRoot = pageOrder.length > 0;
|
||
|
|
const blobUrls = {};
|
||
|
|
const resourceBlobs = {};
|
||
|
|
const UUID_SHAPE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
||
|
|
const pendingFrames = {}; // uuid -> [{el, frag}] awaiting text for own frames
|
||
|
|
const pendingRelays = {}; // uuid -> [childWindow] awaiting text to pass down
|
||
|
|
function mountPage(el, frag, text) {
|
||
|
|
const pageBlob = new Blob([text], { type: 'text/html' });
|
||
|
|
const u = URL.createObjectURL(pageBlob);
|
||
|
|
resourceBlobs[u] = pageBlob;
|
||
|
|
el.src = u + frag;
|
||
|
|
}
|
||
|
|
function deliverPage(uuid, text) {
|
||
|
|
const frames = pendingFrames[uuid];
|
||
|
|
delete pendingFrames[uuid];
|
||
|
|
if (frames) for (const f of frames) mountPage(f.el, f.frag, text);
|
||
|
|
const relays = pendingRelays[uuid];
|
||
|
|
delete pendingRelays[uuid];
|
||
|
|
if (relays) {
|
||
|
|
for (const cw of relays) {
|
||
|
|
try { cw.postMessage({ __bundler_page: uuid, __bundler_text: text }, OWN_TARGET); } catch (err) { /* gone */ }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function ownFrameWindows() {
|
||
|
|
const out = [];
|
||
|
|
for (const f of Array.from(document.querySelectorAll('iframe'))) {
|
||
|
|
try { if (f.contentWindow) out.push(f.contentWindow); } catch (err) { /* detached */ }
|
||
|
|
}
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
window.addEventListener('message', function (e) {
|
||
|
|
const d = e.data;
|
||
|
|
if (!d || typeof d !== 'object' || !e.source) return;
|
||
|
|
// Origin gate first; the structural checks below (own-child-frames
|
||
|
|
// for requests, direct-parent + solicited-uuid for replies, the
|
||
|
|
// root's never-relay rule) are what hold in fully opaque chains
|
||
|
|
// where origin strings can't discriminate — a sandboxed root's
|
||
|
|
// FOREIGN wrapper still fails all three even though both serialize
|
||
|
|
// as real-vs-'null' rather than matching.
|
||
|
|
if (!trustedOrigin(e)) return;
|
||
|
|
if (typeof d.__bundler_need === 'string' && UUID_SHAPE.test(d.__bundler_need)) {
|
||
|
|
// Requests are honored only for this document's own child frames.
|
||
|
|
if (ownFrameWindows().indexOf(e.source) < 0) return;
|
||
|
|
const uuid = d.__bundler_need;
|
||
|
|
const text = pageTexts[uuid];
|
||
|
|
if (typeof text === 'string') {
|
||
|
|
try { e.source.postMessage({ __bundler_page: uuid, __bundler_text: text }, OWN_TARGET); } catch (err) { /* gone */ }
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (isPageRoot || window.parent === window) return; // authoritative miss
|
||
|
|
const waiting = pendingRelays[uuid];
|
||
|
|
if (waiting) {
|
||
|
|
if (waiting.indexOf(e.source) < 0) waiting.push(e.source);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
// Never-answered uuids would otherwise accumulate forever under a
|
||
|
|
// request spray; past the cap (far above any real page count) new
|
||
|
|
// relays are dropped rather than remembered.
|
||
|
|
if (Object.keys(pendingRelays).length >= 128) return;
|
||
|
|
pendingRelays[uuid] = [e.source];
|
||
|
|
window.parent.postMessage({ __bundler_need: uuid }, OWN_TARGET);
|
||
|
|
} else if (
|
||
|
|
typeof d.__bundler_page === 'string' &&
|
||
|
|
UUID_SHAPE.test(d.__bundler_page) &&
|
||
|
|
typeof d.__bundler_text === 'string'
|
||
|
|
) {
|
||
|
|
// The shape test is load-bearing beyond tidiness: the solicited
|
||
|
|
// check below uses the in operator, which matches prototype keys
|
||
|
|
// ('__proto__', 'constructor') — an unshaped key would reach
|
||
|
|
// deliverPage and throw, painting the error banner.
|
||
|
|
// Text is accepted only from the direct parent — the document
|
||
|
|
// that minted this one.
|
||
|
|
if (e.source !== window.parent || window.parent === window) return;
|
||
|
|
if (!(d.__bundler_page in pendingFrames) && !(d.__bundler_page in pendingRelays)) return;
|
||
|
|
deliverPage(d.__bundler_page, d.__bundler_text);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
const uuids = Object.keys(manifest);
|
||
|
|
setStatus('Unpacking ' + uuids.length + ' assets...');
|
||
|
|
|
||
|
|
await Promise.all(uuids.map(async (uuid) => {
|
||
|
|
const entry = manifest[uuid];
|
||
|
|
try {
|
||
|
|
const binaryStr = atob(entry.data);
|
||
|
|
const bytes = new Uint8Array(binaryStr.length);
|
||
|
|
for (let i = 0; i < binaryStr.length; i++) bytes[i] = binaryStr.charCodeAt(i);
|
||
|
|
|
||
|
|
let finalBytes = bytes;
|
||
|
|
if (entry.compressed) {
|
||
|
|
if (typeof DecompressionStream !== 'undefined') {
|
||
|
|
const ds = new DecompressionStream('gzip');
|
||
|
|
const writer = ds.writable.getWriter();
|
||
|
|
const reader = ds.readable.getReader();
|
||
|
|
writer.write(bytes);
|
||
|
|
writer.close();
|
||
|
|
const chunks = [];
|
||
|
|
let totalLen = 0;
|
||
|
|
while (true) {
|
||
|
|
const { done, value } = await reader.read();
|
||
|
|
if (done) break;
|
||
|
|
chunks.push(value);
|
||
|
|
totalLen += value.length;
|
||
|
|
}
|
||
|
|
finalBytes = new Uint8Array(totalLen);
|
||
|
|
let offset = 0;
|
||
|
|
for (const chunk of chunks) { finalBytes.set(chunk, offset); offset += chunk.length; }
|
||
|
|
} else {
|
||
|
|
console.warn('DecompressionStream not available, asset ' + uuid + ' may not render');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (pageSet.has(uuid)) {
|
||
|
|
pageTexts[uuid] = new TextDecoder().decode(finalBytes);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (FONT_MIME.test(entry.mime) && MIME_TOKEN.test(entry.mime)) {
|
||
|
|
// Strict artifact hosts allow font-src data: but not blob:.
|
||
|
|
blobUrls[uuid] = 'data:' + entry.mime + ';base64,' +
|
||
|
|
(entry.compressed ? toBase64(finalBytes) : entry.data);
|
||
|
|
} else {
|
||
|
|
const blob = new Blob([finalBytes], { type: entry.mime });
|
||
|
|
blobUrls[uuid] = URL.createObjectURL(blob);
|
||
|
|
resourceBlobs[blobUrls[uuid]] = blob;
|
||
|
|
}
|
||
|
|
} catch (err) {
|
||
|
|
console.error('Failed to decode asset ' + uuid + ':', err);
|
||
|
|
const blob = new Blob([], { type: entry.mime });
|
||
|
|
blobUrls[uuid] = URL.createObjectURL(blob);
|
||
|
|
resourceBlobs[blobUrls[uuid]] = blob;
|
||
|
|
}
|
||
|
|
}));
|
||
|
|
|
||
|
|
const extResEl = document.querySelector('script[type="__bundler/ext_resources"]');
|
||
|
|
const extResources = extResEl ? JSON.parse(extResEl.textContent) : [];
|
||
|
|
const resourceMap = {};
|
||
|
|
for (const entry of extResources) {
|
||
|
|
if (blobUrls[entry.uuid]) resourceMap[entry.id] = blobUrls[entry.uuid];
|
||
|
|
}
|
||
|
|
|
||
|
|
// Artifact-host CSP (connect-src 'self') refuses fetch() of blob: URLs —
|
||
|
|
// consumers read these Blobs directly. Survives the swap like the error sink.
|
||
|
|
window.__resourceBlobs = resourceBlobs;
|
||
|
|
|
||
|
|
setStatus('Rendering...');
|
||
|
|
// Page uuids have no blob here — they ride inside about:blank#<uuid>
|
||
|
|
// frame markers the pass below resolves after the swap.
|
||
|
|
for (const uuid of uuids) {
|
||
|
|
if (pageSet.has(uuid)) continue;
|
||
|
|
template = template.split(uuid).join(blobUrls[uuid]);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Strip integrity + crossorigin — blob URLs from a file:// document inherit
|
||
|
|
// a null origin, so crossorigin forces a CORS fetch that SRI then rejects.
|
||
|
|
// The manifest bytes are ours; SRI protects against CDN compromise, not this.
|
||
|
|
template = template.replace(/\s+integrity="[^"]*"/gi, '').replace(/\s+crossorigin="[^"]*"/gi, '');
|
||
|
|
|
||
|
|
const resourceScript = '<script>window.__resources = ' +
|
||
|
|
JSON.stringify(resourceMap).replace(/<\//g, '<\\/') +
|
||
|
|
';</' + 'script>';
|
||
|
|
// Inject after <head> so the DOCTYPE stays first; prepending the script
|
||
|
|
// would push the parser into quirks mode. DOMParser always emits a <head>
|
||
|
|
// (synthesizing one if the source HTML omitted it) but may carry
|
||
|
|
// attributes through, so match the full opening tag. slice() rather than
|
||
|
|
// replace() keeps us clear of $-pattern substitution in resourceScript.
|
||
|
|
const headOpen = template.match(/<head[^>]*>/i);
|
||
|
|
if (headOpen) {
|
||
|
|
const i = headOpen.index + headOpen[0].length;
|
||
|
|
template = template.slice(0, i) + resourceScript + template.slice(i);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Parse the template and swap the root element. Scripts inserted via
|
||
|
|
// DOMParser/replaceWith are inert per spec — re-create each with
|
||
|
|
// createElement so they execute, awaiting onload for src scripts to
|
||
|
|
// preserve ordering (React before ReactDOM before Babel before text/babel).
|
||
|
|
const doc = new DOMParser().parseFromString(template, 'text/html');
|
||
|
|
document.documentElement.replaceWith(doc.documentElement);
|
||
|
|
|
||
|
|
// Resolve page-frame markers: mint this document's own blob for each
|
||
|
|
// framed page — from local text when this is the root, otherwise by
|
||
|
|
// asking the direct parent over the relay protocol above. An
|
||
|
|
// unanswered request leaves the frame on about:blank, the same
|
||
|
|
// degraded state as a refused embed.
|
||
|
|
{
|
||
|
|
const MARKER = /^about:blank#([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(#.*)?$/;
|
||
|
|
const needed = [];
|
||
|
|
for (const el of Array.from(document.querySelectorAll('iframe'))) {
|
||
|
|
const m = MARKER.exec(el.getAttribute('src') || '');
|
||
|
|
if (!m) continue;
|
||
|
|
const text = pageTexts[m[1]];
|
||
|
|
if (typeof text === 'string') {
|
||
|
|
mountPage(el, m[2] || '', text);
|
||
|
|
} else if (window.parent !== window && !isPageRoot) {
|
||
|
|
const had = pendingFrames[m[1]];
|
||
|
|
(pendingFrames[m[1]] = had || []).push({ el: el, frag: m[2] || '' });
|
||
|
|
if (!had) needed.push(m[1]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for (const uuid of needed) {
|
||
|
|
window.parent.postMessage({ __bundler_need: uuid }, OWN_TARGET);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const dead = Array.from(document.scripts);
|
||
|
|
for (const old of dead) {
|
||
|
|
const s = document.createElement('script');
|
||
|
|
for (const a of old.attributes) s.setAttribute(a.name, a.value);
|
||
|
|
s.textContent = old.textContent;
|
||
|
|
// text/babel scripts with a src: read and inline. transformScriptTags
|
||
|
|
// does XHR against the src, but blob:null/ from a file:// origin is
|
||
|
|
// silently dropped. Inlining makes it a plain inline babel script,
|
||
|
|
// which transformScriptTags handles unconditionally.
|
||
|
|
if ((s.type === 'text/babel' || s.type === 'text/jsx') && s.src) {
|
||
|
|
const pre = resourceBlobs[s.src.split('#')[0]];
|
||
|
|
s.textContent = pre ? await pre.text() : await (await fetch(s.src)).text();
|
||
|
|
s.removeAttribute('src');
|
||
|
|
}
|
||
|
|
const p = s.src ? new Promise(function(r) { s.onload = s.onerror = r; }) : null;
|
||
|
|
old.replaceWith(s);
|
||
|
|
if (p) await p;
|
||
|
|
}
|
||
|
|
// Babel standalone auto-transforms type=text/babel on DOMContentLoaded,
|
||
|
|
// which fired before we swapped the document. Trigger manually if present.
|
||
|
|
if (window.Babel && typeof window.Babel.transformScriptTags === 'function') {
|
||
|
|
window.Babel.transformScriptTags();
|
||
|
|
}
|
||
|
|
} catch (err) {
|
||
|
|
setStatus('Error unpacking: ' + err.message);
|
||
|
|
console.error('Bundle unpack error:', err);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<script type="__bundler/manifest">
|
||
|
|
{"723bf167-d3b9-44f3-ad11-ab2224e60afd":{"mime":"application/javascript","compressed":true,"data":"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
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<script type="__bundler/ext_resources">
|
||
|
|
[{"id":"https://unpkg.com/react@18.3.1/umd/react.production.min.js","uuid":"399d0226-5d97-4ce7-9356-2c8292f30acc"},{"id":"https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js","uuid":"75a821eb-1d88-477e-be9c-b5bcb35d458c"}]
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<script type="__bundler/page_order">
|
||
|
|
[]
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<script type="__bundler/template">
|
||
|
|
"<!DOCTYPE html>\n<html><head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<script src=\"bc1e6809-d059-4c97-a856-4fc54536960c\"><\u002Fscript>\n<\u002Fhead>\n<body>\n\n<x-dc>\n<helmet><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><link rel=\"preconnect\" href=\"https://fonts.googleapis.com\"><style>/* cyrillic-ext */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(\"b001cfb2-efa5-4eb0-b9db-0d034b17d7a2\") format('woff2');\n unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;\n}\n/* cyrillic */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(\"e3840408-9f11-434e-b639-1bdd07ce5f04\") format('woff2');\n unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(\"3c8bdbd0-9a3e-4e93-b98b-fd5534aefc92\") format('woff2');\n unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;\n}\n/* vietnamese */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(\"90fd077f-92c6-4381-b3dc-f085d2dfa575\") format('woff2');\n unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(\"20c7bc0a-e082-484d-b41d-b58be8815639\") format('woff2');\n unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(\"b4f4838b-fbdb-4531-9ef3-3b8094a3b4b9\") format('woff2');\n unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;\n}\n/* cyrillic-ext */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(\"b001cfb2-efa5-4eb0-b9db-0d034b17d7a2\") format('woff2');\n unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;\n}\n/* cyrillic */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(\"e3840408-9f11-434e-b639-1bdd07ce5f04\") format('woff2');\n unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(\"3c8bdbd0-9a3e-4e93-b98b-fd5534aefc92\") format('woff2');\n unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;\n}\n/* vietnamese */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(\"90fd077f-92c6-4381-b3dc-f085d2dfa575\") format('woff2');\n unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(\"20c7bc0a-e082-484d-b41d-b58be8815639\") format('woff2');\n unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7
|
||
|
|
</script>
|
||
|
|
</body>
|
||
|
|
</html>
|