Files
portal/style/main.css
T
Bendik Aagaard LynghaugandClaude Sonnet 5 95ad459b24
Deploy / deploy (push) Successful in 1m5s
One shared, content-labeled Confirm button per alternative instead of one per row
Each AnswerRow used to carry its own ServerAction<TransitionAnswer> and
its own "Confirm" button - a page with N open rows showed N buttons.
Selecting a transition now just toggles an entry in a shared
pending_transitions map (keyed by feature_name+item_id) owned by
AlternativeCard; one button, labeled from the alternative's own
consequence field (same mechanism Subscribers' "Send" already used),
confirms every selection at once via the new batch transition_answers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 14:32:10 +02:00

655 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;
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%;
/* 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: <px>` 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 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: 8rem;
background: linear-gradient(to bottom, transparent, var(--paper));
pointer-events: none;
z-index: 1;
}
/* alternatives */
.alternatives {
max-width: 65ch;
margin: 0 auto 5rem;
padding: 0 1.5rem;
display: grid;
gap: 1.25rem;
}
@media (max-width: 700px) {
.alternatives {
padding: 0;
gap: 3.5rem;
}
.alt-card {
border-width: 0;
border-radius: 0rem;
}
}
.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;
}
.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);
}
.select-option:disabled {
opacity: 0.5;
cursor: not-allowed;
}
@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;
}
}