UI Improvements #4

Merged
jhalitaksoy merged 9 commits from develop into main 2026-10-17 03:36:44 +03:00
10 changed files with 390 additions and 288 deletions
Showing only changes of commit 342a2455fa - Show all commits

View File

@ -1,108 +1,111 @@
/* /*
* Mancala look for keycloak.v2's login pages. Loaded after keycloak.v2's styles.css. * The web app's look for keycloak.v2's login pages (docs/specs/theme-polish). Loaded after keycloak.v2's styles.css.
* Colors come from LightTheme and DarkTheme in packages/core/src/theme; text colors are darker (or lighter) than * js/theme.js puts the player's theme on <html> as t-ceviz, t-gece or t-yesim; their colors are copied from
* the game's where the game's own would not be readable on the board color. * apps/frontend/src/styles/app.css, keep them in step. The dark themes also get pf-v5-theme-dark.
* keycloak.v2 adds pf-v5-theme-dark to <html> when the system prefers a dark color scheme.
*/ */
:root { @import url("https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,400;12..96,600;12..96,700;12..96,800&display=swap");
--mancala-bg: #bbbbbb;
--mancala-bar: #7b7b7b;
--mancala-bar-text: #ebebeb;
--mancala-board: #9b9b9b;
--mancala-pit: #c4c4c4;
--mancala-pit-readonly: #b0b0b0;
--mancala-pit-edge: #4d4d4d;
--mancala-pit-shadow: rgba(0, 0, 0, 0.22);
--mancala-stone: #5b5b5b;
--mancala-stone-hover: #474747;
--mancala-stone-text: #f2f2f2;
--mancala-ink: #262626;
--mancala-ink-soft: #333333;
--mancala-ink-strong: #000000;
--mancala-hover: rgba(0, 0, 0, 0.08);
--mancala-divider: #6b6b6b;
--mancala-danger: #6b0900;
--mancala-board-image: url("../img/board.svg");
--mancala-bar-height: 57px;
--mancala-font: serif;
--pf-v5-global--FontFamily--text: var(--mancala-font); .t-ceviz {
--pf-v5-global--FontFamily--heading: var(--mancala-font); --bg: #1f1712; --surface: #2a2019; --surface2: #382a20; --line: rgba(246, 234, 216, .10);
--pf-v5-global--Color--100: var(--mancala-ink); --ink: #f6ead8; --muted: #c4ad93; --accent: #f2b84b; --onAccent: #2a1a0a;
--pf-v5-global--Color--200: var(--mancala-ink-soft); --accentSoft: rgba(242, 184, 75, .45); --live: #ff8a73; --miniBg: #5b3d28;
--pf-v5-global--link--Color: var(--mancala-ink); color-scheme: dark;
--pf-v5-global--link--Color--hover: var(--mancala-ink-strong);
--pf-v5-global--danger-color--100: var(--mancala-danger);
} }
:root.pf-v5-theme-dark { .t-gece {
--mancala-bg: #414141; --bg: #0c1220; --surface: #141c30; --surface2: #1d2744; --line: rgba(238, 241, 248, .09);
--mancala-bar: #313131; --ink: #eef1f8; --muted: #9aa6c2; --accent: #e2b36a; --onAccent: #241707;
--mancala-bar-text: #aaaaaa; --accentSoft: rgba(226, 179, 106, .45); --live: #ff8a7a; --miniBg: #1d2a4d;
--mancala-board: #313131; color-scheme: dark;
--mancala-pit: #606060; }
--mancala-pit-readonly: #4b4b4b;
--mancala-pit-edge: #8a8a8a; .t-yesim {
--mancala-pit-shadow: rgba(0, 0, 0, 0.45); --bg: #e9ebe3; --surface: #f7f8f3; --surface2: #dde0d3; --line: rgba(29, 39, 33, .12);
--mancala-stone: #aaaaaa; --ink: #1d2721; --muted: #4f5d55; --accent: #2a7650; --onAccent: #ffffff;
--mancala-stone-hover: #bdbdbd; --accentSoft: rgba(42, 118, 80, .4); --live: #b43a26; --miniBg: #d3d6c8;
--mancala-stone-text: #1e1e1e; color-scheme: light;
--mancala-ink: #e0e0e0; }
--mancala-ink-soft: #bbbbbb;
--mancala-ink-strong: #ffffff; :root:is(.t-ceviz, .t-gece, .t-yesim) {
--mancala-hover: rgba(255, 255, 255, 0.08); --font: 'Bricolage Grotesque', system-ui, sans-serif;
--mancala-divider: #555555; --bar-height: 69px;
--mancala-danger: #ff8a80;
--mancala-board-image: url("../img/board-dark.svg"); --pf-v5-global--FontFamily--text: var(--font);
--pf-v5-global--FontFamily--heading: var(--font);
--pf-v5-global--Color--100: var(--ink);
--pf-v5-global--Color--200: var(--muted);
--pf-v5-global--BackgroundColor--100: var(--surface);
--pf-v5-global--BackgroundColor--200: var(--bg);
--pf-v5-global--BorderColor--100: var(--line);
--pf-v5-global--link--Color: var(--ink);
--pf-v5-global--link--Color--hover: var(--accent);
--pf-v5-global--primary-color--100: var(--accent);
--pf-v5-global--danger-color--100: var(--live);
--pf-v5-global--danger-color--200: var(--live);
} }
.login-pf body { .login-pf body {
background: var(--mancala-bg); background: var(--bg);
font-family: var(--mancala-font); color: var(--ink);
font-family: var(--font);
} }
/* Header: the game's app bar with its logo and title */ /* Header: the app's header with its logo and name */
#kc-header { #kc-header {
position: fixed; position: fixed;
inset: 0 0 auto 0; inset: 0 0 auto 0;
z-index: 10; z-index: 10;
height: var(--mancala-bar-height); height: var(--bar-height);
margin: 0; margin: 0;
padding: 0 4vw; padding: 0 32px;
box-sizing: border-box;
display: flex; display: flex;
align-items: center; align-items: center;
background: var(--mancala-bar); background: var(--bg);
border-bottom: 1px solid var(--line);
} }
/* The realm's name is replaced by the app's, which is "Köçürme" in Turkish. */
#kc-header-wrapper { #kc-header-wrapper {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
margin: 0; margin: 0;
padding: 0; padding: 0;
font-family: var(--mancala-font); font-size: 0;
font-size: 2em;
font-weight: bold;
line-height: 1;
letter-spacing: normal; letter-spacing: normal;
text-transform: none; text-transform: none;
text-align: start; color: var(--ink) !important;
color: var(--mancala-bar-text) !important;
} }
#kc-header-wrapper::before { #kc-header-wrapper::before {
content: ""; content: "";
flex: none; flex: none;
width: 30px; width: 34px;
height: 30px; height: 34px;
background: url("../img/logo.svg") center / contain no-repeat; background: var(--accent);
-webkit-mask: url("../img/logo-mask.svg") center / contain no-repeat;
mask: url("../img/logo-mask.svg") center / contain no-repeat;
}
#kc-header-wrapper::after {
content: "Mancala";
font-family: var(--font);
font-size: 24px;
font-weight: 800;
letter-spacing: -.02em;
line-height: 1;
}
:lang(tr) #kc-header-wrapper::after {
content: "Köçürme";
} }
.pf-v5-c-login { .pf-v5-c-login {
min-height: 100vh; min-height: 100vh;
padding-block-start: calc(var(--mancala-bar-height) + 2rem); padding-block-start: calc(var(--bar-height) + 2rem);
padding-block-end: 2rem; padding-block-end: 2rem;
} }
@ -112,24 +115,42 @@
grid-template-columns: minmax(0, 30rem); grid-template-columns: minmax(0, 30rem);
} }
/* Card: the board, with a small board at the start of a game on top */ /* Card: the app's card, with a board at the start of a game on top */
.pf-v5-c-login__main { .pf-v5-c-login__main {
--pf-v5-c-login__main--BackgroundColor: var(--mancala-board); --pf-v5-c-login__main--BackgroundColor: var(--surface);
--pf-v5-c-login__main--BoxShadow: 0 8px 24px rgba(0, 0, 0, 0.18); --pf-v5-c-login__main--BoxShadow: 0 20px 50px rgba(0, 0, 0, .25);
--pf-v5-c-login__main-footer-band--BorderTopColor: var(--mancala-divider); --pf-v5-c-login__main-footer-band--BorderTopColor: var(--line);
--pf-v5-c-login__main-footer-band--BackgroundColor: transparent;
position: relative;
padding-block-start: 2rem; padding-block-start: 2rem;
border-radius: 24px; border-radius: 28px;
color: var(--mancala-ink); color: var(--ink);
}
.pf-v5-c-login__main::before,
.pf-v5-c-login__main::after {
content: "";
display: block;
width: min(calc(100% - 4rem), 280px);
aspect-ratio: 328 / 92;
margin: 0 auto;
} }
.pf-v5-c-login__main::before { .pf-v5-c-login__main::before {
content: ""; background: var(--miniBg);
display: block; -webkit-mask: url("../img/board-mask.svg") center / contain no-repeat;
width: min(calc(100% - 4rem), 260px); mask: url("../img/board-mask.svg") center / contain no-repeat;
aspect-ratio: 312 / 76; }
margin: 0 auto;
background: var(--mancala-board-image) center / contain no-repeat; .pf-v5-c-login__main::after {
position: absolute;
top: 2rem;
left: 50%;
transform: translateX(-50%);
background: var(--accent);
-webkit-mask: url("../img/stones-mask.svg") center / contain no-repeat;
mask: url("../img/stones-mask.svg") center / contain no-repeat;
} }
.pf-v5-c-login__main-header { .pf-v5-c-login__main-header {
@ -138,43 +159,56 @@
} }
#kc-page-title { #kc-page-title {
color: var(--mancala-ink); color: var(--ink);
font-family: var(--mancala-font); font-family: var(--font);
font-size: 1.5rem; font-size: 1.6rem;
font-weight: bold; font-weight: 800;
letter-spacing: -.02em;
} }
.pf-v5-c-login__main hr { .pf-v5-c-login__main hr {
border-color: var(--mancala-divider); border-color: var(--line);
} }
.pf-v5-c-login__main a:not(.pf-v5-c-button) { .pf-v5-c-login__main a:not(.pf-v5-c-button) {
color: var(--mancala-ink); color: var(--ink);
font-weight: 700;
text-decoration: underline; text-decoration: underline;
text-decoration-color: var(--accentSoft);
text-underline-offset: 3px;
} }
.pf-v5-c-login__main a:not(.pf-v5-c-button):hover { .pf-v5-c-login__main a:not(.pf-v5-c-button):hover {
color: var(--mancala-ink-strong); color: var(--accent);
text-decoration-color: currentColor;
} }
/* Inputs: pits */ .pf-v5-c-form__label,
.pf-v5-c-form__label-text {
color: var(--ink);
font-weight: 700;
}
.pf-v5-c-login__main .pf-v5-c-helper-text,
.pf-v5-c-login__main .pf-v5-c-check__label,
.pf-v5-c-login__main-footer-band {
color: var(--muted);
}
/* Inputs */
.pf-v5-c-form-control { .pf-v5-c-form-control {
--pf-v5-c-form-control--BackgroundColor: var(--mancala-pit); --pf-v5-c-form-control--BackgroundColor: var(--surface2);
--pf-v5-c-form-control--Color: var(--mancala-ink); --pf-v5-c-form-control--Color: var(--ink);
--pf-v5-c-form-control--m-placeholder--Color: var(--mancala-ink-soft); --pf-v5-c-form-control--m-placeholder--Color: var(--muted);
--pf-v5-c-form-control--m-readonly--BackgroundColor: var(--mancala-pit-readonly); --pf-v5-c-form-control--m-readonly--BackgroundColor: var(--bg);
--pf-v5-c-form-control__toggle-icon--Color: var(--mancala-ink); --pf-v5-c-form-control__toggle-icon--Color: var(--ink);
--pf-v5-c-form-control--PaddingLeft: 1rem; --pf-v5-c-form-control--PaddingLeft: 1rem;
--pf-v5-c-form-control--PaddingRight: 1rem; --pf-v5-c-form-control--PaddingRight: 1rem;
border: 1px solid var(--mancala-pit-edge); min-height: 46px;
border-radius: 999px; border: 1.5px solid var(--line);
box-shadow: inset 0 2px 5px var(--mancala-pit-shadow); border-radius: 14px;
font-family: var(--mancala-font); font-family: var(--font);
}
.pf-v5-c-form-control:has(textarea) {
border-radius: 16px;
} }
.pf-v5-c-form-control::before, .pf-v5-c-form-control::before,
@ -184,6 +218,7 @@
.pf-v5-c-form-control > :is(input, select, textarea) { .pf-v5-c-form-control > :is(input, select, textarea) {
font-family: inherit; font-family: inherit;
color: var(--ink);
background-color: transparent; background-color: transparent;
border-radius: inherit; border-radius: inherit;
} }
@ -192,103 +227,194 @@
padding-inline-start: 1rem; padding-inline-start: 1rem;
} }
.pf-v5-c-form-control > select option {
background: var(--surface);
color: var(--ink);
}
.pf-v5-c-form-control > :is(input, select, textarea):focus { .pf-v5-c-form-control > :is(input, select, textarea):focus {
outline: none; outline: none;
} }
.pf-v5-c-form-control:hover { .pf-v5-c-form-control:hover {
border-color: var(--mancala-ink); border-color: var(--muted);
} }
.pf-v5-c-form-control:focus-within { .pf-v5-c-form-control:focus-within {
border-color: var(--mancala-ink); border-color: var(--accent);
outline: 2px solid var(--mancala-ink); box-shadow: 0 0 0 3px var(--accentSoft);
outline-offset: 2px;
} }
.pf-v5-c-form-control.pf-m-error { .pf-v5-c-form-control.pf-m-error {
border-color: var(--mancala-danger); border-color: var(--live);
box-shadow: inset 0 2px 5px var(--mancala-pit-shadow), 0 0 0 1px var(--mancala-danger); }
.pf-v5-c-form-control.pf-m-error .pf-v5-c-form-control__icon.pf-m-status {
color: var(--live);
} }
.pf-v5-c-login__main .pf-v5-c-helper-text__item.pf-m-error, .pf-v5-c-login__main .pf-v5-c-helper-text__item.pf-m-error,
.pf-v5-c-login__main .pf-v5-c-helper-text__item.pf-m-error .pf-v5-c-helper-text__item-text, .pf-v5-c-login__main .pf-v5-c-helper-text__item.pf-m-error .pf-v5-c-helper-text__item-text,
.pf-v5-c-login__main .pf-v5-c-helper-text__item-text.pf-m-error { .pf-v5-c-login__main .pf-v5-c-helper-text__item-text.pf-m-error {
color: var(--mancala-danger); color: var(--live);
font-weight: 700;
} }
/* Password field and its show button form one pill */ /* The language select in the card's header */
.pf-v5-c-input-group > .pf-v5-c-input-group__item:first-child:not(:last-child) .pf-v5-c-form-control { .pf-v5-c-login__main-header-utilities .pf-v5-c-form-control {
border-start-end-radius: 0; min-height: 40px;
border-end-end-radius: 0; border-radius: 20px;
} }
.pf-v5-c-input-group > .pf-v5-c-input-group__item:last-child:not(:first-child) .pf-v5-c-button { /* Password field and its show button form one field: the group has the border */
border-radius: 0 999px 999px 0;
.pf-v5-c-input-group {
border: 1.5px solid var(--line);
border-radius: 14px;
background: var(--surface2);
overflow: hidden;
} }
/* Buttons: pill shaped like the game's */ .pf-v5-c-input-group:hover {
border-color: var(--muted);
}
.pf-v5-c-input-group:focus-within {
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accentSoft);
}
.pf-v5-c-input-group .pf-v5-c-form-control,
.pf-v5-c-input-group .pf-v5-c-form-control:focus-within {
border: none;
border-radius: 0;
box-shadow: none;
}
.pf-v5-c-input-group .pf-v5-c-button.pf-m-control {
--pf-v5-c-button--BorderRadius: 0;
--pf-v5-c-button--after--BorderRadius: 0;
border-radius: 0;
}
.pf-v5-c-input-group .pf-v5-c-button.pf-m-control::after {
border: none;
}
/* Buttons: the app's pills */
.pf-v5-c-button { .pf-v5-c-button {
--pf-v5-c-button--BorderRadius: 999px; --pf-v5-c-button--BorderRadius: 23px;
--pf-v5-c-button--after--BorderRadius: 999px; --pf-v5-c-button--after--BorderRadius: 23px;
--pf-v5-c-button--FontWeight: 700;
--pf-v5-c-button--m-primary--BackgroundColor: var(--mancala-stone); --pf-v5-c-button--m-primary--BackgroundColor: var(--accent);
--pf-v5-c-button--m-primary--hover--BackgroundColor: var(--mancala-stone-hover); --pf-v5-c-button--m-primary--hover--BackgroundColor: var(--accent);
--pf-v5-c-button--m-primary--focus--BackgroundColor: var(--mancala-stone-hover); --pf-v5-c-button--m-primary--focus--BackgroundColor: var(--accent);
--pf-v5-c-button--m-primary--active--BackgroundColor: var(--mancala-stone-hover); --pf-v5-c-button--m-primary--active--BackgroundColor: var(--accent);
--pf-v5-c-button--m-primary--Color: var(--mancala-stone-text); --pf-v5-c-button--m-primary--Color: var(--onAccent);
--pf-v5-c-button--m-primary--hover--Color: var(--mancala-stone-text); --pf-v5-c-button--m-primary--hover--Color: var(--onAccent);
--pf-v5-c-button--m-primary--focus--Color: var(--mancala-stone-text); --pf-v5-c-button--m-primary--focus--Color: var(--onAccent);
--pf-v5-c-button--m-primary--active--Color: var(--mancala-stone-text); --pf-v5-c-button--m-primary--active--Color: var(--onAccent);
--pf-v5-c-button--m-secondary--Color: var(--mancala-ink); --pf-v5-c-button--m-secondary--BackgroundColor: transparent;
--pf-v5-c-button--m-secondary--hover--Color: var(--mancala-ink); --pf-v5-c-button--m-secondary--Color: var(--ink);
--pf-v5-c-button--m-secondary--focus--Color: var(--mancala-ink); --pf-v5-c-button--m-secondary--hover--Color: var(--ink);
--pf-v5-c-button--m-secondary--active--Color: var(--mancala-ink); --pf-v5-c-button--m-secondary--focus--Color: var(--ink);
--pf-v5-c-button--m-secondary--after--BorderColor: var(--mancala-ink); --pf-v5-c-button--m-secondary--active--Color: var(--ink);
--pf-v5-c-button--m-secondary--hover--after--BorderColor: var(--mancala-ink); --pf-v5-c-button--m-secondary--after--BorderColor: var(--line);
--pf-v5-c-button--m-secondary--focus--after--BorderColor: var(--mancala-ink); --pf-v5-c-button--m-secondary--hover--after--BorderColor: var(--muted);
--pf-v5-c-button--m-secondary--active--after--BorderColor: var(--mancala-ink); --pf-v5-c-button--m-secondary--focus--after--BorderColor: var(--muted);
--pf-v5-c-button--m-secondary--hover--BackgroundColor: var(--mancala-hover); --pf-v5-c-button--m-secondary--active--after--BorderColor: var(--muted);
--pf-v5-c-button--m-secondary--focus--BackgroundColor: var(--mancala-hover); --pf-v5-c-button--m-secondary--hover--BackgroundColor: transparent;
--pf-v5-c-button--m-secondary--active--BackgroundColor: var(--mancala-hover); --pf-v5-c-button--m-secondary--focus--BackgroundColor: transparent;
--pf-v5-c-button--m-secondary--active--BackgroundColor: transparent;
--pf-v5-c-button--m-link--Color: var(--mancala-ink); --pf-v5-c-button--m-link--Color: var(--ink);
--pf-v5-c-button--m-link--hover--Color: var(--mancala-ink-strong); --pf-v5-c-button--m-link--hover--Color: var(--accent);
--pf-v5-c-button--m-link--focus--Color: var(--mancala-ink-strong); --pf-v5-c-button--m-link--focus--Color: var(--accent);
--pf-v5-c-button--m-link--active--Color: var(--mancala-ink-strong); --pf-v5-c-button--m-link--active--Color: var(--accent);
--pf-v5-c-button--m-control--BackgroundColor: var(--mancala-pit); --pf-v5-c-button--m-control--BackgroundColor: var(--surface2);
--pf-v5-c-button--m-control--hover--BackgroundColor: var(--mancala-pit); --pf-v5-c-button--m-control--hover--BackgroundColor: var(--surface2);
--pf-v5-c-button--m-control--focus--BackgroundColor: var(--mancala-pit); --pf-v5-c-button--m-control--focus--BackgroundColor: var(--surface2);
--pf-v5-c-button--m-control--active--BackgroundColor: var(--mancala-pit); --pf-v5-c-button--m-control--active--BackgroundColor: var(--surface2);
--pf-v5-c-button--m-control--Color: var(--mancala-ink); --pf-v5-c-button--m-control--Color: var(--ink);
--pf-v5-c-button--m-control--hover--Color: var(--mancala-ink-strong); --pf-v5-c-button--m-control--hover--Color: var(--ink);
--pf-v5-c-button--m-control--focus--Color: var(--mancala-ink-strong); --pf-v5-c-button--m-control--focus--Color: var(--ink);
--pf-v5-c-button--m-control--active--Color: var(--mancala-ink-strong); --pf-v5-c-button--m-control--active--Color: var(--ink);
font-family: var(--mancala-font); --pf-v5-c-button--m-control--after--BorderColor: var(--line);
--pf-v5-c-button--m-control--hover--after--BorderColor: var(--muted);
font-family: var(--font);
} }
.pf-v5-c-button.pf-m-primary { .pf-v5-c-button.pf-m-primary {
font-weight: bold; min-height: 46px;
font-size: 15px;
} }
.pf-v5-c-button.pf-m-control::after { .pf-v5-c-button.pf-m-primary:hover {
border: 1px solid var(--mancala-pit-edge); filter: brightness(1.06);
border-radius: inherit;
} }
.pf-v5-c-button.pf-m-control:hover::after { /* Secondary buttons are the app's ghost buttons: a thin line, a stronger one on hover */
border-color: var(--mancala-ink); .pf-v5-c-button.pf-m-secondary::after {
border: 1.5px solid var(--line);
}
.pf-v5-c-button.pf-m-secondary:hover::after {
border-color: var(--muted);
} }
.pf-v5-c-button:focus-visible { .pf-v5-c-button:focus-visible {
outline: 2px solid var(--mancala-ink); outline: 3px solid var(--accent);
outline-offset: 2px; outline-offset: 3px;
} }
.pf-v5-c-check__input { .pf-v5-c-check__input {
accent-color: var(--mancala-stone); accent-color: var(--accent);
}
/* Messages: info, success, warning and errors above the form */
.pf-v5-c-alert {
--pf-v5-c-alert--BackgroundColor: var(--surface2);
--pf-v5-c-alert--BoxShadow: none;
--pf-v5-c-alert--BorderTopWidth: 0;
--pf-v5-c-alert__title--Color: var(--ink);
--pf-v5-c-alert__icon--Color: var(--accent);
--pf-v5-c-alert--m-danger__icon--Color: var(--live);
--pf-v5-c-alert--m-danger__title--Color: var(--ink);
--pf-v5-c-alert--m-warning__title--Color: var(--ink);
--pf-v5-c-alert--m-success__title--Color: var(--ink);
--pf-v5-c-alert--m-info__title--Color: var(--ink);
border-radius: 14px;
}
.pf-v5-c-alert.pf-m-danger {
box-shadow: inset 4px 0 0 var(--live);
}
@media (max-width: 640px) {
:root:is(.t-ceviz, .t-gece, .t-yesim) {
--bar-height: 61px;
}
#kc-header {
padding: 0 16px;
}
#kc-header-wrapper::before {
width: 28px;
height: 28px;
}
#kc-header-wrapper::after {
font-size: 20px;
}
.pf-v5-c-login__main {
border-radius: 24px;
}
} }

