1
0

refactor style sheets

This commit is contained in:
Adrian Wannenmacher 2026-03-05 01:55:45 +01:00
parent f9c8882e9c
commit 32ab7594f1
Signed by: tfld
GPG Key ID: 19D986ECB1E492D5
13 changed files with 369 additions and 315 deletions

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="bar-AT"> <html lang="bar-AT" class="_regular">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
@ -16,7 +16,7 @@
<script src="/index.js" type="module"></script> <script src="/index.js" type="module"></script>
</head> </head>
<body> <body class="wb-layout">
<p>Watterblock wird geladen…</p> <p>Watterblock wird geladen…</p>
<noscript> <noscript>
<p>Das Laden des Watterblocks ist fehlgeschlagen.</p> <p>Das Laden des Watterblocks ist fehlgeschlagen.</p>

413
style.css
View File

@ -1,77 +1,141 @@
/* || IMPORTS */
@import "/ui/box.css";
@import "/ui/button.css";
@import "/ui/field.css";
@import "/ui/layout.css";
@import "/ui/round.css";
@import "/ui/session-view.css";
/* || COLORS
*
* The watterblock uses the following color system:
*
* 1. At the top, there are themes. The entire app always uses the same theme.
* Currently, there is one dark and one light theme, defaulting to dark.
* 2. Each theme contains two color sets, "regular" and "alternate". Individual
* components always use the same set, but their parent or child components
* may differ. Color sets within a theme are meant to complement each other.
* 3. Each color set contains three main colors: text, background and accent.
* 4. Each color set also contains additional colors derived from the main
* three. For the moment these need to be calculated at authoring time, but
* the intention is to use relative colors once they are baseline widely
* available, which should happen around April 2027.
* 5. The "current" color set redefines the applicable colors based on which
* color set is currently active. Components are meant to rely on it.
*/
html {
/* regular main colors */
--color-regular-text: lch(100 0 0);
--color-regular-background: lch(10 0 0);
--color-regular-accent: lch(86 84 71);
/* regular derived colors */
--color-regular-disabled-text: lch(75 0 0);
--color-regular-disabled-background: lch(35 0 0);
--color-regular-disabled-accent: lch(100 50 71);
--color-regular-focus-text: lch(90 0 0);
--color-regular-focus-background: lch(20 0 0);
--color-regular-focus-accent: lch(95 84 71);
--color-regular-field: lch(20 0 0);
/* alternate main colors */
--color-alternate-text: lch(100 0 0);
--color-alternate-background: lch(0 0 0);
--color-alternate-accent: lch(86 84 71);
/* alternate derived colors */
--color-alternate-disabled-text: lch(75 0 0);
--color-alternate-disabled-background: lch(25 0 0);
--color-alternate-disabled-accent: lch(100 50 71);
--color-alternate-focus-text: lch(90 0 0);
--color-alternate-focus-background: lch(10 0 0);
--color-alternate-focus-accent: lch(95 84 71);
--color-alternate-field: lch(5 0 0);
@media (prefers-color-scheme: light) {
/* regular main colors */
--color-regular-text: lch(0 0 0);
--color-regular-background: lch(100 0 0);
--color-regular-accent: lch(14 77 304);
/* regular derived colors */
--color-regular-disabled-text: lch(25 0 0);
--color-regular-disabled-background: lch(75 0 0);
--color-regular-disabled-accent: lch(30 77 304);
--color-regular-focus-text: lch(10 0 0);
--color-regular-focus-background: lch(90 0 0);
--color-regular-focus-accent: lch(20 77 304);
--color-regular-field: lch(95 0 0);
/* alternate main colors */
--color-alternate-text: lch(0 0 0);
--color-alternate-background: lch(90 0 0);
--color-alternate-accent: lch(14 77 304);
/* alternate derived colors */
--color-alternate-disabled-text: lch(25 0 0);
--color-alternate-disabled-background: lch(65 0 0);
--color-alternate-disabled-accent: lch(30 77 304);
--color-alternate-focus-text: lch(10 0 0);
--color-alternate-focus-background: lch(80 0 0);
--color-alternate-focus-accent: lch(20 77 304);
--color-alternate-field: lch(95 0 0);
}
}
._regular {
/* main colors */
--color-text: var(--color-regular-text);
--color-background: var(--color-regular-background);
--color-accent: var(--color-regular-accent);
/* derived colors */
--color-disabled-text: var(--color-regular-disabled-text);
--color-disabled-background: var(--color-regular-disabled-background);
--color-disabled-accent: var(--color-regular-disabled-accent);
--color-focus-text: var(--color-regular-focus-text);
--color-focus-background: var(--color-regular-focus-background);
--color-focus-accent: var(--color-regular-focus-accent);
--color-field: var(--color-regular-field);
}
._alternate {
/* main colors */
--color-text: var(--color-alternate-text);
--color-background: var(--color-alternate-background);
--color-accent: var(--color-alternate-accent);
/* derived colors */
--color-disabled-text: var(--color-alternate-disabled-text);
--color-disabled-background: var(--color-alternate-disabled-background);
--color-disabled-accent: var(--color-alternate-disabled-accent);
--color-focus-text: var(--color-alternate-focus-text);
--color-focus-background: var(--color-alternate-focus-background);
--color-focus-accent: var(--color-alternate-focus-accent);
--color-field: var(--color-alternate-field);
}
._apply {
color: var(--color-text);
background-color: var(--color-background);
}
/* || BASE STYLES
*
* The most basic styles for regular content.
*/
html { html {
height: 100%; height: 100%;
font-family: sans-serif; font-family: sans-serif;
color: var(--color-text);
--color-regular-text: white; background-color: var(--color-background);
--color-regular-background: lch(from black calc(l + 10) c h);
--color-regular-field: black;
--color-alter-text: white;
--color-alter-background: black;
--color-alter-field: var(--color-regular-background);
--color-primary-text: black;
--color-primary-background: lch(from orange calc(l + 10) c h);
--color-primary-field:
lch(from var(--color-primary-background) calc(l + 25) c h);
@media (prefers-color-scheme: light) {
--color-regular-text: black;
--color-regular-background: white;
--color-regular-field: var(--color-alter-background);
--color-alter-text: black;
--color-alter-background: lch(from white calc(l - 10) c h);
--color-alter-field: white;
--color-primary-text: black;
--color-primary-background: lch(from orange calc(l + 10) c h);
--color-primary-field:
lch(from var(--color-primary-background) calc(l + 25) c h);
}
color: var(--color-regular-text);
background-color: var(--color-regular-background);
--color-active-text: var(--color-regular-text);
--color-active-background: var(--color-regular-background);
--color-active-field: var(--color-regular-field);
}
.background {
background-color: var(--color-active-background);
}
.regular,
.alter,
.primary {
color: var(--color-active-text);
}
.regular {
--color-active-text: var(--color-regular-text);
--color-active-background: var(--color-regular-background);
--color-active-field: var(--color-regular-field);
}
.alter {
--color-active-text: var(--color-alter-text);
--color-active-background: var(--color-alter-background);
--color-active-field: var(--color-alter-field);
}
.primary {
--color-active-text: var(--color-primary-text);
--color-active-background: var(--color-primary-background);
--color-active-field: var(--color-primary-field);
}
.positioned {
margin: 0;
}
.padded {
padding: 1em;
} }
body { body {
min-height: 100%; min-height: 100%;
/* really hacky seeming workaround for some CSS weirdness */
height: 1px; height: 1px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -113,216 +177,25 @@ h6 {
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
button { /* || POSITIONING
border: none; *
border-radius: 5px; * Helpers for positioning stuff exactly as needed.
color: var(--color-primary-text); */
background: var(--color-primary-background);
padding: 1em 1em;
margin: 1em;
&:disabled { ._positioned {
color: hsl(from var(--color-primary-text) h s calc(l + 25)); margin: 0;
background-color:
hsl(from var(--color-primary-background) h s calc(l + 25));
}
&:hover,
&:focus {
color: hsl(from var(--color-primary-text) h s calc(l + 10));
background-color:
hsl(from var(--color-primary-background) h s calc(l + 10));
}
} }
header.layout { ._positioned-top {
display: flex; margin-top: 0;
align-items: center;
>* {
padding: 0.5em 0.25em;
font-size: 2em;
color: inherit;
text-decoration: none;
margin: 0;
&:first-child {
padding-left: 0.5em;
padding-right: 0.5em;
}
&:nth-child(2) {
padding-left: 0;
}
&:last-child {
padding-right: 0.5em;
}
}
} }
main.layout { ._sticky-top {
flex-grow: 1; position: sticky;
top: 0;
} }
.session-view { ._sticky-bottom {
display: flex;
flex-direction: column;
min-height: 100%;
gap: 1em;
>* {
padding-left: 1rem;
padding-right: 1rem;
}
>:first-child {
padding-top: 1rem;
}
>:last-child {
padding-bottom: 1rem;
}
.session-view-header {
display: flex;
justify-content: space-between;
align-items: center;
}
:nth-last-child(2) {
flex-grow: 1;
}
table {
font-size: 1.5em;
table-layout: fixed;
width: 100%;
text-align: center;
border-spacing: 0;
}
th:not(:last-child),
td:not(:last-child) {
border-right: 1px solid var(--color-regular-text);
}
thead {
border-bottom: 1px solid var(--color-regular-text);
position: sticky;
top: 0;
th {
border-bottom: 3px double var(--color-regular-text);
padding: 0.5em 0;
}
}
tbody {
&:not(:last-of-type) tr:last-child td {
border-bottom: 1px solid var(--color-regular-text);
}
min-height: 5px;
}
}
.current-round {
display: grid;
grid-template-areas:
"title title title title"
"they-raise current-points current-points we-raise"
"they-win they-win we-win we-win";
grid-template-columns: 1fr auto auto 1fr;
gap: 1em;
position: sticky; position: sticky;
bottom: 0; bottom: 0;
padding: 1em;
>h3 {
grid-area: title;
}
>.current-points {
grid-area: current-points;
align-content: center;
font-size: 2em;
}
>.they-raise {
grid-area: they-raise;
}
>.we-raise {
grid-area: we-raise;
}
>.they-win {
grid-area: they-win;
}
>.we-win {
grid-area: we-win;
}
}
.continue {
position: sticky;
bottom: 0;
padding: 1em;
button {
width: 100%;
}
}
.field {
display: grid;
grid-template-areas: "label" "field" "description";
grid-template-columns: 1fr;
grid-template-rows: repeat(3, min-content);
column-gap: 1ch;
row-gap: 0.5ch;
&:not(:last-child) {
margin-bottom: 1em;
}
&:has(input[type=radio]),
&:has(input[type=checkbox]) {
grid-template-areas: "field label" ". description";
grid-template-columns: min-content 1fr;
grid-template-rows: repeat(2, min-content);
}
.label {
grid-area: label;
font-weight: bold;
}
input {
grid-area: field;
padding: 0.5em;
border-radius: 5px;
background-color: var(--color-active-field);
border: solid 1px var(--color-active-text);
color: var(--color-active-text);
}
.description {
grid-area: description;
}
}
.session-head {
>:first-child {
margin-top: 0;
}
>:last-child {
margin-bottom: 0;
}
} }

7
ui/box.css Normal file
View File

@ -0,0 +1,7 @@
.wb-box {
padding: 1em;
>* {
width: 100%;
}
}

19
ui/button.css Normal file
View File

@ -0,0 +1,19 @@
.wb-button {
border: none;
border-radius: 5px;
padding: 1em;
margin: 1em;
color: var(--color-background);
background-color: var(--color-accent);
&:disabled {
color: var(--color-disabled-background);
background-color: var(--color-disabled-accent);
}
&:is(:hover, :focus):not(:disabled) {
color: var(--color-focus-background);
background-color: var(--color-focus-accent);
}
}

37
ui/field.css Normal file
View File

@ -0,0 +1,37 @@
.wb-field {
display: grid;
grid-template-areas: "label" "field" "description";
grid-template-columns: 1fr;
grid-template-rows: repeat(3, min-content);
column-gap: 1ch;
row-gap: 0.5ch;
&:not(:last-of-type) {
margin-bottom: 1em;
}
&.-selectable {
grid-template-areas: "field label" ". description";
grid-template-columns: min-content 1fr;
grid-template-rows: repeat(2, min-content);
}
>.label {
grid-area: label;
font-weight: bold;
}
>.field {
grid-area: field;
padding: 0.5em;
border-radius: 5px;
background-color: var(--color-field);
border: solid 1px var(--color-text);
color: var(--color-text);
}
>.description {
grid-area: description;
}
}

31
ui/layout.css Normal file
View File

@ -0,0 +1,31 @@
.wb-layout {
>.header {
display: flex;
align-items: center;
>* {
padding: 0.5em 0.25em;
font-size: 2em;
color: inherit;
text-decoration: none;
margin: 0;
&:first-child {
padding-left: 0.5em;
padding-right: 0.5em;
}
&:nth-child(2) {
padding-left: 0;
}
&:last-child {
padding-right: 0.5em;
}
}
}
>.main {
flex-grow: 1;
}
}

View File

@ -46,11 +46,11 @@ export default class Layout {
return m("p", "Öffne Datenbank, bitte warten…"); return m("p", "Öffne Datenbank, bitte warten…");
return m.fragment([ return m.fragment([
m("header.layout.alter.background", [ m("header.header._alternate._apply", [
backHref ? m(m.route.Link, { href: backHref }, "←") : null, backHref ? m(m.route.Link, { href: backHref }, "←") : null,
m("h1", "Watterblock"), m("h1", "Watterblock"),
]), ]),
m("main.layout", children), m("main.main", children),
]); ]);
} }
} }

