df9641cddd
Both cards re-rendered off the current engine, and the office card's own copy fixed at its source: `og.html` said "one floor plan" while the artwork behind it showed two storeys held apart around a commons. The meta description in `vite.config.ts` and the words baked into the picture are two different strings and only one of them had been corrected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
219 lines
7.8 KiB
HTML
219 lines
7.8 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<title>Tera / Spaces — share cards</title>
|
|
<!--
|
|
One template, two doors, for the same reason the app is one bundle behind
|
|
two names: `?card=tera` and `?card=office` differ in four strings and one
|
|
background, and two files would drift the moment somebody adjusted the
|
|
scrim on one of them.
|
|
|
|
No webfonts. The card is rasterised by a headless browser on a box that may
|
|
or may not have network at the time, and a card whose type silently falls
|
|
back to Times is worse than one that never used a webfont — so this uses
|
|
the same system stack the app itself uses (`ui-monospace` throughout, with
|
|
Georgia for the headline, which is the face `index.html`'s own no-JS
|
|
fallback already sets).
|
|
|
|
1200x630 is the size every unfurler crops toward: Twitter/X's
|
|
`summary_large_image`, Open Graph's own recommendation, Slack, Discord,
|
|
iMessage, LinkedIn. Nothing important goes within 48px of an edge, because
|
|
several of them shave a few pixels and one of them rounds the corners.
|
|
-->
|
|
<style>
|
|
html, body { margin: 0; padding: 0; background: #06080b; }
|
|
|
|
.card {
|
|
position: relative;
|
|
width: 1200px;
|
|
height: 630px;
|
|
overflow: hidden;
|
|
box-sizing: border-box;
|
|
background: #06080b;
|
|
color: rgba(255, 255, 255, 0.92);
|
|
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
}
|
|
|
|
/* The art is the product, shot from the running app — see `capture.mjs`.
|
|
`cover` plus a per-card origin, because the two shots want cropping
|
|
toward different halves of themselves. */
|
|
.art {
|
|
position: absolute;
|
|
inset: 0;
|
|
background-repeat: no-repeat;
|
|
background-size: cover;
|
|
}
|
|
|
|
/*
|
|
Two scrims, not one, and they do different jobs.
|
|
|
|
The horizontal one buys contrast for the type: the left 58% goes to
|
|
near-solid so a white headline sits on a known background rather than on
|
|
whatever pixel the render happened to put there — which is the failure
|
|
that makes screenshot-backed cards unreadable at timeline size. The
|
|
vertical one is a vignette that keeps the top and bottom edges from
|
|
competing with the corner text, and it is much weaker, because the whole
|
|
point is that you can still see the place.
|
|
*/
|
|
.scrim {
|
|
position: absolute;
|
|
inset: 0;
|
|
background:
|
|
linear-gradient(90deg, rgba(6, 8, 11, 0.97) 0%, rgba(6, 8, 11, 0.9) 38%,
|
|
rgba(6, 8, 11, 0.45) 62%, rgba(6, 8, 11, 0.12) 100%),
|
|
linear-gradient(180deg, rgba(6, 8, 11, 0.45) 0%, rgba(6, 8, 11, 0) 30%,
|
|
rgba(6, 8, 11, 0) 68%, rgba(6, 8, 11, 0.6) 100%);
|
|
}
|
|
|
|
/* The app's own hairline grid, at the app's own opacity. It is the one
|
|
piece of furniture both this and the lumbridgecorp.com card share, so
|
|
the two read as a family without being the same layout. */
|
|
.grid {
|
|
position: absolute;
|
|
inset: 0;
|
|
background-image:
|
|
linear-gradient(rgba(255, 255, 255, 0.04) 1px, transparent 1px),
|
|
linear-gradient(90deg, rgba(255, 255, 255, 0.04) 1px, transparent 1px);
|
|
background-size: 48px 48px;
|
|
}
|
|
|
|
.body {
|
|
position: absolute;
|
|
inset: 0;
|
|
padding: 64px 72px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.eyebrow {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
font-size: 17px;
|
|
letter-spacing: 0.22em;
|
|
text-transform: uppercase;
|
|
color: #f2b134;
|
|
margin: 0 0 28px;
|
|
}
|
|
/* The same coin the lumbridgecorp.com card leads with. */
|
|
.coin {
|
|
width: 15px;
|
|
height: 15px;
|
|
border-radius: 50%;
|
|
background: radial-gradient(circle at 34% 30%, #ffe8ac, #f2b134 48%, #d9932b 100%);
|
|
box-shadow: 0 0 16px rgba(242, 177, 52, 0.45);
|
|
}
|
|
|
|
h1 {
|
|
font-family: Georgia, "Times New Roman", serif;
|
|
font-weight: 400;
|
|
font-size: 68px;
|
|
line-height: 1.04;
|
|
letter-spacing: -0.022em;
|
|
margin: 0 0 26px;
|
|
max-width: 15ch;
|
|
color: #f4f6f9;
|
|
}
|
|
|
|
.sub {
|
|
font-size: 21px;
|
|
line-height: 1.5;
|
|
letter-spacing: 0.005em;
|
|
color: rgba(255, 255, 255, 0.62);
|
|
margin: 0;
|
|
max-width: 30ch;
|
|
}
|
|
|
|
/* Bottom left, on the baseline the padding already establishes. The host
|
|
is the one string on the card that is not a claim about anything. */
|
|
.host {
|
|
position: absolute;
|
|
left: 72px;
|
|
bottom: 56px;
|
|
font-size: 18px;
|
|
letter-spacing: 0.14em;
|
|
color: rgba(255, 255, 255, 0.5);
|
|
margin: 0;
|
|
}
|
|
.host b { color: #ffd68a; font-weight: 400; }
|
|
|
|
.licence {
|
|
position: absolute;
|
|
right: 72px;
|
|
bottom: 56px;
|
|
font-size: 15px;
|
|
letter-spacing: 0.12em;
|
|
text-transform: uppercase;
|
|
color: rgba(255, 255, 255, 0.34);
|
|
margin: 0;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="card">
|
|
<div class="art" id="art"></div>
|
|
<div class="scrim"></div>
|
|
<div class="grid"></div>
|
|
<div class="body">
|
|
<p class="eyebrow"><span class="coin"></span><span id="eyebrow"></span></p>
|
|
<h1 id="headline"></h1>
|
|
<p class="sub" id="sub"></p>
|
|
</div>
|
|
<p class="host">lumbridgecorp.com<b id="host"></b></p>
|
|
<p class="licence">Apache-2.0</p>
|
|
</div>
|
|
|
|
<script>
|
|
/*
|
|
Every string here is the project's own.
|
|
|
|
The headline for each door is the sentence `README.md` already uses to
|
|
say what that half is, and the sub-lines describe only what the code
|
|
actually does — a sun and moon computed locally, an office that renders
|
|
with no server at all, a licence that is in the repo root. A share card
|
|
is the most-read sentence a project has and the least reviewed, which is
|
|
exactly why it should not be the one place new claims get invented.
|
|
*/
|
|
const CARDS = {
|
|
tera: {
|
|
eyebrow: "Lumbridge Simulate",
|
|
headline: "Cities from above.",
|
|
// The last sentence is CONTRACT.md §0's acceptance test, in the words
|
|
// `main.ts` already uses for it.
|
|
sub: "A real sun and moon over a board you can fly around, and an office you can step into. Clone it and it works.",
|
|
host: "tera.",
|
|
art: "art-tera.png",
|
|
// Low, so the far edge of the board — where the terrain stops and the
|
|
// sky begins in a hard diagonal — stays out of frame. A card with the
|
|
// seam in it reads as a rendering fault rather than as a map.
|
|
position: "56% 88%",
|
|
},
|
|
office: {
|
|
eyebrow: "Lumbridge Spaces",
|
|
headline: "An office you can walk around.",
|
|
sub: "Two buildings, real hours, the lights on at night — rendered from a data file anybody can copy.",
|
|
host: "office.",
|
|
art: "art-office.png",
|
|
position: "62% 46%",
|
|
},
|
|
};
|
|
|
|
const which = new URLSearchParams(location.search).get("card") ?? "tera";
|
|
const card = CARDS[which] ?? CARDS.tera;
|
|
document.getElementById("eyebrow").textContent = card.eyebrow;
|
|
document.getElementById("headline").textContent = card.headline;
|
|
document.getElementById("sub").textContent = card.sub;
|
|
document.getElementById("host").textContent = "";
|
|
document.querySelector(".host").innerHTML =
|
|
'<b>' + card.host + '</b>lumbridgecorp.com';
|
|
const art = document.getElementById("art");
|
|
art.style.backgroundImage = 'url("' + card.art + '")';
|
|
art.style.backgroundPosition = card.position;
|
|
document.title = which;
|
|
</script>
|
|
</body>
|
|
</html>
|