View File

@ -1,26 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 312 76">
<!-- A board at the start of a game: two stores and 2 x 6 pits with four stones each. Colors from DarkTheme. -->
<defs>
<g id="pit">
<circle r="16" fill="#606060"/>
<circle cx="-7.5" r="2.2" fill="#252525"/>
<circle cx="-2.5" r="2.2" fill="#252525"/>
<circle cx="2.5" r="2.2" fill="#252525"/>
<circle cx="7.5" r="2.2" fill="#252525"/>
</g>
</defs>
<rect x="0" y="0" width="28" height="76" rx="14" fill="#606060"/>
<rect x="284" y="0" width="28" height="76" rx="14" fill="#606060"/>
<use xlink:href="#pit" x="56" y="18"/>
<use xlink:href="#pit" x="96" y="18"/>
<use xlink:href="#pit" x="136" y="18"/>
<use xlink:href="#pit" x="176" y="18"/>
<use xlink:href="#pit" x="216" y="18"/>
<use xlink:href="#pit" x="256" y="18"/>
<use xlink:href="#pit" x="56" y="58"/>
<use xlink:href="#pit" x="96" y="58"/>
<use xlink:href="#pit" x="136" y="58"/>
<use xlink:href="#pit" x="176" y="58"/>
<use xlink:href="#pit" x="216" y="58"/>
<use xlink:href="#pit" x="256" y="58"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,24 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 328 92">
<!-- Mask of a board at the start of a game: the board with its two stores and 2 x 6 pits cut out. The theme fills
it with its board color; img/stones-mask.svg puts the stones in the pits. -->
<mask id="holes">
<rect width="328" height="92" fill="#fff"/>
<g fill="#000">
<rect x="12" y="10" width="28" height="72" rx="14"/>
<rect x="288" y="10" width="28" height="72" rx="14"/>
<circle cx="66" cy="28" r="15"/>
<circle cx="105" cy="28" r="15"/>
<circle cx="144" cy="28" r="15"/>
<circle cx="183" cy="28" r="15"/>
<circle cx="222" cy="28" r="15"/>
<circle cx="261" cy="28" r="15"/>
<circle cx="66" cy="64" r="15"/>
<circle cx="105" cy="64" r="15"/>
<circle cx="144" cy="64" r="15"/>
<circle cx="183" cy="64" r="15"/>
<circle cx="222" cy="64" r="15"/>
<circle cx="261" cy="64" r="15"/>
</g>
</mask>
<rect width="328" height="92" rx="22" mask="url(#holes)"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,26 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 312 76">
<!-- A board at the start of a game: two stores and 2 x 6 pits with four stones each. Colors from LightTheme. -->
<defs>
<g id="pit">
<circle r="16" fill="#B8B8B8"/>
<circle cx="-7.5" r="2.2" fill="#5B5B5B"/>
<circle cx="-2.5" r="2.2" fill="#5B5B5B"/>
<circle cx="2.5" r="2.2" fill="#5B5B5B"/>
<circle cx="7.5" r="2.2" fill="#5B5B5B"/>
</g>
</defs>
<rect x="0" y="0" width="28" height="76" rx="14" fill="#B8B8B8"/>
<rect x="284" y="0" width="28" height="76" rx="14" fill="#B8B8B8"/>
<use xlink:href="#pit" x="56" y="18"/>
<use xlink:href="#pit" x="96" y="18"/>
<use xlink:href="#pit" x="136" y="18"/>
<use xlink:href="#pit" x="176" y="18"/>
<use xlink:href="#pit" x="216" y="18"/>
<use xlink:href="#pit" x="256" y="18"/>
<use xlink:href="#pit" x="56" y="58"/>
<use xlink:href="#pit" x="96" y="58"/>
<use xlink:href="#pit" x="136" y="58"/>
<use xlink:href="#pit" x="176" y="58"/>
<use xlink:href="#pit" x="216" y="58"/>
<use xlink:href="#pit" x="256" y="58"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<!-- Mask of the app's logo (LogoIcon in apps/frontend/src/components/Icons.tsx): its square with the store and the
stones cut out, so the theme's accent fills the square and the header shows through. -->
<mask id="cut">
<rect width="32" height="32" fill="#fff"/>
<rect x="6" y="7" width="6" height="18" rx="3" fill="#000"/>
<circle cx="18" cy="11" r="3.2" fill="#000"/>
<circle cx="18" cy="21" r="3.2" fill="#000"/>
<circle cx="25" cy="11" r="3.2" fill="#737373"/>
<circle cx="25" cy="21" r="3.2" fill="#000"/>
</mask>
<rect x="1" y="1" width="30" height="30" rx="9" mask="url(#cut)"/>
</svg>

