/* Same self-hosted Quicksand used for headings on Gitea/the homepage dashboard elsewhere in this stack (see ~/repos/infrastructure/gitea-custom-header.tmpl) - reused as-is (same files, same unicode-range split) rather than pulled from a font CDN. font-display: optional, not swap, on all three: swap always shows the fallback font first then reflows to Quicksand once it loads, which is a real layout shift if that swap lands after the page is already scrolled past that heading - reported as "text jumps" on real mobile Safari. optional skips the swap entirely unless the font is already cached/ready almost immediately, trading an occasional fallback-font render for zero shift. */ @font-face { font-family: "Quicksand"; font-style: normal; font-weight: 600; font-display: optional; src: url("/fonts/quicksand-semibold-latin.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } @font-face { font-family: "Quicksand"; font-style: normal; font-weight: 600; font-display: optional; src: url("/fonts/quicksand-semibold-latin-ext.woff2") format("woff2"); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; } @font-face { font-family: "Quicksand"; font-style: normal; font-weight: 600; font-display: optional; src: url("/fonts/quicksand-semibold-vietnamese.woff2") format("woff2"); unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; } :root { /* the one brand color - everything else is neutral. A muted, desaturated echo of the YES canvas's cyan ink: same family as the canvas so chrome harmonizes with it, but dusty enough to read as a quiet spot color on dark stock rather than compete. Legible against near-black both as text and under #0a0a0a button text. */ --accent: #8ec2c0; --accent-soft: rgba(142, 194, 192, 0.16); --paper: #0a0a0a; --ink: #ededed; --ink-dim: #9a9a9a; --line: #2a2a2a; /* Cards float over a sticky hero piece - translucent so the piece reads faintly through them (paired with backdrop-filter on .alt-card), shadowed so their edge against the animated backdrop is a soft lift rather than a hard contrast line. */ --card-bg: rgba(22, 22, 22, 0.35); --card-bg-opaque: rgba(22, 22, 22, 0.84); --card-shadow: rgba(0, 0, 0, 0.5); --error: #ff6b6b; /* Behind .hero-copy: dark halo on dark stock, paper halo on light. */ --hero-glow: rgba(0, 0, 0, 0.8); --heading: "Quicksand", var(--sans); --sans: -apple-system, "SF Pro Text", ui-sans-serif, "Segoe UI", system-ui, sans-serif; --radius: 1.1rem; /* The single place absolute units are allowed: this IS the base every other length derives from (rem/em everywhere else - px is banned in stylesheets). First line is the fallback for engines without lvmin. */ font-size: 17px; font-size: max(12px, calc(2.1lvmin + 3pt)); } /* Light theme: the same muted-press idea on white stock - warm paper, near-black ink, and the accent deepened from dusty cyan to a teal ink that actually carries as text on light paper (the dark theme's #8ec2c0 washes out there). Canvas-drawing modules (gesture.js, and content-shipped hero modules) mirror this palette on their own, since canvas paint can't read CSS vars. */ @media (prefers-color-scheme: light) { :root { --accent: #47807e; --accent-soft: rgba(71, 128, 126, 0.16); --paper: #f6f5f1; --ink: #1d1d1b; --ink-dim: #6d6c66; --line: #d9d7cf; --card-bg: rgba(252, 251, 248, 0.35); --card-bg-opaque: rgba(252, 251, 248, 0.84); --card-shadow: rgba(50, 48, 42, 0.18); --error: #a83a32; --hero-glow: rgba(246, 245, 241, 0.85); } /* The house wordmark SVG is a hardcoded white stroke (also used raw in dark contexts elsewhere) - flip it to ink here rather than fork the asset. A content-provided logo keeps its colors. */ .wordmark-house img { filter: invert(0.92); } } * { box-sizing: border-box; } html, body { margin: 0; padding: 0; background: var(--paper); color: var(--ink); font-family: var(--sans); line-height: 1.55; } h1, h2, h3 { font-family: var(--heading); font-weight: 600; line-height: 1.2; margin: 0 0 0.4em; } p { margin: 0 0 0.9em; } a { color: var(--accent); } ::selection { background: var(--accent-soft); color: var(--ink); } main.loading, main.not-found { min-height: 60vh; /* svh so wrapper browsers that resize the webview as their toolbar animates (Firefox/Chrome iOS) don't reflow this placeholder - the small viewport is a fixed value. Same ladder as .hero-piece. */ min-height: 60svh; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1rem; text-align: center; color: var(--ink-dim); } /* hero */ .hero { max-width: 46rem; margin: 0 auto; padding: 4rem 1.5rem 2.5rem; display: flex; flex-direction: column; align-items: center; text-align: center; gap: 1rem; } .wordmark { display: inline-flex; opacity: 0.85; transition: opacity 120ms; } .wordmark:hover { opacity: 1; } .wordmark img { height: 2.1rem; width: auto; display: block; } .hero-copy h1 { font-size: 2.1rem; } .hero-copy p { color: var(--ink-dim); font-size: 1.05rem; max-width: 46ch; /* The measure cap shrinks the box below the copy column; without auto margins the box left-anchors and its centered text centers in the wrong frame. */ margin-inline: auto; } /* A content-shipped hero module (site.yaml hero.kind: module) draws into .hero-piece; the module owns its look (it may restyle .hero itself). Portal only reserves the box so the copy doesn't jump when the module mounts - plain-vh fallback first, as everywhere. */ .hero-module .hero-piece { position: relative; width: 100%; min-height: 55vh; min-height: 55svh; } /* announcements - live event pages, a strip above the hero copy. Header, not footer: the one place a page claims attention before the question is asked. */ .announce { width: 100%; display: flex; flex-wrap: wrap; justify-content: center; gap: 0.6rem 1.2rem; margin: -2rem 0 0.5rem; font-size: 0.88rem; position: relative; z-index: 1; } .announce-item { display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: 0.35rem 0.7rem; padding: 0.45rem 0.9rem; border: 0.06rem solid var(--line); border-left: 0.2rem solid var(--accent); border-radius: 0.3rem; color: var(--ink); text-decoration: none; background: var(--paper); transition: border-color 120ms; } .announce-item:hover, .announce-item[aria-current="page"] { border-color: var(--accent); } .announce-name { font-weight: 600; } .announce-when { color: var(--ink-dim); } .announce-relative { color: var(--accent); text-transform: uppercase; letter-spacing: 0.06em; font-size: 0.72rem; } /* alternatives */ /* position + z-index on these three: everything that scrolls over the sticky canvas must be a positioned box above the hero's z-index 0, or the (positioned) hero would paint over it. */ /* Pictures across the page (`slideshow`): the full width of the screen, every picture the strip's height and as wide as its own shape, never wider than the screen. The caption floats over the foot of its picture. */ .slideshow { --swiper-navigation-color: #fff; --swiper-navigation-size: 1.8rem; position: relative; z-index: 1; display: block; width: 100%; height: var(--slideshow-height, 60svh); max-height: var(--slideshow-height, 60svh); margin: 0 0 3rem; } /* Before the Swiper bundle has upgraded the element, or without it: a row that scrolls sideways. */ .slideshow:not(:defined) { display: flex; gap: 6px; overflow-x: auto; overflow-y: hidden; } .slideshow swiper-slide { flex: none; width: auto; height: 100%; } .slide { position: relative; height: 100%; margin: 0; } .slide img { display: block; height: 100%; width: auto; /* A picture that has not loaded and says nothing of its size still takes a place, or the whole strip would sit in view at once. */ min-width: 12rem; max-width: 100vw; object-fit: cover; background: var(--line); } .slide figcaption { position: absolute; left: 0; right: 0; bottom: 0; padding: 2.4rem 1rem 0.9rem; color: #fff; font-size: 0.95rem; line-height: 1.35; text-shadow: 0 0.06rem 0.2rem rgba(0, 0, 0, 0.6); background: linear-gradient(to top, rgba(0, 0, 0, 0.72), rgba(0, 0, 0, 0)); pointer-events: none; } .alternatives { position: relative; z-index: 1; max-width: 75ch; margin: 0 auto 5rem; padding: 0 1.5rem; display: grid; gap: 1.25rem; } .question-nav, .question-responsible { position: relative; z-index: 1; } @media (max-width: 43.75rem) { .alternatives { padding: 0; gap: 3.5rem; } .alt-card { border-width: 0; border-radius: 0rem; } } /* The way down the tree: one level toward the front door, from any page, as a line at the foot of it. */ .down-the-tree-foot { position: relative; z-index: 1; text-align: center; margin: 2.6rem auto 0; font-size: 0.95rem; } .down-the-tree-foot a { color: var(--ink); text-decoration: none; border-bottom: 0.06rem solid var(--line); } .down-the-tree-foot a:hover { color: var(--accent); border-bottom-color: var(--accent); } /* Only for the person a page names as responsible: a quiet card at the foot of their own page for suggesting a change to it. */ .suggest-edit { position: relative; z-index: 1; max-width: 44rem; margin: 2.4rem auto 0; padding: 1.2rem 1.4rem; border: 0.06rem dashed var(--line); border-radius: var(--radius); background: var(--card-bg); } .suggest-edit h2 { font-size: 1.05rem; margin: 0 0 0.4rem; } .suggest-edit p { color: var(--ink-dim); margin: 0 0 0.8rem; } .suggest-edit textarea { width: 100%; margin-bottom: 0.7rem; } .suggest-edit h3 { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ink-dim); margin: 1.4rem 0 0.5rem; } .suggest-edit-mine { list-style: none; margin: 0; padding: 0; } .suggest-edit-mine li { display: flex; gap: 0.8rem; align-items: baseline; padding: 0.5rem 0; border-top: 0.06rem solid var(--line); } .suggest-edit-state { flex: none; min-width: 6.5rem; font-size: 0.8rem; color: var(--ink-dim); } .suggest-edit-state[data-state="done"] { color: var(--ink); font-weight: 600; } .suggest-edit-text { white-space: pre-wrap; overflow-wrap: anywhere; } .question-nav { display: flex; flex-wrap: wrap; justify-content: center; align-items: baseline; gap: 0.5rem 1.2rem; margin: 2.2rem auto 0; max-width: 44rem; padding: 0 1rem; font-size: 0.88rem; } .question-nav-lead { color: var(--ink-dim); text-transform: uppercase; letter-spacing: 0.06em; font-size: 0.72rem; } .question-nav a { color: var(--ink-dim); text-decoration: none; border-bottom: 0.06rem solid var(--line); } .question-nav a:hover { color: var(--accent); border-bottom-color: var(--accent); } /* Meta information above the question nav - dimmer and smaller than anything the question itself says. */ .question-responsible { display: block; text-align: center; color: var(--ink-dim); opacity: 0.7; font-size: 0.85rem; line-height: 1.6; margin: 2.2rem 1rem 0; } .question-responsible a { color: var(--ink-dim); } .question-responsible a:hover { color: var(--accent); } .alt-card { background: var(--card-bg); /* Frosted glass over the animated canvas behind: the blur is what keeps text on a translucent card readable while the piece still reads through it in color and motion. */ -webkit-backdrop-filter: blur(0.8rem); backdrop-filter: blur(0.8rem); border: 0.06rem solid var(--line); border-radius: var(--radius); padding: 1.75rem 1.9rem; box-shadow: 0 1.1rem 2.4rem var(--card-shadow); } /* Without backdrop blur, 0.55-alpha cards put text straight over the moving canvas - illegible. Engines lacking the filter get a near-opaque card instead. */ @supports not ((backdrop-filter: blur(1rem)) or (-webkit-backdrop-filter: blur(1rem))) { .alt-card { background: var(--card-bg-opaque); } } .alt-image { display: block; width: 100%; height: auto; /* Natural aspect, not a cropped band; a very tall image is still bounded so it can't tower. The deck below overrides for its fixed-height cards. */ max-height: 32rem; object-fit: contain; border-radius: calc(var(--radius) - 0.3rem); margin-bottom: 1.1rem; } /* Sized down from full card width so the cards effect's fanned-out neighbors stay inside the card - swiper positions slides absolutely, so the container needs its own height. Before the bundle upgrades the element (or without JS) slides stack as plain blocks; capping the container height keeps that fallback from towering. */ .alt-image-deck { display: block; width: min(100%, 22rem); height: 14rem; margin: 0 auto 1.1rem; overflow: hidden; } .alt-image-deck .alt-image { height: 100%; max-height: none; margin-bottom: 0; } .alt-card h2 { font-size: 1.3rem; } .alt-description { color: var(--ink-dim); } .alt-encouragement { font-size: 0.9rem; color: var(--accent); font-style: italic; margin: 0; } .alt-cta { display: flex; flex-direction: row; align-items: center; justify-content: space-between; gap: 1.2rem; margin-top: 1.4rem; } .alt-encouragements { display: flex; flex-direction: column; gap: 0.3rem; } .features { display: grid; gap: 1.1rem; margin-top: 0.6rem; } .feature { border-left: 0.18rem solid var(--feature-accent, transparent); } .feature-icon { width: 1.3rem; height: 1.3rem; margin-top: 0.1rem; background-color: var(--feature-accent, currentColor); mask-repeat: no-repeat; mask-size: contain; mask-position: center; -webkit-mask-repeat: no-repeat; -webkit-mask-size: contain; -webkit-mask-position: center; } /* With an icon the feature is two columns: the icon in the first, everything else - name, description, fields, resources - in the second, so every line of text shares one left edge instead of wrapping back under the icon. */ .feature:has(> .feature-icon) { display: grid; grid-template-columns: 1.3rem minmax(0, 1fr); column-gap: 0.7rem; align-items: start; } .feature:has(> .feature-icon) > .feature-icon { grid-column: 1; grid-row: 1; } .feature:has(> .feature-icon) > :not(.feature-icon) { grid-column: 2; } .feature h3 { font-size: 1rem; margin-bottom: 0.15em; } /* voice field (voice.js): a record button, a status line, a preview */ .voice-wrap { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem 1rem; } .voice-button { font: inherit; padding: 0.55rem 1.1rem; border: 0.06rem solid var(--accent); border-radius: 2rem; background: transparent; color: var(--ink); cursor: pointer; } .voice-button:hover:not(:disabled) { background: var(--accent); color: var(--paper); } .voice-button.recording { background: var(--accent); color: var(--paper); animation: voice-pulse 1.2s ease-in-out infinite; } .voice-button:disabled { opacity: 0.5; cursor: default; } @keyframes voice-pulse { 50% { opacity: 0.6; } } .voice-status { font-size: 0.85rem; color: var(--ink-dim); } .voice-preview, .item-card-audio { display: block; width: 100%; max-width: 28rem; margin-top: 0.4rem; } /* display:block above would beat the hidden attribute */ .voice-preview[hidden] { display: none; } /* links inside markdown descriptions */ .alt-description a, .feature p a { color: inherit; text-decoration: none; border-bottom: 0.06rem solid var(--accent); } .alt-description a:hover, .feature p a:hover { color: var(--accent); } /* images inside markdown descriptions: full-width figures in the card's flow, framed like the rest of the press sheet */ .alt-description img, .item-card-description img, .feature p img { display: block; width: 100%; margin: 0.6rem 0 0.2rem; border-radius: 0.5rem; border: 0.06rem solid var(--line); } /* Content hint via a markdown image title (`![alt](url "right 9rem")`): float and shrink so text wraps around a portrait. */ .alt-description img.md-float-left, .item-card-description img.md-float-left, .feature p img.md-float-left, .alt-description img.md-float-right, .item-card-description img.md-float-right, .feature p img.md-float-right { width: auto; max-width: 45%; } .alt-description img.md-float-left, .item-card-description img.md-float-left, .feature p img.md-float-left { float: left; margin: 0.2rem 1.1rem 0.5rem 0; } .alt-description img.md-float-right, .item-card-description img.md-float-right, .feature p img.md-float-right { float: right; margin: 0.2rem 0 0.5rem 1.1rem; } .alt-description code, .feature p code { font-size: 0.9em; } .feature p { color: var(--ink-dim); font-size: 0.95rem; } .field { display: flex; flex-direction: column; gap: 0.35rem; margin-top: 0.6rem; } .field-label { font-size: 0.85rem; font-weight: 600; color: var(--ink-dim); } .required { color: var(--accent); margin-left: 0.2em; } input, textarea { font: inherit; color: var(--ink); background: var(--paper); border: 0.06rem solid var(--line); border-radius: 0.6rem; padding: 0.65rem 0.8rem; width: 100%; } textarea { min-height: 5rem; resize: vertical; } /* A bound textarea holds loaded source text - monospace, roomy, no soft-wrap surprises. */ textarea.bound { font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: 0.85rem; min-height: 16rem; white-space: pre; overflow-x: auto; } input:focus, textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 0.18rem var(--accent-soft); } /* One placeholder treatment for every input-like surface: solid ink-dim, no translucency. opacity: 1 also undoes the UA's own placeholder dimming (Firefox) and ProseKit's 0.3 on its placeholder pseudo-element, which read far dimmer than the native inputs next to it. */ ::placeholder, .prosekit-editor .prosekit-placeholder::before, .prosekit-editor.prosekit-placeholder::before { color: var(--ink-dim); opacity: 1; } .prosekit-wrap { background: var(--paper); border: 0.06rem solid var(--line); border-radius: 0.6rem; overflow: hidden; } /* The gesture drawing field (gesture.js). Stroke/ghost/echo colors live in the JS module's own palette table - canvas can't read these custom properties - so palette changes go there AND here. */ .gesture-wrap { position: relative; width: 100%; } /* The canvas is created by JS only after wasm hydration - reserve its 3/2 box until then so content below doesn't jump. Scoped to :empty on purpose: on the mounted wrap (a stretched item inside the flex .field) the same aspect-ratio turned the echo strip's extra height into extra WIDTH and the whole card ballooned past the viewport after the first stroke. */ .gesture-wrap:empty { aspect-ratio: 3 / 2; } .gesture-canvas { display: block; width: 100%; aspect-ratio: 3 / 2; background: var(--paper); border: 0.06rem solid var(--line); border-radius: 0.6rem; cursor: crosshair; /* Load-bearing on touch: without it the browser pans instead of letting the stroke happen. */ touch-action: none; } /* Results overlay the canvas instead of growing the widget - the page never jumps when places arrive (or don't). */ .gesture-echoes { position: absolute; left: 0.6rem; right: 0.6rem; bottom: 0.6rem; display: flex; justify-content: center; gap: 0.5rem; pointer-events: none; } .gesture-echoes:empty { display: none; } .gesture-echo { width: 3rem; height: 3rem; background: var(--paper); border: 0.06rem solid var(--line); border-radius: 0.4rem; opacity: 0; transition: opacity 0.5s ease; pointer-events: auto; box-shadow: 0 0.15rem 0.6rem rgba(0, 0, 0, 0.18); } .gesture-echo.shown { opacity: var(--echo-strength, 1); } /* places are pickable: the pick re-derives the input's key */ .gesture-echo { cursor: pointer; transition: opacity 0.5s ease, border-color 120ms, transform 120ms; } .gesture-echo:hover, .gesture-echo:focus-visible { border-color: var(--accent); outline: none; } .gesture-echo.selected { border-color: var(--accent); box-shadow: 0 0 0 0.12rem var(--accent); transform: translateY(-0.1rem); } .gesture-empty { position: absolute; left: 0; right: 0; bottom: 0.7rem; margin: 0; text-align: center; font-size: 0.85rem; color: var(--ink-dim); pointer-events: none; } .gesture-empty:empty { display: none; } .gesture-status { position: absolute; top: 0.55rem; right: 0.55rem; width: 0.45rem; height: 0.45rem; border-radius: 50%; background: var(--ink-dim); opacity: 0.35; transition: opacity 0.3s ease, background 0.3s ease; } .gesture-status.live { background: var(--accent); opacity: 0.9; } .prosekit-toolbar { display: flex; gap: 0.2rem; padding: 0.4rem; border-bottom: 0.06rem solid var(--line); flex-wrap: wrap; } .prosekit-toolbar button { font: inherit; font-size: 0.9rem; color: var(--ink-dim); background: transparent; border: 0.06rem solid transparent; border-radius: 0.4rem; padding: 0.3rem 0.55rem; cursor: pointer; line-height: 1; } .prosekit-toolbar button:hover { color: var(--ink); border-color: var(--line); } .prosekit-toolbar button.is-active { color: var(--paper); background: var(--accent); } .prosekit-editor { font: inherit; color: var(--ink); padding: 0.65rem 0.8rem; min-height: 5rem; } .prosekit-editor:focus { outline: none; } .prosekit-wrap:focus-within { border-color: var(--accent); box-shadow: 0 0 0 0.18rem var(--accent-soft); } /* The editor's whole type scale, in rem. ProseKit's own typography.css is deliberately not loaded (it sizes content in fixed px - see mountEditor in prosekit-editor.js), so these rules are the only typography the editor gets, on top of UA defaults (em-based, they scale fine for lists/blockquote/code). NOTE: editor.mount() turns the .prosekit-editor div itself into the .ProseMirror root, so content elements are its direct children - no .ProseMirror in the selector. */ .prosekit-editor p { font-size: 1rem; line-height: 1.5; margin: 0 0 0.6em; } .prosekit-editor p:last-child { margin-bottom: 0; } .prosekit-editor h1 { font-size: 2.5rem; line-height: 1.1; margin: 0.9em 0 0.1em; } .prosekit-editor h2 { font-size: 1.875rem; line-height: 1.3; margin: 1.05em 0 0.13em; } .prosekit-editor h3 { font-size: 1.5rem; line-height: 1.33; margin: 0.9em 0 0.04em; } .prosekit-editor h4 { font-size: 1.25rem; line-height: 1.3; margin: 0.8em 0 0.05em; } .prosekit-editor h5 { font-size: 1.125rem; line-height: 1.22; margin: 0.78em 0 0.06em; } .prosekit-editor h6 { font-size: 1rem; line-height: 1.25; margin: 0.75em 0 0.06em; } .prosekit-editor :is(h1, h2, h3, h4, h5, h6):first-child { margin-top: 0; } .alt-submit { font-family: var(--sans); font-weight: 600; font-size: 0.95rem; color: var(--paper); background: var(--accent); border: none; border-radius: 0.7rem; padding: 0.75rem 1.4rem; cursor: pointer; transition: filter 120ms, transform 120ms; /* A gateway alternative renders this as an (navigation, no POST); kill the link chrome so it reads as the button it looks like. */ text-decoration: none; display: inline-block; } .alt-submit:hover { filter: brightness(1.08); } .alt-submit:active { transform: translateY(0.06em); } .alt-submit:disabled { filter: grayscale(0.5) brightness(0.85); cursor: wait; } /* A content-disabled alternative (`disabled: true`): announced, not yet takeable - reads as a promise, not as a stuck form. */ .alt-submit.soon:disabled { cursor: not-allowed; opacity: 0.6; } .gate-card { text-align: center; } /* resources */ .resource { margin-top: 0.6rem; } .resource-loading, .resource-empty { color: var(--ink-dim); font-size: 0.9rem; } .resource-error { color: var(--error); font-size: 0.9rem; } .resource-raw { background: var(--paper); border: 0.06rem solid var(--line); border-radius: 0.6rem; padding: 0.8rem; font-size: 0.82rem; overflow-x: auto; white-space: pre-wrap; } .answer-list { display: grid; gap: 0.8rem; } .answer-row { border: 0.06rem solid var(--line); border-radius: 0.8rem; padding: 1rem 1.1rem; display: grid; gap: 0.6rem; } .answer-row.decided { opacity: 0.55; } .answer-fields { display: grid; grid-template-columns: repeat(auto-fill, minmax(10.5rem, 1fr)); gap: 0.5rem 1rem; } .answer-field { display: flex; flex-direction: column; gap: 0.1rem; } .answer-field-key { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ink-dim); } .answer-field-value { font-size: 0.92rem; word-break: break-word; } .answer-meta { display: flex; gap: 0.8rem; align-items: center; font-size: 0.78rem; color: var(--ink-dim); } .answer-state { text-transform: uppercase; letter-spacing: 0.05em; font-weight: 600; color: var(--accent); } /* The page about a listed record, when the list offers one. */ .answer-link { display: inline-block; margin-top: 0.4rem; font-size: 0.9rem; font-weight: 600; color: var(--accent); text-decoration: none; border-bottom: 0.1rem solid var(--accent); } .answer-link:hover { color: var(--ink); border-bottom-color: var(--ink); } .answer-actions { display: flex; flex-direction: column; align-items: flex-start; gap: 0.6rem; } .item-list { display: grid; gap: 0.8rem; } .item-card { border: 0.06rem solid var(--line); border-radius: 0.8rem; padding: 1rem 1.1rem; display: grid; gap: 0.4rem; } .item-card-title { font-family: var(--sans); font-weight: 600; font-size: 1rem; } .item-card-title a { color: var(--ink); text-decoration: none; } .item-card-title a:hover { color: var(--accent); text-decoration: underline; } .item-card-description { font-size: 0.9rem; color: var(--ink-dim); /* The card is a grid with its own gap; the global p bottom margin would stack on top of it. */ margin: 0; } .item-card-fields { display: flex; flex-wrap: wrap; gap: 0.4rem 0.9rem; margin-top: 0.2rem; } .item-card-field { font-size: 0.82rem; color: var(--ink-dim); } .item-card-field-key { text-transform: uppercase; letter-spacing: 0.06em; font-size: 0.68rem; margin-right: 0.3rem; } .select-field { margin-top: 0.3rem; } .select-options { display: flex; flex-wrap: wrap; gap: 0.5rem; } .select-option { font-family: var(--sans); font-size: 0.85rem; color: var(--ink); background: var(--paper); border: 0.06rem solid var(--line); border-radius: 999rem; padding: 0.4rem 0.9rem; cursor: pointer; transition: border-color 120ms, color 120ms, background 120ms; } .select-option:hover { border-color: var(--accent); color: var(--accent); } .select-option.selected { border-color: var(--accent); color: var(--paper); background: var(--accent); } .select-option:disabled { opacity: 0.5; cursor: not-allowed; } @media (max-width: 40rem) { .hero { padding: 3rem 1.25rem 2rem; } .alt-card { padding: 1.4rem 1.3rem; } .alt-cta { flex-direction: column; align-items: flex-start; } } /* A `type: location` field: the map fills the field, and the pin is the centre of it - panning works the same with a finger, a mouse or the arrow keys, and there is nothing to miss by a few pixels. MapLibre draws into .map-canvas after hydration, so the box is reserved up front the way .gesture-wrap reserves its own. */ .map-wrap { position: relative; width: 100%; } .map-wrap:empty { aspect-ratio: 3 / 2; } .map-canvas { display: block; width: 100%; aspect-ratio: 3 / 2; border: 0.06rem solid var(--line); border-radius: 0.2rem; overflow: hidden; } /* The pin sits over the exact centre of the canvas and takes no pointer events, so every drag goes to the map under it. Its point is the bottom tip, hence the upward shift of its own height. */ .map-pin { position: absolute; top: 50%; left: 50%; width: 0.9rem; height: 0.9rem; margin: -1.35rem 0 0 -0.45rem; border: 0.18rem solid var(--ink); border-radius: 50% 50% 50% 0; transform: rotate(-45deg); pointer-events: none; background: var(--paper); } .map-readout { margin: 0.3rem 0 0; font-size: 0.85em; color: var(--ink-dim); } /* A stored location in a list: a still image from this origin, never a live map - twenty rows of WebGL would cost more than the page. */ .map-still { display: block; width: 100%; max-width: 24rem; height: auto; border: 0.06rem solid var(--line); border-radius: 0.2rem; } .map-coords { display: block; margin-top: 0.2rem; font-size: 0.85em; } .answer-field-map .answer-field-value { display: block; } /* Replies that came back by mail, under the record they belong to. They move nothing, so there is no control here - only who wrote, when, and what they said. */ .answer-notes { list-style: none; margin: 0.4rem 0 0; padding: 0 0 0 0.7rem; border-left: 0.12rem solid var(--line); display: flex; flex-direction: column; gap: 0.5rem; } .answer-note-who { font-weight: 600; } .answer-note-time { margin-left: 0.4rem; font-size: 0.85em; color: var(--ink-dim); } .answer-note-text { margin: 0.15rem 0 0; white-space: pre-wrap; } /* A page that is about a place: the map sits under the question, at the width of the text it belongs to. */ .question-place { margin: 0.8rem 0 0; } .question-place figcaption { margin-top: 0.3rem; font-size: 0.9em; color: var(--ink-dim); } /* A loading slot holds its line's worth of space and says nothing. The words that belong here have not arrived yet, and "loading…" is not one of them. */ .resource-loading { min-height: 1.4em; }