Files
portal/style/main.css
T
Bendik Aagaard Lynghaug f0a571916d
Deploy / deploy (push) Successful in 34s
Quicksand: font-display optional, not swap, to stop the text-jump on scroll
Confirmed via user testing that the jump was in DOM text below the
hero (headings using Quicksand), not the YES canvas (which draws in
plain Arial, unaffected). swap always paints the fallback font first
then reflows once Quicksand loads - a real layout shift if that swap
lands after the user has already scrolled past that heading, which
is exactly what was happening. optional skips the swap entirely
unless the font is already cached/ready almost immediately.
2026-08-06 16:48:11 +02:00

535 lines
12 KiB
CSS

/* 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. Tuned for
legibility against near-black, distinct from the YES canvas's own
cyan/magenta/yellow so UI chrome never fights with it. */
--accent: #f0923c;
--accent-soft: rgba(240, 146, 60, 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;
/* responsive base size: scales with viewport (2.1svmin) on top of a
fixed floor (3pt) so it keeps growing on large/wide screens
instead of capping at a fixed px value like the old 17px did;
max(12px, ...) keeps it from shrinking below legible on tiny
viewports. svmin (small-viewport min), not plain vmin - plain vh
-based units recalculate as mobile Safari's address bar
hides/shows on scroll, which would reflow every text size on the
page mid-scroll; svmin stays fixed to the smallest viewport state,
same stability .hero-yes gets from 100svh below.
Plain-px fallback declared first: an engine that doesn't
understand svmin/calc() here treats the whole line as invalid and
ignores it, leaving this in effect instead of falling through to
the UA default. */
font-size: 17px;
font-size: max(12px, calc(2.1svmin + 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: 780px;
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%;
/* plain-vh fallback first - same reasoning as :root's font-size
above: 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 2px 24px 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: 6em;
background: linear-gradient(to bottom, transparent, var(--paper));
pointer-events: none;
z-index: 1;
}
/* alternatives */
.alternatives {
max-width: 780px;
margin: 0 auto 5rem;
padding: 0 1.5rem;
display: grid;
gap: 1.25rem;
}
.question-responsible {
text-align: center;
color: var(--ink-dim);
font-size: 0.85rem;
line-height: 1.6;
}
.question-responsible a { color: var(--ink-dim); }
.question-responsible a:hover { color: var(--accent); }
.question-report {
font: inherit;
color: var(--ink-dim);
background: none;
border: none;
padding: 0;
text-decoration: underline;
cursor: pointer;
}
.question-report:hover { color: var(--accent); }
.question-report:disabled { opacity: 0.5; cursor: wait; }
.alt-card {
background: var(--paper-raised);
border: 1px solid var(--line);
border-radius: var(--radius);
padding: 1.75rem 1.9rem;
}
.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 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: 1px solid var(--line);
border-radius: 0.6rem;
padding: 0.65rem 0.8rem;
width: 100%;
}
textarea { min-height: 5rem; resize: vertical; }
input:focus, textarea:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-soft);
}
.prosekit-wrap {
background: var(--paper);
border: 1px solid var(--line);
border-radius: 0.6rem;
overflow: hidden;
}
.prosekit-toolbar {
display: flex;
gap: 0.2rem;
padding: 0.4rem;
border-bottom: 1px solid var(--line);
flex-wrap: wrap;
}
.prosekit-toolbar button {
font: inherit;
font-size: 0.9rem;
color: var(--ink-dim);
background: transparent;
border: 1px 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 3px var(--accent-soft);
}
.prosekit-editor p { margin: 0 0 0.6em; }
.prosekit-editor p:last-child { margin-bottom: 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(1px); }
.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: 1px 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: 1px 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(180px, 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;
}
.answer-confirm {
font-family: var(--sans);
font-weight: 600;
font-size: 0.82rem;
color: #0a0a0a;
background: var(--accent);
border: none;
border-radius: 0.5rem;
padding: 0.45rem 1rem;
cursor: pointer;
transition: filter 120ms;
}
.answer-confirm:hover { filter: brightness(1.08); }
.answer-confirm:disabled { opacity: 0.5; cursor: not-allowed; filter: none; }
.item-list { display: grid; gap: 0.8rem; }
.item-card {
border: 1px 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: 1px solid var(--line);
border-radius: 999px;
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);
}
@media (max-width: 640px) {
.hero { padding: 3rem 1.25rem 2rem; }
.alt-card { padding: 1.4rem 1.3rem; }
.alt-cta { flex-direction: column; align-items: flex-start; }
}