diff --git a/apps/web/scripts/render-share-image.mjs b/apps/web/scripts/render-share-image.mjs index d5c2abc..9515d49 100644 --- a/apps/web/scripts/render-share-image.mjs +++ b/apps/web/scripts/render-share-image.mjs @@ -1,6 +1,8 @@ // Renders the share images (Open Graph and Twitter) from the real hero, one per -// language, so they always match the site. Run it after the hero headline or its -// design changes: +// language, so they always match the site. The hero is shown the way the dark +// sections of the site look: light type on a dark surface, a lit edge, and +// binary digits coming off it. Run it after the hero headline or its design +// changes: // // pnpm --filter @mintel/web share-image (uses the live site) // pnpm --filter @mintel/web share-image http://localhost:3000 @@ -37,7 +39,9 @@ for (const shot of SHOTS) { main > div > section:first-of-type .grid { margin-top: 0 !important; display: block !important; } main > div > section:first-of-type h1 span:nth-of-type(2) { color: rgba(15, 23, 42, 0.2) !important; } main > div > section:first-of-type > div.relative { padding-top: 34px !important; } - html, body { overflow: hidden !important; } + html, body { overflow: hidden !important; background: #000 !important; } + /* Dark version of the hero: inverted, with the hues turned back. */ + main > div > section:first-of-type { filter: invert(1) hue-rotate(180deg) !important; } `, }); // Let the page finish starting up first; it would discard the added labels. @@ -54,6 +58,66 @@ for (const shot of SHOTS) { "position:absolute;right:72px;top:54px;z-index:20;font:500 15px ui-monospace,Menlo,monospace;letter-spacing:.2em;color:#64748b"; section.style.position = "relative"; section.append(tag, site); + + // Everything below lies over the inverted hero and is not inverted itself. + const W = 1200; + const H = 630; + const layer = (css) => { + const el = document.createElement("div"); + el.style.cssText = `position:absolute;left:0;top:0;width:${W}px;height:${H}px;z-index:60;pointer-events:none;${css}`; + document.body.append(el); + return el; + }; + document.body.style.position = "relative"; + // Black becomes the slate of the site's dark sections; light type stays light. + layer("background:#0f172a;mix-blend-mode:screen"); + // Light coming from the lower right corner. + layer( + "mix-blend-mode:screen;background:radial-gradient(58% 62% at 92% 108%, rgba(99,102,241,.85), transparent 70%),radial-gradient(34% 40% at 104% -6%, rgba(129,140,248,.4), transparent 70%)", + ); + // A frame whose lower right edge is lit, with a soft glow behind it. + const INSET = 22; + const edge = (blur, width, opacity) => { + const el = layer( + `left:${INSET}px;top:${INSET}px;width:${W - 2 * INSET}px;height:${H - 2 * INSET}px;box-sizing:border-box;border-radius:30px;padding:${width}px;opacity:${opacity};filter:blur(${blur}px);` + + "background:conic-gradient(from 60deg at 50% 50%, transparent 0 8%, rgba(129,140,248,.95) 22%, #fff 30%, rgba(129,140,248,.95) 40%, transparent 56% 100%);" + + "-webkit-mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask:linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0)", + ); + return el; + }; + layer( + `left:${INSET}px;top:${INSET}px;width:${W - 2 * INSET}px;height:${H - 2 * INSET}px;box-sizing:border-box;border-radius:30px;border:1px solid rgba(165,180,252,.16)`, + ); + edge(26, 14, 0.9); + edge(8, 5, 0.9); + edge(0, 2, 1); + + // Digits coming off the lit edge. Seeded, so the picture is the same on every run. + let seed = 20261004; + const random = () => { + seed = (seed * 1664525 + 1013904223) % 4294967296; + return seed / 4294967296; + }; + const digits = layer( + "font:500 15px var(--font-jetbrains-mono), ui-monospace, Menlo, monospace;color:#c7d2fe", + ); + for (let i = 0; i < 150; i++) { + // Along the bottom edge (right half) and up the right edge (lower half). + const alongBottom = random() < 0.62; + const spread = Math.pow(random(), 1.8); + const x = alongBottom ? W * 0.42 + random() * (W * 0.58 - INSET - 30) : 0; + // Under the lead text the digits stay close to the edge. + const reach = spread * (alongBottom && x < 820 ? 54 : 120); + const left = alongBottom ? x : W - INSET - 14 - reach; + const y = alongBottom + ? H - INSET - 16 - reach + : H * 0.3 + random() * (H * 0.7 - INSET - 30); + const digit = document.createElement("span"); + digit.textContent = random() < 0.5 ? "0" : "1"; + const big = random() < 0.2; + digit.style.cssText = `position:absolute;left:${left.toFixed(1)}px;top:${y.toFixed(1)}px;opacity:${((1 - spread) * (0.35 + random() * 0.65)).toFixed(2)};font-size:${big ? 19 : 14}px;font-weight:${big ? 700 : 500}`; + digits.append(digit); + } window.scrollTo(0, 0); }); await page.waitForTimeout(4500); diff --git a/apps/web/src/assets/share/share-de.png b/apps/web/src/assets/share/share-de.png index 1e8d9b9..486d759 100644 Binary files a/apps/web/src/assets/share/share-de.png and b/apps/web/src/assets/share/share-de.png differ diff --git a/apps/web/src/assets/share/share-en.png b/apps/web/src/assets/share/share-en.png index 0f3b3ca..496926c 100644 Binary files a/apps/web/src/assets/share/share-en.png and b/apps/web/src/assets/share/share-en.png differ