After

Width:  |  Height:  |  Size: 694 B

View File

@ -1,76 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="100.00002mm"
height="100.00001mm"
viewBox="0 0 100.00002 100.00001"
version="1.1"
id="svg316"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview318"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="false" />
<defs
id="defs313" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-230.50606,-73.754324)">
<rect
style="fill:#b3b3b3;stroke-width:1.03151"
id="rect489-5-7-8"
width="100"
height="100"
x="230.50607"
y="73.754326"
ry="27.328773"
inkscape:export-filename="mancala.svg"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96" />
<rect
style="fill:#666666;fill-opacity:1;stroke-width:0.747434"
id="rect642-6-2-0-7"
width="35"
height="35"
x="281.50607"
y="87.754311"
ry="17.5" />
<rect
style="fill:#b3b3b3;stroke-width:0.747434"
id="rect642-6-7-9-9-9"
width="35"
height="35"
x="244.50607"
y="87.754311"
ry="17.5" />
<rect
style="fill:#666666;fill-opacity:1;stroke:none;stroke-width:0.747434;stroke-opacity:1"
id="rect642-6-2-1-3-2"
width="35"
height="35"
x="281.50607"
y="124.75433"
ry="17.5" />
<rect
style="fill:#666666;fill-opacity:1;stroke-width:1.05044"
id="rect1550-0"
width="35"
height="70"
x="244.50607"
y="87.754311"
ry="17.5" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,51 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 328 92">
<!-- Mask of the stones of img/board-mask.svg: four in each pit. -->
<circle cx="61.4" cy="23.4" r="3.6"/>
<circle cx="70.6" cy="23.4" r="3.6"/>
<circle cx="61.4" cy="32.6" r="3.6"/>
<circle cx="70.6" cy="32.6" r="3.6"/>
<circle cx="100.4" cy="23.4" r="3.6"/>
<circle cx="109.6" cy="23.4" r="3.6"/>
<circle cx="100.4" cy="32.6" r="3.6"/>
<circle cx="109.6" cy="32.6" r="3.6"/>
<circle cx="139.4" cy="23.4" r="3.6"/>
<circle cx="148.6" cy="23.4" r="3.6"/>
<circle cx="139.4" cy="32.6" r="3.6"/>
<circle cx="148.6" cy="32.6" r="3.6"/>
<circle cx="178.4" cy="23.4" r="3.6"/>
<circle cx="187.6" cy="23.4" r="3.6"/>
<circle cx="178.4" cy="32.6" r="3.6"/>
<circle cx="187.6" cy="32.6" r="3.6"/>
<circle cx="217.4" cy="23.4" r="3.6"/>
<circle cx="226.6" cy="23.4" r="3.6"/>
<circle cx="217.4" cy="32.6" r="3.6"/>
<circle cx="226.6" cy="32.6" r="3.6"/>
<circle cx="256.4" cy="23.4" r="3.6"/>
<circle cx="265.6" cy="23.4" r="3.6"/>
<circle cx="256.4" cy="32.6" r="3.6"/>
<circle cx="265.6" cy="32.6" r="3.6"/>
<circle cx="61.4" cy="59.4" r="3.6"/>
<circle cx="70.6" cy="59.4" r="3.6"/>
<circle cx="61.4" cy="68.6" r="3.6"/>
<circle cx="70.6" cy="68.6" r="3.6"/>
<circle cx="100.4" cy="59.4" r="3.6"/>
<circle cx="109.6" cy="59.4" r="3.6"/>
<circle cx="100.4" cy="68.6" r="3.6"/>
<circle cx="109.6" cy="68.6" r="3.6"/>
<circle cx="139.4" cy="59.4" r="3.6"/>
<circle cx="148.6" cy="59.4" r="3.6"/>
<circle cx="139.4" cy="68.6" r="3.6"/>
<circle cx="148.6" cy="68.6" r="3.6"/>
<circle cx="178.4" cy="59.4" r="3.6"/>
<circle cx="187.6" cy="59.4" r="3.6"/>
<circle cx="178.4" cy="68.6" r="3.6"/>
<circle cx="187.6" cy="68.6" r="3.6"/>
<circle cx="217.4" cy="59.4" r="3.6"/>
<circle cx="226.6" cy="59.4" r="3.6"/>
<circle cx="217.4" cy="68.6" r="3.6"/>
<circle cx="226.6" cy="68.6" r="3.6"/>
<circle cx="256.4" cy="59.4" r="3.6"/>
<circle cx="265.6" cy="59.4" r="3.6"/>
<circle cx="256.4" cy="68.6" r="3.6"/>
<circle cx="265.6" cy="68.6" r="3.6"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1,13 @@
// Puts the web app's theme on the page: the one the player chose, which the web app keeps in the mancala_theme
// cookie, or else Ceviz for a dark system color scheme and Yeşim for a light one. Runs in <head>, before the page
// is drawn. The dark themes also get PatternFly's dark mode.
(function () {
var DARK_THEMES = { ceviz: true, gece: true };
var THEMES = { ceviz: true, gece: true, yesim: true };
var match = document.cookie.match(/(?:^|;\s*)mancala_theme=([a-z]+)/);
var theme = match && THEMES[match[1]] ? match[1]
: window.matchMedia("(prefers-color-scheme: dark)").matches ? "ceviz" : "yesim";
var classList = document.documentElement.classList;
classList.add("t-" + theme);
if (DARK_THEMES[theme]) classList.add("pf-v5-theme-dark");
})();

