/* ============================================================================ 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'; *, *::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. */ [data-demo-url=''] { display: none; } /* 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); }