35
ui/round.css Normal file
View File

@ -0,0 +1,35 @@
.wb-round {
display: grid;
grid-template-areas:
"title title title title"
"they-raise current-points current-points we-raise"
"they-win they-win we-win we-win";
grid-template-columns: 1fr auto auto 1fr;
gap: 1em;
>.title {
grid-area: title;
}
>.current {
grid-area: current-points;
align-content: center;
font-size: 2em;
}
>.theyraise {
grid-area: they-raise;
}
>.theywin {
grid-area: they-win;
}
>.weraise {
grid-area: we-raise;
}
>.wewin {
grid-area: we-win;
}
}

View File

@ -6,24 +6,24 @@ export default class RoundView {
/** @param { { attrs: { model: Round } } } param The round model to use. */ /** @param { { attrs: { model: Round } } } param The round model to use. */
view({ attrs: { model } }) { view({ attrs: { model } }) {
return m("section.current-round.alter.background", [ return m("section.wb-round", [
m("h3.positioned", "Rundnschreiba"), m("h3.title._positioned", "Rundnschreiba"),
m("span.current-points", `${model.points}`), m("span.current", `${model.points}`),
m("button.they-raise.positioned", m("button.wb-button.theyraise._positioned",
{ {
onclick: () => model.raise(Team.They), onclick: () => model.raise(Team.They),
disabled: !model.canRaise(Team.They), disabled: !model.canRaise(Team.They),
}, },
"se erhöhn", "se erhöhn",
), ),
m("button.we-raise.positioned", m("button.wb-button.weraise._positioned",
{ {
onclick: () => model.raise(Team.We), onclick: () => model.raise(Team.We),
disabled: !model.canRaise(Team.We), disabled: !model.canRaise(Team.We),
}, },
"mia erhöhn", "mia erhöhn",
), ),
m("button.they-win.positioned", m("button.wb-button.theywin._positioned",
{ {
onclick: () => { onclick: () => {
model.winner = Team.They; model.winner = Team.They;
@ -36,7 +36,7 @@ export default class RoundView {
}, },
"se habn gwonnen", "se habn gwonnen",
), ),
m("button.we-win.positioned", m("button.wb-button.wewin._positioned",
{ {
onclick: () => { onclick: () => {
model.winner = Team.We; model.winner = Team.We;

51
ui/session-view.css Normal file
View File

@ -0,0 +1,51 @@
.wb-session-view {
display: flex;
flex-direction: column;
min-height: 100%;
gap: 1em;
>* {
padding-left: 1rem;
padding-right: 1rem;
&:first-child {
padding-top: 1rem;
}
&:last-child {
padding-bottom: 1rem;
}
}
>.header {
display: flex;
justify-content: space-between;
align-items: center;
}
>.spacer {
flex-grow: 1;
}
}
/* results table styles */
.wb-session-view table.results {
font-size: 1.5em;
table-layout: fixed;
width: 100%;
text-align: center;
border-spacing: 0;
:is(th, td):not(:last-child) {
border-right: 1px solid var(--color-text);
}
thead th {
border-bottom: 3px double var(--color-text);
padding: 0.5em 0;
}
tbody:not(:last-of-type) tr:last-child td {
border-bottom: 1px solid var(--color-text);
}
}

View File

@ -13,27 +13,27 @@ export default class SessionView {
let res = model.result; let res = model.result;
return m("article.session-view", [ return m("article.wb-session-view", [
( model.games.length === 0 && model.currentGame === null) ( model.games.length === 0 && model.currentGame === null)
? m(SessionHead, { model }) ? m(".spacer", m(SessionHead, { model }))
: m.fragment([ : m.fragment([
m(".session-view-header", [ m("header.header", [
m("h2.positioned", "Satz"), m("h2._positioned", "Satz"),
m( m(
"button.positioned", "button.wb-button._positioned",
{ onclick: () => this.#headOpen = !this.#headOpen }, { onclick: () => this.#headOpen = !this.#headOpen },
"Regln" "Regln"
), ),
]), ]),
this.#headOpen this.#headOpen
? m(".alter.background.padded", m(SessionHead, { model })) ? m("._alternate._apply.wb-box", m(SessionHead, { model }))
: null, : null,
m("section.record", [ m("section.spacer", [
this.#headOpen ? m("h3", "Mitschrift") : null, this.#headOpen ? m("h3._positioned-top", "Mitschrift") : null,
m("table", [ m("table.results", [
m("thead.background", [ m("thead._sticky-top._apply", [
m("tr", [ m("tr", [
m("th", [ m("th", [
model.theirTeam ? model.theirTeam : "Se", model.theirTeam ? model.theirTeam : "Se",
@ -55,12 +55,12 @@ export default class SessionView {
]), ]),
]), ]),
model.currentGame !== null m(".wb-box._alternate._apply._sticky-bottom", [
? model.currentGame.currentRound !== null model.currentGame !== null
&& model.currentGame.currentRound !== null
? m(RoundView, { model: model.currentGame.currentRound }) ? m(RoundView, { model: model.currentGame.currentRound })
: null : m(
: m(".continue.alter.background", [ "button.wb-button._positioned",
m("button.positioned",
{ {
onclick: () => { onclick: () => {
model.anotherGame(); model.anotherGame();
@ -70,8 +70,9 @@ export default class SessionView {
}); });
}, },
}, },
"no a spiel"), "no a spiel"
]), ),
]),
]); ]);
} }
} }

View File

@ -6,32 +6,32 @@ import Session from "/models/session.js";
export default class SessionHead { export default class SessionHead {
/** @param {{ attrs: { model: Session } }} param The session model to use. */ /** @param {{ attrs: { model: Session } }} param The session model to use. */
view({ attrs: { model } }) { view({ attrs: { model } }) {
return m("section.session-head", [ return m("section", [
m("h3", "Satzeinstellungen"), m("h3._positioned", "Satzeinstellungen"),
m("section.session-head-names", [ m("section", [
m("h4", "Teamnamen"), m("h4", "Teamnamen"),
m("label.field", [ m("label.wb-field", [
m("span.label", "Nam von eana"), m("span.label", "Nam von eana"),
m("input", { m("input.field", {
placeholder: "Se", placeholder: "Se",
value: model.theirTeam, value: model.theirTeam,
oninput: (e) => model.theirTeam = e.target.value, oninput: (e) => model.theirTeam = e.target.value,
}), }),
]), ]),
m("label.field", [ m("label.wb-field", [
m("span.label", "Nam von ins"), m("span.label", "Nam von ins"),
m("input", { m("input.field", {
placeholder: "Mia", placeholder: "Mia",
value: model.ourTeam, value: model.ourTeam,
oninput: (e) => model.ourTeam = e.target.value, oninput: (e) => model.ourTeam = e.target.value,
}), }),
]), ]),
]), ]),
m("section.session-head-base", [ m("section", [
m("h4", "Grundregln"), m("h4", "Grundregln"),
m("label.field", [ m("label.wb-field", [
m("span.label", "Punkte zum gwinna"), m("span.label", "Punkte zum gwinna"),
m("input", { m("input.field", {
placeholder: "mindestns 1", placeholder: "mindestns 1",
type: "number", type: "number",
value: model.rules.goal, value: model.rules.goal,
@ -45,10 +45,10 @@ export default class SessionHead {
}), }),
]), ]),
]), ]),
m("section.session-head-raising", [ m("section", [
m("h4", "Erhöhn"), m("h4", "Erhöhn"),
m("label.field", [ m("label.wb-field.-selectable", [
m("input", { m("input.field", {
type: "radio", type: "radio",
checked: model.rules.raising === RaisingRule.UnlessStricken, checked: model.rules.raising === RaisingRule.UnlessStricken,
oninput: () => model.rules.raising = RaisingRule.UnlessStricken, oninput: () => model.rules.raising = RaisingRule.UnlessStricken,
@ -61,8 +61,8 @@ export default class SessionHead {
m("em", "So steht's in di Regln von da Tirola Wattagmeinschaft"), m("em", "So steht's in di Regln von da Tirola Wattagmeinschaft"),
]), ]),
]), ]),
m("label.field", [ m("label.wb-field.-selectable", [
m("input", { m("input.field", {
type: "radio", type: "radio",
checked: model.rules.raising === RaisingRule.UntilEnough, checked: model.rules.raising === RaisingRule.UntilEnough,
oninput: () => model.rules.raising = RaisingRule.UntilEnough, oninput: () => model.rules.raising = RaisingRule.UntilEnough,

View File

@ -8,7 +8,7 @@ export default class SessionList {
return m("section", [ return m("section", [
m(m.route.Link, { m(m.route.Link, {
href: "/", href: "/",
selector: "button", selector: "button.wb-button",
options: { options: {
state: { newSession: true }, state: { newSession: true },
}, },
@ -19,7 +19,7 @@ export default class SessionList {
m.route.Link, m.route.Link,
{ {
href: "/", href: "/",
selector: "button", selector: "button.wb-button",
params: { session: s.id }, params: { session: s.id },
onclick: () => onSelect(s), onclick: () => onSelect(s),
}, },