Files
2026-08-15 11:15:31 +02:00

771 lines
20 KiB
CSS

/* The landing page: flat Sticknife chrome framing the pantheon.
Everything is scoped to body.landing so the rest of the app keeps styles.css as it was.
The identity is the logo's construction — content banded between hard rules, sitting on a
baseline — plus one discipline: gold belongs to the art and to status, never to the chrome.
That is what lets thirteen gilt seals be the only rich thing on the page. */
body.landing {
--ink: #0c0b0a;
--wall: #141210;
--rule: #2b2622;
--rule-2: #3a342d;
--cream: #f2ece1;
--muted: #8e8579;
--dim: #5f584f;
--gold: #b5976e;
--hot: #e1812b;
--chrome: "Oswald", "Archivo Narrow", "Barlow Condensed", "Roboto Condensed",
"Helvetica Neue Condensed", "Arial Narrow", system-ui, sans-serif;
--plaque: "Cinzel", "Trajan Pro", "Palatino Linotype", Palatino, "Book Antiqua",
Georgia, serif;
--data: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
--gutter: clamp(16px, 3vw, 28px);
--col: 1180px;
/* roughly the width of the Sign in button: how far right of the content column the fixed
nav sits, so the sign-up card passes underneath it rather than through it */
--nav-clear: 104px;
background: var(--ink);
color: var(--cream);
font-family: var(--chrome);
font-stretch: 85%;
overflow-x: hidden;
}
/* a faint warm cast from above, so the black isn't flat vinyl */
body.landing::before {
content: "";
position: fixed;
inset: 0;
z-index: 0;
pointer-events: none;
background: radial-gradient(120% 70% at 50% -10%, rgba(181, 151, 110, .07), transparent 60%);
}
body.landing .page { position: relative; z-index: 1; display: block; }
body.landing .landing-shell {
width: 100%;
margin: 0;
padding: 0;
border: 0;
background: none;
box-shadow: none;
}
.sn-page {
width: min(var(--col), 100%);
margin: 0 auto;
padding: 0 var(--gutter);
}
/* The auth nav lives in the shared topbar. It used to line up with the content column's right
edge, which is exactly where the sign-up card is, so scrolling dragged the card straight
under it. Sitting it a button's width further right puts it in the margin instead — and on
viewports too narrow to have a margin to spare, the floor keeps it on the page. */
body.landing .topbar {
top: 34px;
right: max(10px, calc(50vw - var(--col) / 2 + var(--gutter) - var(--nav-clear)));
gap: 8px;
}
body.landing .nav-button {
min-height: 36px;
border: 2px solid var(--cream);
border-radius: 0;
background: transparent;
color: var(--cream);
font-family: var(--chrome);
font-size: .78rem;
font-stretch: 85%;
font-weight: 600;
letter-spacing: .11em;
text-transform: uppercase;
transition: background .16s, color .16s;
}
body.landing .nav-button:hover { background: var(--cream); color: var(--ink); }
body.landing .navlinks form { margin: 0; }
/* ---------------------------------------------------------------- brand and structure */
.sn-head { padding: 30px 0 22px; }
.sn-brand { display: inline-block; }
.sn-brand img {
display: block;
width: clamp(150px, 17vw, 200px);
height: auto;
}
.sn-baseline { height: 3px; background: var(--cream); }
.sn-hair { height: 1px; background: var(--rule); }
/* ------------------------------------------------------------------------------- gate */
.sn-gate {
display: grid;
grid-template-columns: minmax(0, 1.3fr) minmax(320px, .7fr);
gap: clamp(28px, 5vw, 60px);
align-items: start;
padding: clamp(38px, 6vw, 62px) 0 clamp(34px, 5vw, 52px);
}
.sn-eyebrow {
margin: 0 0 18px;
color: var(--muted);
font-size: .72rem;
font-weight: 600;
letter-spacing: .3em;
text-transform: uppercase;
}
body.landing .sn-gate-copy h1 {
margin: 0 0 20px;
color: var(--cream);
font-family: var(--chrome);
font-size: clamp(2.3rem, 5.4vw, 4.2rem);
font-weight: 600;
line-height: .94;
letter-spacing: .012em;
text-transform: uppercase;
text-wrap: balance;
}
body.landing .sn-lede {
max-width: 46ch;
margin: 0;
color: var(--muted);
font-family: var(--plaque);
font-size: clamp(1rem, 1.5vw, 1.12rem);
line-height: 1.6;
}
.sn-lede em { color: var(--cream); font-style: normal; }
.sn-gate-card {
padding: clamp(20px, 2.4vw, 28px);
border: 1px solid var(--rule-2);
background: var(--wall);
}
body.landing .sn-gate-card h2 {
margin: 0 0 18px;
color: var(--muted);
font-family: var(--chrome);
font-size: .74rem;
font-weight: 600;
letter-spacing: .26em;
text-transform: uppercase;
}
body.landing .sn-gate-card label {
display: grid;
gap: 7px;
color: var(--muted);
font-family: var(--chrome);
font-size: .72rem;
font-weight: 600;
letter-spacing: .16em;
text-transform: uppercase;
}
body.landing .sn-gate-card .stack { display: grid; gap: 14px; }
body.landing .sn-gate-card input {
width: 100%;
height: 42px;
padding: 0 12px;
border: 1px solid var(--rule-2);
border-radius: 0;
background: #0a0908;
color: var(--cream);
font-family: var(--data);
font-size: .92rem;
letter-spacing: 0;
text-transform: none;
}
body.landing .sn-gate-card input:focus-visible { border-color: var(--gold); }
body.landing .sn-gate-card button {
width: 100%;
min-height: 42px;
margin-top: 4px;
border: 2px solid var(--cream);
border-radius: 0;
background: var(--cream);
color: var(--ink);
font-family: var(--chrome);
font-size: .8rem;
font-weight: 600;
font-stretch: 85%;
letter-spacing: .11em;
text-transform: uppercase;
cursor: pointer;
transition: background .16s;
}
body.landing .sn-gate-card button:hover { background: #fff; }
body.landing .sn-fineprint {
margin: 16px 0 0;
color: var(--dim);
font-family: var(--data);
font-size: .74rem;
line-height: 1.55;
}
.sn-fineprint b { color: var(--muted); font-weight: 400; }
/* the global link colour belongs to the other pages' palette; at this size the underline is
doing most of the work anyway */
body.landing .sn-fineprint a {
color: var(--muted);
text-decoration: underline;
text-underline-offset: 2px;
text-decoration-color: var(--rule-2);
}
body.landing .sn-fineprint a:hover { color: var(--gold); text-decoration-color: var(--gold); }
body.landing ul.sn-fineprint {
display: grid;
gap: 9px;
padding: 0;
list-style: none; /* separated by space alone — no marker, no indent */
}
body.landing .sn-gate-card .notice {
margin-bottom: 16px;
border-radius: 0;
font-family: var(--plaque);
font-size: .88rem;
}
/* ------------------------------------------------------------------------ section band */
.sn-band {
display: flex;
align-items: baseline;
justify-content: center;
gap: 20px;
padding: 28px 0 2px;
}
.sn-band-label {
color: var(--gold);
font-family: var(--plaque);
font-size: .96rem;
letter-spacing: .34em;
text-indent: .34em; /* offset the trailing letter-space so it centres optically */
text-transform: lowercase;
}
/* ----------------------------------------------------------------------------- the field */
.sn-field {
position: relative;
width: 100vw;
margin-left: calc(50% - 50vw);
/* Taller than the ring needs. geometry() spends the surplus on margin rather than on a
bigger ring, which is what keeps the tenants pushed outside the ring — Nanshe at the top
of it especially — from running off the top edge. */
height: clamp(900px, 105vh, 1140px);
overflow: hidden;
}
.sn-graph { position: absolute; inset: 0; width: 100%; height: 100%; }
/* houses are invisible until you go looking for one */
.sn-links g { opacity: 0; transition: opacity .3s ease; }
.sn-links g.on { opacity: 1; }
.sn-links line { stroke: var(--gold); stroke-width: 1; }
/* The cluster keeps its edges drawn rather than surfacing them on hover — it is small enough
to read at a glance, and the lines are what make it hang off the one at the top. Faint,
though: they are there to be inferred, not read. */
.sn-side-graph .sn-links line { stroke-opacity: .18; transition: stroke-opacity .3s ease; }
.sn-side-graph .sn-links.on line { stroke-opacity: .5; }
.sn-node { cursor: pointer; }
.sn-node image { transition: transform .3s ease; transform-box: fill-box; transform-origin: center; }
.sn-node .sn-halo { opacity: 0; transition: opacity .3s ease; }
.sn-node.lit .sn-halo, .sn-node.sel .sn-halo { opacity: 1; }
.sn-node.lit image, .sn-node.sel image { transform: scale(1.07); }
.sn-niche-ring { fill: #100e0c; stroke: var(--rule-2); stroke-width: 1; }
.sn-monogram {
fill: var(--rule-2);
font-family: var(--plaque);
font-size: 1.5rem;
letter-spacing: .08em;
text-anchor: middle;
}
.sn-label {
fill: var(--muted);
font-family: var(--plaque);
font-size: .68rem;
letter-spacing: .17em;
text-anchor: middle;
text-transform: uppercase;
opacity: .5;
transition: opacity .25s, fill .25s;
}
.sn-node.lit .sn-label, .sn-node.sel .sn-label { fill: var(--cream); opacity: 1; }
.sn-node[data-state="planned"] .sn-label, .sn-node[data-state="machine"] .sn-label { opacity: .34; }
.sn-graph.busy .sn-node:not(.lit):not(.sel) { opacity: .26; }
.sn-node { transition: opacity .3s ease; }
/* House names are authored lowercase — that is the control string. Every place one is shown
title-cases it in CSS, so the data stays plain and the page stays capitalised. */
.sn-house-tag {
fill: var(--gold);
font-family: var(--plaque);
font-size: .68rem;
letter-spacing: .22em;
text-anchor: middle;
text-transform: capitalize;
opacity: 0;
transition: opacity .3s ease;
}
.sn-house-tag.on { opacity: 1; }
.sn-node:focus { outline: none; }
.sn-node:focus-visible .sn-focus-ring { stroke: var(--hot); stroke-width: 2; }
.sn-focus-ring { fill: none; stroke: none; }
/* ------------------------------------------------------------------- the side drawers */
/* A rail of tabs pinned to the right edge, and one parked panel per tab behind it. The tabs
live on the rail rather than on the panels so that opening one never buries another's
handle underneath it. */
.sn-drawers {
position: absolute;
inset: 0;
z-index: 6;
pointer-events: none; /* the field underneath stays reachable */
--rail-w: 36px; /* keep in step with RAIL in pantheon.js */
}
.sn-drawers[hidden] { display: none; }
.sn-rail {
position: absolute;
z-index: 2;
top: 0;
bottom: 0;
right: 0;
width: var(--rail-w);
display: flex;
flex-direction: column;
gap: 10px;
padding-top: 72px;
border-left: 1px solid var(--rule-2);
background: rgba(11, 10, 9, .95);
pointer-events: auto;
}
.sn-side-tab {
flex: none;
padding: 20px 0;
border: 0;
background: transparent;
color: var(--muted);
cursor: pointer;
font-family: var(--plaque);
font-size: .7rem;
letter-spacing: .22em;
text-transform: capitalize;
transition: color .2s;
}
.sn-side-tab span {
display: block;
writing-mode: vertical-rl;
white-space: nowrap;
}
.sn-side-tab:hover { color: var(--cream); }
.sn-side-tab.on { color: var(--gold); }
.sn-side-tab:focus-visible { outline: 1px solid var(--hot); outline-offset: -3px; }
.sn-side-body {
position: absolute;
z-index: 1;
top: 0;
bottom: 0;
right: var(--rail-w);
width: clamp(260px, 24vw, 360px);
border-left: 1px solid var(--rule-2);
background: rgba(11, 10, 9, .97);
transform: translateX(calc(100% + var(--rail-w)));
pointer-events: none;
transition: transform .42s cubic-bezier(.22, .61, .36, 1);
}
.sn-side-body.open { transform: none; pointer-events: auto; }
.sn-side-graph { display: block; width: 100%; height: 100%; }
.sn-hint {
position: absolute;
z-index: 2;
bottom: 14px;
left: 50%;
margin: 0;
color: var(--dim);
font-family: var(--data);
font-size: .7rem;
transform: translateX(-50%);
transition: opacity .3s;
}
.sn-hint.away { opacity: 0; }
/* ------------------------------------------------------------- no-script / no-d3 listing */
/* The listing ships hidden and is only revealed when the graph cannot run — either by the
noscript rule in the page or by pantheon.js giving up. This [hidden] rule has to be here:
the display below would otherwise outrank the browser's own [hidden] { display: none }. */
.sn-fallback[hidden] { display: none; }
/* The stacked view: one block per family, each member a card that opens in place. It is the
no-JavaScript fallback and the whole layout on a narrow screen, where a ring of thirty-two
seals is unreadable and a list of families is not. */
.sn-fallback {
position: relative;
z-index: 1;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 30px 40px;
width: min(var(--col), 100%);
margin: 0 auto;
padding: 34px var(--gutter);
}
body.landing .sn-fallback h2 {
margin: 0 0 14px;
padding-bottom: 8px;
border-bottom: 1px solid var(--rule);
color: var(--gold);
font-family: var(--plaque);
font-size: .8rem;
letter-spacing: .2em;
text-transform: capitalize;
}
.sn-fallback ul { margin: 0; padding: 0; list-style: none; }
.sn-fallback li { margin: 0 0 10px; }
.sn-card { border: 1px solid var(--rule); background: rgba(20, 18, 16, .5); }
.sn-card[open] { border-color: var(--rule-2); background: var(--wall); }
.sn-card summary {
display: flex;
gap: 14px;
align-items: center;
padding: 12px;
cursor: pointer;
list-style: none;
}
.sn-card summary::-webkit-details-marker { display: none; }
.sn-card summary::marker { content: ""; }
.sn-card summary:focus-visible { outline: 1px solid var(--hot); outline-offset: -2px; }
.sn-card-face {
flex: none;
display: grid;
place-items: center;
width: 58px;
height: 58px;
}
.sn-card-face img { display: block; width: 100%; height: auto; }
.sn-card-face .sn-empty {
display: grid;
place-items: center;
width: 46px;
height: 46px;
border: 1px solid var(--rule-2);
border-radius: 50%;
color: var(--dim);
font-family: var(--plaque);
font-size: 1rem;
}
.sn-card-head { display: grid; gap: 2px; min-width: 0; }
.sn-fallback b {
color: var(--cream);
font-family: var(--plaque);
font-size: .84rem;
font-weight: 600;
letter-spacing: .13em;
text-transform: uppercase;
}
.sn-card-domain {
color: var(--gold);
font-family: var(--data);
font-size: .66rem;
letter-spacing: .12em;
text-transform: lowercase;
}
.sn-card-does {
color: var(--muted);
font-family: var(--plaque);
font-size: .82rem;
line-height: 1.45;
}
.sn-card-body { padding: 0 12px 14px; border-top: 1px solid var(--rule); }
.sn-card-body p {
margin: 12px 0 0;
color: var(--muted);
font-family: var(--plaque);
font-size: .88rem;
line-height: 1.6;
}
.sn-card-body .sn-spec { margin: 14px 0 16px; }
.sn-card[data-state="machine"] .sn-card-body .sn-actions { display: none; }
.sn-fallback a { color: var(--gold); font-family: var(--data); font-size: .78rem; }
.sn-fallback span { color: var(--dim); font-family: var(--data); font-size: .78rem; }
/* --------------------------------------------------------------------------- the drawer */
/* The detail panel rides in from the left edge of the field; the graph re-centres into the
space that is left, which the simulation animates on its own. */
.sn-drawer {
position: absolute;
z-index: 7;
top: 0;
bottom: 0;
left: 0;
width: clamp(320px, 33vw, 452px);
overflow-y: auto;
border-right: 1px solid var(--rule-2);
background: rgba(11, 10, 9, .97);
transform: translateX(-101%);
pointer-events: none;
transition: transform .42s cubic-bezier(.22, .61, .36, 1);
}
.sn-drawer.open {
transform: none;
pointer-events: auto;
}
.sn-drawer-in {
display: block;
padding: clamp(22px, 2.4vw, 34px);
}
.sn-drawer-art {
width: min(200px, 54%);
margin-bottom: clamp(18px, 2vw, 26px);
}
.sn-drawer-art img { display: block; width: 100%; height: auto; }
.sn-drawer-art .sn-empty {
display: grid;
place-items: center;
aspect-ratio: 1;
border: 1px solid var(--rule-2);
border-radius: 50%;
background: radial-gradient(circle at 50% 36%, #171512, #0e0c0b 72%);
color: var(--rule-2);
font-family: var(--plaque);
font-size: 3rem;
}
.sn-drawer-body { position: relative; }
body.landing .sn-drawer-body h3 {
margin: 0 44px 6px 0; /* clear of the close button */
color: var(--cream);
font-family: var(--plaque);
font-size: clamp(1.5rem, 3vw, 2rem);
font-weight: 600;
letter-spacing: .13em;
text-transform: uppercase;
}
.sn-drawer-domain {
margin: 0 0 20px;
color: var(--gold);
font-size: .78rem;
font-weight: 600;
letter-spacing: .22em;
text-transform: uppercase;
}
body.landing .sn-long {
max-width: 58ch;
margin: 0 0 22px;
color: var(--muted);
font-family: var(--plaque);
font-size: 1rem;
line-height: 1.65;
}
.sn-spec {
display: grid;
grid-template-columns: max-content minmax(0, 1fr);
gap: 9px 18px;
margin: 0 0 24px;
padding: 18px 0 0;
border-top: 1px solid var(--rule);
font-family: var(--data);
font-size: .78rem;
}
.sn-spec dt { color: var(--dim); }
.sn-spec dd { margin: 0; color: var(--cream); }
.sn-spec .sn-house-name { text-transform: capitalize; }
.sn-actions { display: flex; flex-wrap: wrap; gap: 10px; }
.sn-btn {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 38px;
padding: 0 16px;
border: 2px solid var(--cream);
background: transparent;
color: var(--cream);
font-family: var(--chrome);
font-size: .78rem;
font-weight: 600;
font-stretch: 85%;
letter-spacing: .11em;
text-transform: uppercase;
text-decoration: none;
cursor: pointer;
transition: background .16s, color .16s;
}
.sn-btn:hover { background: var(--cream); color: var(--ink); }
.sn-btn-quiet { border-color: var(--rule-2); color: var(--muted); }
.sn-btn-quiet:hover { border-color: var(--cream); background: transparent; color: var(--cream); }
.sn-close {
position: absolute;
top: 0;
right: 0;
z-index: 2;
display: grid;
place-items: center;
width: 34px;
height: 34px;
border: 1px solid var(--rule-2);
background: transparent;
color: var(--muted);
font-size: 1.1rem;
line-height: 1;
cursor: pointer;
}
.sn-close:hover { border-color: var(--cream); color: var(--cream); }
/* ------------------------------------------------------------------------------- footer */
.sn-maker {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 28px;
min-height: 128px;
}
.sn-maker img { display: block; width: 60px; height: auto; opacity: .16; }
body.landing .sn-thesis {
padding-bottom: 22px;
color: var(--muted);
font-family: var(--plaque);
font-size: 1rem;
letter-spacing: .04em;
}
.sn-colophon {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 14px;
padding: 14px 0 44px;
color: var(--dim);
font-family: var(--data);
font-size: .72rem;
}
/* -------------------------------------------------------------------------- responsive */
@media (max-width: 900px) {
.sn-gate { grid-template-columns: minmax(0, 1fr); }
}
/* no room to slide the graph aside: the panel simply covers it */
@media (max-width: 700px) {
.sn-drawer { width: 100%; border-right: 0; }
}
/* Below this the ring stops being readable: thirty-two seals want more width than a phone
has, and shrinking them to fit turns the whole thing into gravel. So the field gives way
to the stacked families, which need no script and no physics — the [hidden] attribute is
overridden here rather than removed, so this works with JavaScript switched off. */
@media (max-width: 820px) {
.sn-field {
height: auto;
overflow: visible;
}
.sn-graph,
.sn-hint,
.sn-drawers,
.sn-drawer { display: none; }
.sn-fallback[hidden] { display: grid; }
.sn-fallback { padding-top: 8px; }
/* Reading order down the column, set by STACK_ORDER in pantheon.py. Scoped to this query on
purpose: the custom property is inert everywhere else, so the desktop listing keeps the
order the markup is in and the ring is untouched. */
.sn-family { order: var(--stack, 0); }
}
@media (max-width: 560px) {
body.landing .topbar {
top: auto;
right: var(--gutter);
bottom: max(16px, env(safe-area-inset-bottom));
}
/* the field is already stacked by here — nothing left to size */
.sn-fallback { grid-template-columns: minmax(0, 1fr); gap: 26px; }
}
@media (prefers-reduced-motion: reduce) {
body.landing *, body.landing *::before, body.landing *::after {
animation-duration: .01ms !important;
transition-duration: .01ms !important;
}
}