/* ============================================================================ runicgateway.com — the layout shell ============================================================================ Every value here is a token from tokens.css. No colour literal appears below this comment; `scripts/checkTokens.mjs` fails the build if one does. -------------------------------------------------------------------------- */ @import '@fontsource-variable/cinzel'; @import '@fontsource-variable/inter'; /* The SVG diagram vocabulary and the figure-beside-prose layout, shared by the homepage's data path and `/architecture/`'s three. Its own file because it is a self-contained language rather than part of the shell — see its header for the two rules every diagram on this site follows. */ @import './diagram.css'; *, *::before, *::after { box-sizing: border-box; } html { scroll-behavior: smooth; } @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; } } body { margin: 0; background: var(--bg); color: var(--text); font-family: var(--sans); font-size: 1rem; line-height: 1.65; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; } h1, h2, h3, h4 { color: var(--head); line-height: 1.2; text-wrap: balance; } h1 { font-family: var(--display); font-weight: 600; letter-spacing: 0.01em; } p { text-wrap: pretty; } a { color: var(--accent); text-decoration-color: color-mix(in srgb, var(--accent) 40%, transparent); text-underline-offset: 0.18em; } a:hover { color: var(--accent-bright); text-decoration-color: currentColor; } :focus-visible { outline: 2px solid var(--portal-bright); outline-offset: 3px; border-radius: var(--radius-input); } code, pre, kbd, samp { font-family: var(--mono); font-size: 0.9em; } hr { border: 0; border-top: 1px solid var(--line-soft); margin: 2.5rem 0; } img, svg { max-width: 100%; height: auto; } /* ---- Page frame --------------------------------------------------------- */ .page { width: 100%; max-width: var(--page-max); margin-inline: auto; padding-inline: var(--gutter); } .site { display: flex; min-height: 100vh; flex-direction: column; } .site > main { flex: 1; } /* The keyboard escape hatch past the header nav. Visible only when focused. */ .skip-link { position: absolute; left: var(--gutter); top: 0; z-index: 100; transform: translateY(-140%); padding: 0.6rem 1rem; border: 1px solid var(--gold-deep); border-radius: var(--radius-input); background: var(--panel-flat); color: var(--ink); font-size: 0.9rem; text-decoration: none; transition: transform 0.15s ease; } .skip-link:focus { transform: translateY(12px); } /* ---- Header ------------------------------------------------------------- */ .site-header { position: sticky; top: 0; z-index: 50; min-height: var(--header-h); border-bottom: 1px solid var(--line-soft); background: color-mix(in srgb, var(--bg-deep) 88%, transparent); backdrop-filter: blur(10px); } .site-header__inner { display: flex; min-height: var(--header-h); align-items: center; justify-content: space-between; gap: var(--gutter); } .brand-lockup { display: inline-flex; align-items: center; gap: 0.65rem; color: var(--ink); text-decoration: none; } .brand-lockup__mark { width: 40px; height: 40px; flex: none; } .brand-lockup__name { font-family: var(--display); font-size: 1.12rem; font-weight: 600; letter-spacing: 0.04em; color: var(--gold); } .site-nav { display: flex; align-items: center; gap: 0.25rem; } .site-nav a { padding: 0.45rem 0.7rem; border-radius: var(--radius-input); color: var(--muted); font-size: 0.94rem; text-decoration: none; } .site-nav a:hover { background: var(--panel-flat); color: var(--ink); } .site-nav a[aria-current='page'] { color: var(--gold); } /* Phase 1 left the mobile nav to phase 3, expecting a disclosure control. It got a wrap instead, and deliberately: with four links there is nothing to disclose. The lockup keeps the first row, the links take the second, and the whole thing stays four keyboard stops with no state, no script and no duplicate markup — all three of which a hamburger would have cost. Phase 3 found the bug this fixes by rendering the homepage in a 390px frame: the four links plus the lockup measured 433px against a 390px viewport, so every phone got a horizontally scrolling page. Shrinking the type further was the tempting fix and would only have moved the failure to the next narrow screen. */ @media (max-width: 720px) { .site-header__inner { flex-wrap: wrap; justify-content: flex-start; row-gap: 0.15rem; padding-block: 0.55rem; min-height: 0; } .site-nav { flex-wrap: wrap; width: 100%; gap: 0; /* Pull the first link's own padding back to the gutter so the row of links lines up with the lockup above it rather than sitting indented. */ margin-left: -0.45rem; } .site-nav a { padding-inline: 0.45rem; font-size: 0.86rem; } } /* ---- Footer ------------------------------------------------------------- */ .site-footer { margin-top: 4rem; border-top: 1px solid var(--line-soft); background: var(--bg-deep); padding-block: 2.5rem 2rem; color: var(--dim); font-size: 0.9rem; } .site-footer__cols { display: grid; gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); } .site-footer h2 { margin: 0 0 0.7rem; color: var(--muted); font-family: var(--sans); font-size: 0.76rem; font-weight: 700; letter-spacing: 0.11em; text-transform: uppercase; } .site-footer ul { margin: 0; padding: 0; list-style: none; } .site-footer li + li { margin-top: 0.4rem; } .site-footer a { color: var(--muted); text-decoration: none; } .site-footer a:hover { color: var(--accent-bright); text-decoration: underline; } .site-footer__legal { margin-top: 2.25rem; padding-top: 1.25rem; border-top: 1px solid var(--line-soft); display: flex; flex-wrap: wrap; gap: 0.5rem 1.25rem; align-items: baseline; justify-content: space-between; } .site-footer__legal p { margin: 0; } /* ---- Panels ------------------------------------------------------------- */ .panel { border: 1px solid var(--line); border-radius: var(--radius-panel); background: var(--panel-grad); box-shadow: var(--shadow-card); padding: 1.5rem; } .eyebrow { margin: 0 0 0.75rem; color: var(--gold); font-size: 0.74rem; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase; } .prose { max-width: var(--measure); } /* ---- Status chip -------------------------------------------------------- */ .chip { display: inline-flex; align-items: center; gap: 0.4rem; padding: 0.15rem 0.6rem; border: 1px solid var(--line); border-radius: var(--radius-pill); background: var(--panel-flat); color: var(--muted); font-size: 0.76rem; letter-spacing: 0.04em; white-space: nowrap; } .chip--version { border-color: color-mix(in srgb, var(--gold-deep) 70%, transparent); color: var(--gold); } .chip--draft { border-color: color-mix(in srgb, var(--mode-maint) 55%, transparent); color: var(--mode-maint); } .chip--live { border-color: color-mix(in srgb, var(--mode-live) 55%, transparent); color: var(--mode-live); } /* ---- Sections ------------------------------------------------------------ The vertical rhythm every marketing page is built from. Here rather than in a component because phases 4 to 6 add pages that must sit on the same grid, and a per-page `padding-block` is how that stops being true. */ .section { padding-block: clamp(2.5rem, 6vw, 4.5rem); } .section + .section { padding-top: 0; } /* ---- Buttons ------------------------------------------------------------- Three variants, all the same box: solid for the one action a page wants, outlined for the alternatives, and the demo's own below. Anchors, not buttons — every one of them navigates, and the site runs no client JS. */ .btn { display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem; padding: 0.62rem 1.15rem; border: 1px solid var(--line); border-radius: var(--radius-input); background: var(--panel-flat); color: var(--ink); font-size: 0.96rem; font-weight: 500; line-height: 1.3; text-decoration: none; transition: border-color 0.15s ease, background-color 0.15s ease, color 0.15s ease; } .btn:hover { border-color: var(--gold-deep); color: var(--ink); } /* The primary action reads as gold-on-dark rather than a filled gold slab: at 7.91:1 the token is a text colour, and `--gold-deep` is explicitly annotated "rules, borders, UI edges. NEVER text." — so it carries the edge and the wash, and the gold carries the label. */ .btn--primary { border-color: var(--gold); background: color-mix(in srgb, var(--gold-deep) 18%, transparent); color: var(--gold-bright); } .btn--primary:hover { background: color-mix(in srgb, var(--gold-deep) 30%, transparent); color: var(--gold-bright); } .btn--ghost { background: transparent; color: var(--muted); } .btn--ghost:hover { color: var(--ink); } /* ---- The demo slot ------------------------------------------------------- PLAN.md §15 / D12. A public demo instance is planned and out of scope, but the site is built so that gaining one is a line in the mounted brand.json rather than a rebuild — the same class of change as swapping a logo (§7). Anything carrying `data-demo-url` is hidden while that attribute is empty, which is the state a stock build ships in. `scripts/applyBrand.mjs` fills both the attribute and the adjacent empty `href` at boot when the mount sets `demoUrl`, and the element appears. The markup contract is: See it running Written here, before phase 3 writes that markup, because the rule and the rewrite have to agree and they live in different files. */ /* `!important`, and it is earning its keep rather than papering over something. This selector is specificity 0,1,0. So is a class — including the scoped class an Astro component puts on the very same element — and a component's styles are emitted AFTER this file, so any component that gives one of these elements a `display` wins on source order alone. Phase 4 did exactly that: `/features/`'s `.demo-link` set `display: inline-flex` for its arrow, and twelve links to a demo that does not exist appeared on the page, each one pointing at `href=""` — which a browser resolves to the page it is already on. Nothing caught it. checkBrand.mjs verifies the ATTRIBUTES, and they were perfect; the defect was three files away in the cascade. It was found by looking at the rendered page, which is not a mechanism. So the rule is stated as one: while there is no demo, these elements do not render, and no component style may overrule that by accident. A component that genuinely needs to lay one of these out sets every property except `display`. */ [data-demo-url=''] { display: none !important; } /* Phase 3 writes that markup as `class="btn demo-cta"`, so the slot is a button like its neighbours and takes the portal colour — the live signal, for the one link on the site that leads to something actually running. */ .demo-cta { border-color: var(--portal); background: color-mix(in srgb, var(--portal-deep) 22%, transparent); color: var(--portal-bright); } .demo-cta:hover { border-color: var(--portal-bright); background: color-mix(in srgb, var(--portal-deep) 34%, transparent); color: var(--portal-bright); }