Design Entwurf

This commit is contained in:
lujedlitschka committed 2026-10-21 23:55:39 +02:00
1 parent 6ee33eb085
commit 57700d39a4
1 file changed
+491
+491
View File
@@ -0,0 +1,491 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!--
Some of the files packed into this page (the "__bundler/manifest" data in this file, unpacked by the page’s script when it opens) are font files downloaded unchanged from Google Fonts (fonts.gstatic.com) when the page was saved. Google Fonts serves each family in pieces, one file per range of characters, so these may not be complete families. Each family’s license follows and still applies to its files. If you pass this file on, keep this comment in it.
Bricolage Grotesque
Copyright 2022 The Bricolage Grotesque Project Authors (https://github.com/ateliertriay/bricolage)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
-->
<title>Seat Planner</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #faf9f5; 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: #faf9f5; 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_placeholder">Loading Seat Planner...</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.
// appifacts: only on tags now pointing at OUR blob: URLs. A reference the
// bundler left live-fetched (an absolute x-import, a runtime dependency it
// could not embed, an authored external script) keeps the pin it was
// written with: that fetch is exactly what SRI protects. (A crossorigin
// on a parent whose child carries the blob — <video crossorigin><source>
// — is left alone too; the attribute is then the author's to drop.)
template = template.replace(/<[a-z][^>]*>/gi, (tag) => /\s(?:src|href|srcset|poster|data|xlink:href)="blob:/i.test(tag) ? tag.replace(/\s+integrity="[^"]*"/gi, '').replace(/\s+crossorigin="[^"]*"/gi, '') : tag);
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">
{"623d9dbd-0285-4cd5-9fa9-7e39e9fe3ead":{"mime":"text/javascript","compressed":true,"data":"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 truncated
</script>
<script type="__bundler/ext_resources">
[{"id":"https://cdn.jsdelivr.net/npm/react@18.3.1/umd/react.production.min.js","uuid":"94948387-8113-42af-b363-de39adb241d9"},{"id":"https://cdn.jsdelivr.net/npm/react-dom@18.3.1/umd/react-dom.production.min.js","uuid":"53a8f4b8-687e-4c4c-b7bd-1695893a9361"}]
</script>
<script type="__bundler/page_order">
[]
</script>
<script type="__bundler/template">
"<!DOCTYPE html>\n<html lang=\"de\"><head>\n<meta charset=\"utf-8\">\n<title>Seat Planner<\u002Ftitle>\n<script src=\"623d9dbd-0285-4cd5-9fa9-7e39e9fe3ead\"><\u002Fscript>\n<meta name=\"hz:slide-selector\" content=\"[data-canvas-width]\"><meta name=\"hz:canvas-width\" content=\"1440\"><meta name=\"hz:canvas-height\" content=\"1100\"><\u002Fhead>\n<body>\n<x-dc>\n<helmet>\n<style>/* vietnamese */\n@font-face {\n font-family: 'Bricolage Grotesque';\n font-style: normal;\n font-weight: 500;\n font-stretch: 100%;\n font-display: swap;\n src: url(https://fonts.gstatic.com/s/bricolagegrotesque/v9/3y9K6as8bTXq_nANBjzKo3IeZx8z6up5BeSl9D4dj_x9PpZBMlGHInHWUSNIpvKxkw.woff2) 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: 'Bricolage Grotesque';\n font-style: normal;\n font-weight: 500;\n font-stretch: 100%;\n font-display: swap;\n src: url(https://fonts.gstatic.com/s/bricolagegrotesque/v9/3y9K6as8bTXq_nANBjzKo3IeZx8z6up5BeSl9D4dj_x9PpZBMlGGInHWUSNIpvKxkw.woff2) 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: 'Bricolage Grotesque';\n font-style: normal;\n font-weight: 500;\n font-stretch: 100%;\n font-display: swap;\n src: url(\"a437930f-f7a4-4559-853e-34fb6957c513\") 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/* vietnamese */\n@font-face {\n font-family: 'Bricolage Grotesque';\n font-style: normal;\n font-weight: 700;\n font-stretch: 100%;\n font-display: swap;\n src: url(https://fonts.gstatic.com/s/bricolagegrotesque/v9/3y9K6as8bTXq_nANBjzKo3IeZx8z6up5BeSl9D4dj_x9PpZBMlGHInHWUSNIpvKxkw.woff2) 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: 'Bricolage Grotesque';\n font-style: normal;\n font-weight: 700;\n font-stretch: 100%;\n font-display: swap;\n src: url(https://fonts.gstatic.com/s/bricolagegrotesque/v9/3y9K6as8bTXq_nANBjzKo3IeZx8z6up5BeSl9D4dj_x9PpZBMlGGInHWUSNIpvKxkw.woff2) 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: 'Bricolage Grotesque';\n font-style: normal;\n font-weight: 700;\n font-stretch: 100%;\n font-display: swap;\n src: url(\"a437930f-f7a4-4559-853e-34fb6957c513\") 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: 'Cormorant Garamond';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(https://fonts.gstatic.com/s/cormorantgaramond/v21/co3bmX5slCNuHLi8bLeY9MK7whWMhyjYpHtKgS4.woff2) 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: 'Cormorant Garamond';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(https://fonts.gstatic.com/s/cormorantgaramond/v21/co3bmX5slCNuHLi8bLeY9MK7whWMhyjYrXtKgS4.woff2) format('woff2');\n unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* vietnamese */\n@font-face {\n font-family: 'Cormorant Garamond';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(https://fonts.gstatic.com/s/cormorantgaramond/v21/co3bmX5slCNuHLi8bLeY9MK7whWMhyjYpntKgS4.woff2) 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: 'Cormorant Garamond';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(https://fonts.gstatic.com/s/cormorantgaramond/v21/co3bmX5slCNuHLi8bLeY9MK7whWMhyjYp3tKgS4.woff2) 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: 'Cormorant Garamond';\n font-style: normal;\n font-weight: 500;\n font-display: sLine truncated
</script>
</body>
</html>