Deploy / deploy (push) Successful in 34s
Both stable, but svh pins to the *smallest* viewport (toolbar visible), which never grows once mobile Safari auto-collapses its toolbar on the first scroll. The actual visible screen becomes taller than a box sized with svh, so content below it appears to snap into the newly-revealed space the moment the toolbar collapses - exactly the "text jumps down with the expanded area" behavior reported. lvh pins to the toolbar-collapsed size instead, matching the settled state scrolling actually lands on, so there's no gap to reveal. svh is the right choice for the opposite case (a fixed element that must never be hidden behind the toolbar), not a hero at the top of a scrolling page - wrong tool for this particular job.
548 lines
13 KiB
CSS
548 lines
13 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.1lvmin) 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. lvmin (large-viewport min), not plain vmin or svmin -
|
|
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 would be stable too, but pinned to the
|
|
*smallest* (toolbar-visible) state - wrong end for a page whose
|
|
toolbar collapses the moment you scroll: same mismatch as
|
|
.hero-yes below, just for font-size instead of height. lvmin
|
|
matches the settled (toolbar-collapsed) state scrolling actually
|
|
lands on.
|
|
Plain-px fallback declared first: an engine that doesn't
|
|
understand lvmin/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.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: 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%;
|
|
/* lvh (large viewport height), not svh: svh pins to the *smallest*
|
|
viewport (toolbar visible), which never grows once the browser
|
|
auto-collapses its toolbar on the first scroll - so the actual
|
|
visible screen becomes taller than this box, and the content
|
|
below appears to snap into the newly-revealed space the moment
|
|
that happens. lvh pins to the toolbar-collapsed size instead,
|
|
matching the settled state scrolling actually lands on, so
|
|
there's no gap to reveal. (svh is the right choice for the
|
|
opposite case - a fixed element that must never be hidden behind
|
|
the toolbar - not a hero at the top of a scrolling page.)
|
|
Plain-vh fallback declared first - an engine without lvh 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: 100lvh;
|
|
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; }
|
|
}
|