Files
runicgateway.com/src/components/home/Hero.astro
wtclaude 9938e71e07
All checks were successful
PR checks / checks (pull_request) Successful in 8m25s
feat(facts): phase 13 - per-game facts, Rust and RunicNPC checked, main green again
platform.json drops the single protocol/bundle/capability list for a
`games` object (uo, rust) plus a `runicnpc` block. checkFacts.mjs loops
the protocol, plugin-protocol, capability and bundle checks over both
games, reads the schema-2 bundles at v2/<game>/current.json, checks each
module's coreApi, and reads RunicNPC's plugin.toml. 41 facts, up from 19;
clears the two that were red on main (Module API 1.17.0, installer
v0.8.0).

D66: the hero, footer and structured data show the Module API only; each
game's protocol and bundle go on that game's pages, and the docs landing
table splits into shared versions plus one column per game.

The re-read section 1 requires found, and this fixes:
- three hard-coded "1.6.0" Module API versions in the module docs
- module-lifecycle saying modules load alphabetically (the loader now
  refuses a second module, W19), and the install pages never mentioning
  the one-module rule
- "there is one module" in five places, now two modules both built
  inside this project; the Integration Kit keeps its draft banner
- the homepage diagram's ServUO/uo-link sub-labels (D63 wording now)

notBuilt drops `second-module` (resolved by Module-Rust's release) and
rewords `multi-module`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-22 08:09:22 -05:00

184 lines
5.9 KiB
Plaintext

---
import { brand } from '../../lib/brand.mjs';
import platform from '../../data/platform.json';
/**
* The hero (PLAN.md §13 phase 3).
*
* The org lead chose an emblem hero over a type-only one: the mark carries recognition
* across the site, the Android launcher icon and the Play listing, and showing it large is
* what makes those three read as one product (D11, §11).
*
* It costs what D16 already accepted — the emblem is raster illustration, so a mounted
* `theme.css` recolours everything around it and not the mark itself. Replacing the mark
* means replacing `logo.png`, and because every size here is derived on request from
* whichever `logo.png` is in force (D14), that one file changes the hero, the header, the
* tab icon and the installed app icon together.
*
* The glow behind it is drawn in CSS from the portal tokens, so it DOES follow a mounted
* theme. That is deliberate: the part that can track the operator's palette does.
*
* The <h1> is the tagline rather than the product name. The name is in the header, in the
* page title and in the footer; a visitor who has just arrived needs the sentence more than
* the noun. Both strings are brand fields, rewritten at boot by `applyBrand.mjs` (D15).
*/
---
<section class="hero">
<div class="page hero__inner">
<div class="hero__copy">
<p class="eyebrow">Self-hosted community platform</p>
<h1>{brand.tagline}</h1>
<p class="hero__lede">
{brand.siteName} is a community website for a game server — accounts, teams, forums, a
wiki, news and a full admin panel — with a one-way bridge that puts the server's live
world on the public site. The game itself never listens on the internet.
</p>
<div class="hero__actions">
<a class="btn btn--primary" href="/docs/">Install it</a>
<a class="btn btn--ghost" href="/features/">See what it does</a>
{/*
The demo slot (§15 / D12). `global.css` hides `[data-demo-url='']`, so a stock
build renders nothing here; `applyBrand.mjs` fills both attributes at boot when a
mounted `brand.json` sets `demoUrl`, and the link appears.
The attribute pair is a literal contract with that script — `href` immediately
followed by `data-demo-url`, both empty, in this order. Astro preserves attribute
order, so what is written here is what ends up in the HTML it searches for. Do not
insert an attribute between them.
*/}
<a class="btn demo-cta" href="" data-demo-url="">See it running</a>
</div>
<div class="chips">
{/* D66: only the version every deployment shares. A protocol and a bundle belong
to a game, so they live on that game's pages rather than on the front door. */}
<span class="chip chip--version">Module API {platform.moduleApi}</span>
</div>
</div>
<div class="hero__mark">
{/*
`alt=""` because the emblem is the product's mark sitting beside the product's own
sentence — announcing it would add nothing a reader of the <h1> does not have.
Sizes are on `brandAssets.mjs`'s allowlist; `checkBrand.mjs` puts every URL below
through the route's own classifier, so a plausible-but-underivable size fails the
build rather than 404ing in production.
*/}
<picture>
<source
type="image/avif"
srcset="/brand/logo-256.avif 256w, /brand/logo-384.avif 384w, /brand/logo-512.avif 512w"
sizes="(max-width: 900px) 176px, 320px"
/>
<img
src="/brand/logo-384.webp"
srcset="/brand/logo-256.webp 256w, /brand/logo-384.webp 384w, /brand/logo-512.webp 512w"
sizes="(max-width: 900px) 176px, 320px"
width="384"
height="384"
alt=""
fetchpriority="high"
decoding="async"
/>
</picture>
</div>
</div>
</section>
<style>
.hero {
position: relative;
overflow: hidden;
padding-block: clamp(2.5rem, 7vw, 5rem) clamp(2rem, 5vw, 3.5rem);
}
.hero__inner {
display: grid;
align-items: center;
gap: clamp(1.5rem, 5vw, 3.5rem);
grid-template-columns: minmax(0, 1fr) auto;
}
.hero__copy {
max-width: 40rem;
}
.hero h1 {
margin: 0;
color: var(--gold);
font-size: clamp(2.1rem, 5.2vw, 3.35rem);
}
.hero__lede {
margin: 1.15rem 0 0;
max-width: var(--measure);
color: var(--muted);
font-size: clamp(1rem, 1.6vw, 1.13rem);
}
.hero__actions {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin-top: 1.9rem;
}
.hero .chips {
margin-top: 1.75rem;
}
/* ---- The mark ---------------------------------------------------------
The glow is a radial gradient mixed from the portal tokens rather than a
literal, so a mounted theme.css moves it with the rest of the palette.
It is behind the emblem and outside the flow, so it costs no layout. */
.hero__mark {
position: relative;
display: grid;
place-items: center;
}
.hero__mark::before {
content: '';
position: absolute;
z-index: 0;
inset: 50% auto auto 50%;
translate: -50% -50%;
width: 150%;
aspect-ratio: 1;
border-radius: var(--radius-pill);
background: radial-gradient(
circle,
color-mix(in srgb, var(--portal-deep) 34%, transparent) 0%,
color-mix(in srgb, var(--portal-deep) 8%, transparent) 45%,
transparent 68%
);
}
.hero__mark img {
position: relative;
z-index: 1;
display: block;
width: clamp(176px, 26vw, 320px);
height: auto;
}
@media (max-width: 900px) {
.hero__inner {
grid-template-columns: minmax(0, 1fr);
justify-items: start;
}
/* The mark leads on a narrow screen: it is the fastest thing to recognise,
and stacking it under the copy would push it below the fold entirely. */
.hero__mark {
order: -1;
}
}
</style>