61 lines
4.9 KiB
HTML
61 lines
4.9 KiB
HTML
<!doctype html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<link rel="stylesheet" href="/fonts/fonts.css">
|
|
<!-- Play feature graphic, app style, 1024 x 500: logo (or the app name) on the left on a calm gradient, an upright phone
|
|
with one frame on the right. feature-app.html?lang=<id>[&v=<variant>]. Rendered by `storebound shots render` when
|
|
the screenshot style is app. Keep text out of it apart from the name: Play overlays its own UI. -->
|
|
<style>
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
html, body { width: 1024px; height: 500px; overflow: hidden; background: #fff; }
|
|
#c { position: relative; width: 1024px; height: 500px; overflow: hidden; isolation: isolate; background: var(--bg); }
|
|
.soft { position: absolute; left: 760px; top: 260px; width: 700px; height: 600px; transform: translate(-50%, -50%); z-index: -1;
|
|
border-radius: 50%; background: radial-gradient(closest-side, var(--accentSoft), transparent); }
|
|
.logo { position: absolute; left: 330px; top: 250px; width: 440px; max-height: 220px; object-fit: contain; transform: translate(-50%, -50%); z-index: 5; }
|
|
.name { position: absolute; left: 330px; top: 250px; transform: translate(-50%, -50%); z-index: 5; width: 520px; text-align: center;
|
|
font-family: var(--tf); font-weight: 750; font-size: 76px; line-height: 1.02; letter-spacing: -.02em; color: var(--ink); }
|
|
.tel { position: absolute; left: 690px; top: 40px; width: 230px; aspect-ratio: var(--ar); z-index: 3; border-radius: 36px; padding: 5px;
|
|
background: #121317; box-shadow: inset 0 0 0 1px #3a3c44, 0 22px 50px var(--shadow); }
|
|
.screen { position: relative; width: 100%; height: 100%; overflow: hidden; border-radius: 31px; background: #fff; }
|
|
.screen img { position: absolute; left: 0; bottom: 0; width: 100%; height: var(--appH); object-fit: cover; object-position: top; }
|
|
.screen .bar { position: absolute; left: 0; right: 0; top: 0; height: var(--barH); background: #fff center top / 100% 60000% no-repeat; }
|
|
.island { position: absolute; top: 6px; left: 50%; transform: translateX(-50%); width: 30%; height: 19px; background: #000; border-radius: 99px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="c">
|
|
<div class="soft"></div>
|
|
<div class="tel"><div class="screen"><div class="bar" id="bar"></div><img id="f"><div class="island"></div></div></div>
|
|
</div>
|
|
<script type="module">
|
|
const q = new URLSearchParams(location.search);
|
|
const lang = q.get('lang'), V = q.get('v') ?? '';
|
|
const job = await (await fetch(`/job.json?lang=${encodeURIComponent(lang)}&feature=1&v=${encodeURIComponent(V)}`)).json();
|
|
const P = job.palette, R = document.documentElement.style, dev = job.phone;
|
|
const rgba = (hex, a) => { const m = /^#?([0-9a-f]{6})$/i.exec(hex); if (!m) return hex; const n = parseInt(m[1], 16); return `rgba(${n >> 16},${(n >> 8) & 255},${n & 255},${a})`; };
|
|
R.setProperty('--bg', P.bgCss ?? (Array.isArray(P.bg) ? `linear-gradient(135deg, ${P.bg.join(', ')})` : P.bg)); // palette.ts: stops, colour, gradient string or photo + overlay
|
|
R.setProperty('--ink', P.ink); R.setProperty('--shadow', P.shadow); R.setProperty('--accentSoft', rgba(P.glow, .18));
|
|
R.setProperty('--tf', (job.fonts.titleStack ?? [job.fonts.title]).map(f => `'${f}'`).join(', ') + ', sans-serif');
|
|
R.setProperty('--ar', `${dev.w} / ${dev.h + dev.bar}`);
|
|
R.setProperty('--appH', `${100 * dev.h / (dev.h + dev.bar)}%`);
|
|
R.setProperty('--barH', `${100 * dev.bar / (dev.h + dev.bar)}%`);
|
|
const c = document.getElementById('c');
|
|
// device place and size from featureLayout() (lib.ts): fit 'contain' keeps the rotated phone inside the canvas
|
|
const D = job.device, tel = c.querySelector('.tel');
|
|
if (D) Object.assign(tel.style, { left: D.left + 'px', top: D.top + 'px', width: D.width + 'px', transform: `rotate(${D.angle}deg)` });
|
|
const src = `/frames/${encodeURIComponent(lang)}/phone/${encodeURIComponent(job.frame)}`;
|
|
document.getElementById('f').src = src;
|
|
document.getElementById('bar').style.backgroundImage = `url('${src}')`;
|
|
c.insertAdjacentHTML('beforeend', job.logo ? '<img class="logo" src="/logo">' : `<div class="name">${String(job.appName).replace(/</g, '<')}</div>`);
|
|
await document.fonts.ready;
|
|
await Promise.all([...document.images].map(i => i.decode().catch(() => {})));
|
|
// read by `shots render` -> report.json -> shots check: the phone's rotated outline must stay inside the canvas
|
|
document.body.dataset.devices = JSON.stringify([...c.querySelectorAll('.tel')].map(t => { const r = t.getBoundingClientRect(); return { x: r.x, y: r.y, w: r.width, h: r.height }; }));
|
|
if (D?.fit === 'bleed') document.body.dataset.bleed = '1';
|
|
// logo / name box for the safe-margin rule (7 % of the width left / right, 5 % of the height at the top)
|
|
document.body.dataset.boxes = JSON.stringify([...c.querySelectorAll('.logo, .name')].map(e => { const r = e.getBoundingClientRect(); return { el: e.matches('.logo') ? 'logo' : 'title', x: r.x, y: r.y, w: r.width, h: r.height }; }));
|
|
document.body.dataset.ready = '1';
|
|
</script>
|
|
</body>
|
|
</html>
|