View File

@ -1,6 +1,9 @@
# Login pages in the look of the game. Keeps keycloak.v2's templates and only adds a stylesheet, # Login pages in the look of the web app, in its three themes (docs/specs/theme-polish). Keeps keycloak.v2's
# so Keycloak upgrades do not need template changes. # templates and only adds a stylesheet and a script, so Keycloak upgrades do not need template changes.
parent=keycloak.v2 parent=keycloak.v2
import=common/keycloak import=common/keycloak
styles=css/styles.css css/mancala.css styles=css/styles.css css/mancala.css
# Picks the theme before the page is drawn; it also sets PatternFly's dark mode, so Keycloak's own is off.
scripts=js/theme.js
darkMode=false

View File

@ -5,6 +5,6 @@ Work through the tasks in order; each ends with its checks passing and a commit.
- [x] 1. **Spec**: requirements, design and tasks. - [x] 1. **Spec**: requirements, design and tasks.
- [x] 2. **Web app messages**: Notyf at the top centre in the theme's colors. - [x] 2. **Web app messages**: Notyf at the top centre in the theme's colors.
- [x] 3. **Theme cookie**: the web app saves the chosen theme for the login pages. - [x] 3. **Theme cookie**: the web app saves the chosen theme for the login pages.
- [ ] 4. **Login theme**: the script, the three themes' styles, logo and board masks; checked in Chrome in all three - [x] 4. **Login theme**: the script, the three themes' styles, logo and board masks; checked in Chrome in all three
themes, at desktop and phone width, with and without the cookie. themes, at desktop and phone width, with and without the cookie.
- [ ] 5. **Docs**: `AGENTS.md` (specs list, the cookie), `apps/keycloak/README.md`, `TODO.md`. - [ ] 5. **Docs**: `AGENTS.md` (specs list, the cookie), `apps/keycloak/README.md`, `TODO.md`.