/* 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; --paper-raised: #161616; --ink: #ededed; --ink-dim: #9a9a9a; --line: #2a2a2a; --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)); } * { 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; 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 actual "YES - Rasterized Lines" piece live at uhhm.no (see public/yes.js) - landing page only. Full-viewport so it's a real moment, not a thumbnail; hero-copy overlays near the bottom rather than interrupting the canvas. */ .hero-yes { position: relative; max-width: none; width: 100%; /* svh here is only the pre-JS/no-JS fallback (and first paint before yes.js's constructor runs). Once yes.js mounts, it overwrites this with an inline `height: ` frozen from a single measurement - see setupCanvas()'s comment in yes.js for why: on real mobile Safari, content bottom-aligned inside this box kept sliding down as the address bar collapsed even with a spec'd-stable viewport unit here, so the box's actual height can't be trusted to stay put on that unit alone. An inline style set from JS always wins the cascade over this rule, so that frozen number is what actually governs once the page is interactive. Plain-vh fallback declared first - an engine without svh support ignores the invalid second line rather than falling through to auto height, which would collapse this to the height of its in-flow content and clip the canvas via overflow:hidden below. */ height: 100vh; height: 100svh; padding: 0; justify-content: flex-end; overflow: hidden; } .hero-canvas { position: absolute; inset: 0; } .hero-canvas canvas { position: absolute; top: 0; left: 0; cursor: pointer; } .hero-canvas #rasterCanvas { mix-blend-mode: overlay; opacity: 0.5; } .hero-yes .hero-copy { position: relative; z-index: 1; padding: 0 1.5rem 3.5rem; gap: 0.6rem; text-shadow: 0 0.12em 1.4em rgba(0, 0, 0, 0.8); } /* Softens the otherwise hard cut where the full-bleed canvas meets the page background right below it - fades to the same --paper color over the last few ems instead of stopping dead. Non-interactive so it never steals clicks meant for the canvas underneath. */ .hero-yes::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 8rem; background: linear-gradient(to bottom, transparent, var(--paper)); pointer-events: none; z-index: 1; } /* alternatives */ .alternatives { max-width: 75ch; margin: 0 auto 5rem; padding: 0 1.5rem; display: grid; gap: 1.25rem; } @media (max-width: 43.75rem) { .alternatives { padding: 0; gap: 3.5rem; } .alt-card { border-width: 0; border-radius: 0rem; } } .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(--paper-raised); border: 0.06rem solid var(--line); border-radius: var(--radius); padding: 1.75rem 1.9rem; } .alt-image { display: block; width: 100%; max-height: 14rem; object-fit: cover; 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 { float: left; width: 1.3rem; height: 1.3rem; margin: 0.1rem 0.6rem 0.4rem 0; 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; } .feature h3 { font-size: 1rem; margin-bottom: 0.15em; } .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); } .prosekit-wrap { background: var(--paper); border: 0.06rem solid var(--line); border-radius: 0.6rem; overflow: hidden; } .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); } /* ProseKit's typography.css (loaded from esm.sh at mount time) sets fixed px font sizes on .ProseMirror, so they ignore the responsive root font-size. Re-state them in rem/em at the same proportions, with .prosekit-editor prefixed so these win on specificity. */ .prosekit-editor .ProseMirror p { font-size: 1rem; line-height: 1.5; margin: 0 0 0.6em; } .prosekit-editor .ProseMirror p:last-child { margin-bottom: 0; } .prosekit-editor .ProseMirror h1 { font-size: 2.5rem; line-height: 1.1; margin: 0.9em 0 0.1em; } .prosekit-editor .ProseMirror h2 { font-size: 1.875rem; line-height: 1.3; margin: 1.05em 0 0.13em; } .prosekit-editor .ProseMirror h3 { font-size: 1.5rem; line-height: 1.33; margin: 0.9em 0 0.04em; } .prosekit-editor .ProseMirror h4 { font-size: 1.25rem; line-height: 1.3; margin: 0.8em 0 0.05em; } .prosekit-editor .ProseMirror h5 { font-size: 1.125rem; line-height: 1.22; margin: 0.78em 0 0.06em; } .prosekit-editor .ProseMirror h6 { font-size: 1rem; line-height: 1.25; margin: 0.75em 0 0.06em; } .prosekit-editor .ProseMirror :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: #0a0a0a; background: var(--accent); border: none; border-radius: 0.7rem; padding: 0.75rem 1.4rem; cursor: pointer; transition: filter 120ms, transform 120ms; } .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; } .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: #ff6b6b; 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); } .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); } .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; } }