From 4cc68e3e46434894033d4f790dfeb45bfc89e7db Mon Sep 17 00:00:00 2001 From: Martin Dvorak Date: Fri, 11 Sep 2026 09:59:12 +0200 Subject: [PATCH] moved the css to an external file --- CMakeLists.txt | 7 + Makefile | 1 + README.md | 22 +- maturita.c | 804 +++---------------------------------------------- style.css | 739 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 804 insertions(+), 769 deletions(-) create mode 100644 style.css diff --git a/CMakeLists.txt b/CMakeLists.txt index 7858848..802ed6b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,3 +60,10 @@ else() target_compile_options(maturita PRIVATE -Wall -Wextra -Wno-deprecated-declarations) endif() + +# The GTK stylesheet is loaded at runtime from the working directory, so keep +# a copy next to the built binary as well. +add_custom_command(TARGET maturita POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_SOURCE_DIR}/style.css + $/style.css) diff --git a/Makefile b/Makefile index 8cdbd5d..ca1d1f6 100644 --- a/Makefile +++ b/Makefile @@ -46,6 +46,7 @@ bundle: $(TARGET) @rm -rf dist @mkdir -p dist @cp $(TARGET) dist/ + @cp style.css dist/ @ldd $(TARGET) | grep -Ei '/(ucrt64|mingw64)/bin/' | awk '{print $$3}' \ | xargs -r -I{} cp -f {} dist/ @echo "Bundled into dist/ - run dist/$(TARGET)" diff --git a/README.md b/README.md index 33903f2..f722ef9 100644 --- a/README.md +++ b/README.md @@ -300,6 +300,7 @@ saved per unit to `progress/unit1.conf` / `progress/unit2.conf` / ``` maturita.c entire application (UI, navigation, themes, exercises) +style.css GTK stylesheet (palette colors are injected at runtime) Makefile build & run targets (Linux, macOS, MSYS2); `make bundle` on Windows CMakeLists.txt optional CMake build (Linux / macOS) build-windows.bat legacy MSVC build (requires Visual Studio + gvsbuild GTK) @@ -317,12 +318,13 @@ progress/ created at runtime The app ships with ten palettes – Catppuccin, Nord, Dracula, Rose Pine, Ocean, Gruvbox, Solarized, Everforest, Monokai and One Dark – each available in dark -and light mode. Colors are defined as theme palettes in `maturita.c` and -applied through a generated CSS stylesheet (plus matching Cairo colors for the -roadmap rails and icons). +and light mode. Colors are defined as theme palettes in `maturita.c`, injected +as `@define-color` bindings, and combined with the rules in the external +`style.css` stylesheet (plus matching Cairo colors for the roadmap rails and +icons). -Change the look from the in-app settings panel, or edit the palette tables / -`build_theme_css()` in `maturita.c` and rebuild. +Change the look from the in-app settings panel, or edit the palette tables in +`maturita.c` / the rules in `style.css` and rebuild. ### Languages @@ -647,6 +649,7 @@ prvním dokončení). ``` maturita.c celá aplikace (UI, navigace, témata, cvičení) +style.css GTK styl (barvy palety se vkládají za běhu) Makefile build & run (Linux, macOS, MSYS2); `make bundle` na Windows CMakeLists.txt volitelný CMake build (Linux / macOS) build-windows.bat legacy MSVC build (vyžaduje Visual Studio + gvsbuild GTK) @@ -664,11 +667,12 @@ progress/ vzniká za běhu Aplikace má deset palet – Catppuccin, Nord, Dracula, Rose Pine, Ocean, Gruvbox, Solarized, Everforest, Monokai a One Dark – každou ve světlém i tmavém režimu. -Barvy jsou v `maturita.c` a aplikují se přes generované CSS (a Cairo barvy pro -koleje a ikony na mapě). +Barvy jsou v `maturita.c`, vkládají se jako `@define-color` a spojují se s +pravidly v externím souboru `style.css` (a Cairo barvy pro koleje a ikony na +mapě). -Vzhled změníte v nastavení, nebo upravte tabulky palet / `build_theme_css()` v -`maturita.c` a znovu zkompilujte. +Vzhled změníte v nastavení, nebo upravte tabulky palet v `maturita.c` / pravidla +v `style.css` a znovu zkompilujte. ### Jazyky diff --git a/maturita.c b/maturita.c index 2b5e24b..634a8c0 100644 --- a/maturita.c +++ b/maturita.c @@ -10,6 +10,7 @@ #define NUM_SUBJECTS 13 #define NET_SUBJ 1 /* index of "Správa počítačových sítí" */ +#define CSS_FILE "style.css" #define PROGRESS_DIR "progress" #define PROGRESS_U1 "progress/unit1.conf" #define PROGRESS_U2 "progress/unit2.conf" @@ -1058,771 +1059,54 @@ static void apply_language(void) { net_slide_apply(); } +static char *read_css_file(void) { + gchar *contents = NULL; + GError *err = NULL; + + if (!g_file_get_contents(CSS_FILE, &contents, NULL, &err)) { + g_warning("Could not load stylesheet '%s': %s", CSS_FILE, + err ? err->message : "unknown error"); + if (err) + g_error_free(err); + return g_strdup(""); + } + return contents; +} + +/* The palette is data-driven, so its @define-color bindings are generated + * here and prepended to the rules loaded from the external stylesheet. */ static char *build_theme_css(const ThemePalette *p) { - return g_strdup_printf( - "@define-color bg_crust #%06x;" - "@define-color bg_base #%06x;" - "@define-color bg_mantle #%06x;" - "@define-color bg_surface0 #%06x;" - "@define-color bg_surface1 #%06x;" - "@define-color bg_surface2 #%06x;" - "@define-color fg_overlay #%06x;" - "@define-color fg_text #%06x;" - "@define-color fg_subtext #%06x;" - "@define-color accent #%06x;" - "@define-color accent2 #%06x;" - "@define-color accent3 #%06x;" - "@define-color success #%06x;" - "@define-color success2 #%06x;" - "@define-color warning #%06x;" - "@define-color error #%06x;" - "@define-color on_accent #%06x;" - "@define-color node_bg #%06x;" - "@define-color locked_bg #%06x;" - "@define-color locked_border #%06x;" - "window {" - " background-color: @bg_base;" - " background-image: linear-gradient(160deg, @bg_crust 0%%, @bg_base 40%%, @bg_surface0 100%%);" - "}" - "button {" - " background-image: none;" - "}" - ".app-title {" - " color: @fg_text;" - " font-weight: 700;" - " font-size: 14px;" - "}" - ".settings-btn," - ".settings-btn:hover," - ".settings-btn:active," - ".settings-btn:checked," - ".settings-btn:focus," - ".settings-btn:focus-visible {" - " min-width: 34px;" - " min-height: 34px;" - " padding: 0;" - " border-radius: 999px;" - " background: transparent;" - " background-color: transparent;" - " background-image: none;" - " border: none;" - " outline: none;" - " box-shadow: none;" - " color: @fg_text;" - "}" - ".settings-btn:hover {" - " color: @accent;" - " background-color: alpha(@bg_surface0, 0.55);" - "}" - ".settings-btn:active {" - " color: @accent;" - " background-color: alpha(@bg_surface1, 0.4);" - "}" - "popover," - "popover.background," - ".settings-popover {" - " background: @bg_mantle;" - " background-color: @bg_mantle;" - " color: @fg_text;" - " border: 1px solid @bg_surface1;" - " border-radius: 18px;" - " box-shadow: 0 18px 40px alpha(@bg_crust, 0.45);" - "}" - "popover > contents," - ".settings-popover > contents {" - " background: transparent;" - " background-color: transparent;" - " border: none;" - " border-radius: 18px;" - " box-shadow: none;" - " padding: 0;" - "}" - ".settings-box {" - " background-color: transparent;" - " color: @fg_text;" - " min-width: 292px;" - " padding: 16px 16px 14px 16px;" - "}" - ".settings-title {" - " color: @fg_text;" - " font-size: 17px;" - " font-weight: 800;" - " letter-spacing: -0.2px;" - "}" - ".settings-label {" - " color: @fg_overlay;" - " font-size: 11px;" - " font-weight: 700;" - " letter-spacing: 0.8px;" - "}" - ".settings-seg {" - " background-color: alpha(@bg_surface0, 0.55);" - " border: none;" - " border-radius: 12px;" - " padding: 3px;" - "}" - ".mode-chip {" - " background-image: none;" - " background-color: transparent;" - " color: @fg_subtext;" - " border: none;" - " border-radius: 10px;" - " padding: 9px 12px;" - " font-size: 13px;" - " font-weight: 600;" - " min-width: 0;" - " box-shadow: none;" - " transition: background-color 140ms ease, color 140ms ease," - " box-shadow 140ms ease;" - "}" - ".mode-chip:hover {" - " background-color: alpha(@bg_surface1, 0.28);" - " color: @fg_text;" - "}" - ".mode-chip:checked {" - " background-image: linear-gradient(135deg, @accent 0%%, @accent2 100%%);" - " color: @on_accent;" - " font-weight: 800;" - " box-shadow: 0 4px 12px alpha(@accent, 0.25);" - "}" - ".mode-chip:checked:hover {" - " background-image: linear-gradient(135deg, @accent 0%%, @accent2 100%%);" - " color: @on_accent;" - "}" - ".theme-grid {" - " background-color: transparent;" - " border: none;" - " padding: 0;" - "}" - "flowboxchild {" - " padding: 0;" - " margin: 0;" - " background: transparent;" - " border: none;" - " box-shadow: none;" - "}" - ".theme-card {" - " background-image: none;" - " background-color: transparent;" - " color: @fg_text;" - " border: none;" - " border-radius: 12px;" - " padding: 6px 4px;" - " min-width: 0;" - " box-shadow: none;" - " transition: background-color 140ms ease;" - "}" - ".theme-card:hover {" - " background-color: alpha(@bg_surface0, 0.45);" - "}" - ".theme-card:checked {" - " background-color: transparent;" - " box-shadow: none;" - "}" - ".theme-card:checked:hover {" - " background-color: alpha(@bg_surface0, 0.35);" - "}" - ".theme-card-name {" - " color: @fg_subtext;" - " font-size: 11px;" - " font-weight: 600;" - "}" - ".theme-card:checked .theme-card-name {" - " color: @fg_text;" - " font-weight: 700;" - "}" - "headerbar," - "headerbar.titlebar," - ".titlebar {" - " background-color: @bg_base;" - " background-image: none;" - " color: @fg_text;" - " box-shadow: none;" - " border: none;" - "}" - "windowcontrols button:not(:hover) {" - " color: @fg_text;" - "}" - "windowcontrols button:not(:hover) image {" - " color: @fg_text;" - "}" - ".heading {" - " color: @fg_text;" - " font-size: 32px;" - " font-weight: 800;" - "}" - ".card {" - " background-color: alpha(@bg_surface0, 0.92);" - " border: 1px solid @bg_surface1;" - " border-radius: 18px;" - " padding: 30px 34px;" - " color: @fg_text;" - " font-size: 16px;" - "}" - ".btn-primary {" - " background-image: linear-gradient(135deg, @accent 0%%, @accent2 100%%);" - " color: @on_accent;" - " font-size: 16px;" - " font-weight: 700;" - " padding: 12px 40px;" - " border: none;" - " border-radius: 999px;" - " box-shadow: 0 6px 18px alpha(@accent, 0.25);" - " transition: box-shadow 150ms ease, background-image 150ms ease;" - "}" - ".btn-primary:hover {" - " box-shadow: 0 8px 24px alpha(@accent, 0.45);" - " background-image: linear-gradient(135deg, @accent2 0%%, @accent3 100%%);" - "}" - ".btn-primary:active {" - " box-shadow: 0 3px 10px alpha(@accent, 0.35);" - "}" - ".roadmap-title {" - " color: @fg_text;" - " font-size: 28px;" - " font-weight: 800;" - "}" - ".roadmap-sub {" - " color: @fg_subtext;" - " font-size: 14px;" - "}" - ".back-btn {" - " min-width: 42px;" - " min-height: 42px;" - " padding: 0;" - " border-radius: 999px;" - " background-color: alpha(@bg_surface0, 0.9);" - " border: 1px solid @bg_surface1;" - " color: @fg_text;" - " box-shadow: 0 4px 14px alpha(@bg_crust, 0.35);" - " transition: background-color 150ms ease, color 150ms ease," - " border-color 150ms ease, box-shadow 150ms ease;" - "}" - ".back-btn:hover {" - " background-color: @bg_surface1;" - " border-color: @bg_surface2;" - " color: @accent;" - " box-shadow: 0 6px 20px alpha(@accent, 0.35);" - "}" - ".back-btn:active {" - " background-color: @bg_surface0;" - " box-shadow: 0 2px 8px alpha(@bg_crust, 0.35);" - "}" - ".unit-node {" - " background-image: none;" - " background-color: @node_bg;" - " color: @fg_text;" - " border: 2px solid @locked_border;" - " border-radius: 999px;" - " padding: 0;" - " box-shadow: 0 4px 14px alpha(@bg_crust, 0.35);" - " transition: transform 150ms ease, box-shadow 150ms ease," - " border-color 150ms ease;" - "}" - ".unit-node.done {" - " background-image: linear-gradient(135deg, @success 0%%, @success2 100%%);" - " border-color: @success;" - " color: @on_accent;" - " box-shadow: 0 4px 16px alpha(@success, 0.35);" - "}" - ".unit-node.current {" - " background-image: linear-gradient(135deg, @accent 0%%, @accent2 55%%, @accent3 100%%);" - " border-color: @accent;" - " color: @on_accent;" - " box-shadow: 0 4px 18px alpha(@accent, 0.5)," - " 0 0 0 4px alpha(@bg_base, 0.9)," - " 0 0 0 7px alpha(@accent, 0.45);" - "}" - ".unit-node.done:hover," - ".unit-node.current:hover {" - " transition: box-shadow 150ms ease, border-color 150ms ease;" - "}" - ".unit-node.done:hover {" - " box-shadow: 0 8px 22px alpha(@success, 0.5);" - "}" - ".unit-node.current:hover {" - " box-shadow: 0 8px 26px alpha(@accent, 0.65)," - " 0 0 0 4px alpha(@bg_base, 0.9)," - " 0 0 0 7px alpha(@accent, 0.55);" - "}" - ".unit-node.locked {" - " background-image: none;" - " background-color: @locked_bg;" - " border: 2px solid @locked_border;" - " color: @fg_overlay;" - " box-shadow: none;" - "}" - ".unit-node.locked:hover {" - " transform: none;" - " box-shadow: none;" - "}" - ".unit-node.finish {" - " background-color: transparent;" - " background-image: none;" - " border: none;" - " color: @fg_overlay;" - " box-shadow: 0 4px 14px alpha(@bg_crust, 0.35);" - "}" - ".unit-node.finish:hover {" - " transform: none;" - " box-shadow: 0 4px 14px alpha(@bg_crust, 0.35);" - "}" - ".unit-node:focus," - ".unit-node:focus-visible," - ".unit-node:hover {" - " outline: none;" - "}" - ".unit-number {" - " font-size: 24px;" - " font-weight: 800;" - "}" - ".unit-node.done .unit-number," - ".unit-node.current .unit-number {" - " color: @on_accent;" - "}" - ".unit-node.locked .unit-number {" - " color: @fg_overlay;" - "}" - ".state-icon {" - " color: @on_accent;" - "}" - ".unit-node.locked .lock-icon {" - " color: @bg_surface2;" - "}" - ".unit-name {" - " color: @fg_text;" - " font-size: 13px;" - " font-weight: 600;" - "}" - ".unit-name-locked {" - " color: @fg_overlay;" - "}" - ".ex-bubble {" - " min-width: 64px;" - " min-height: 64px;" - " padding: 0;" - " border-radius: 999px;" - " background-color: @node_bg;" - " color: @fg_text;" - " border: 2px solid @locked_border;" - " box-shadow: 0 4px 14px alpha(@bg_crust, 0.35);" - " transition: border-color 150ms ease, box-shadow 150ms ease;" - "}" - ".ex-bubble:hover {" - " background-image: none;" - " border-color: @accent;" - " box-shadow: 0 6px 20px alpha(@accent, 0.35);" - "}" - ".ex-bubble.done {" - " background-image: linear-gradient(135deg, @success 0%%, @success2 100%%);" - " border-color: @success;" - " color: @on_accent;" - " box-shadow: 0 4px 16px alpha(@success, 0.35);" - "}" - ".ex-bubble.done:hover {" - " box-shadow: 0 8px 22px alpha(@success, 0.5);" - "}" - ".ex-bubble:focus," - ".ex-bubble:focus-visible {" - " outline: none;" - "}" - ".bubble-number {" - " font-size: 22px;" - " font-weight: 800;" - "}" - ".ex-bubble.done .bubble-number {" - " color: @on_accent;" - "}" - ".bubble-icon {" - " color: @on_accent;" - "}" - ".ex-label {" - " color: @fg_text;" - " font-size: 12px;" - " font-weight: 600;" - "}" - ".ex-prompt {" - " color: @fg_text;" - " font-size: 16px;" - " font-weight: 500;" - "}" - ".ex-sub {" - " color: @fg_subtext;" - " font-size: 13px;" - " font-weight: 700;" - "}" - ".hint {" - " color: @fg_subtext;" - " font-size: 13px;" - " font-style: italic;" - "}" - ".meaning {" - " color: @fg_subtext;" - " font-size: 13px;" - " font-style: italic;" - "}" - ".u3-answer {" - " color: @success;" - " font-size: 13px;" - " font-weight: 700;" - "}" - ".notes-card {" - " background-color: alpha(@bg_surface0, 0.9);" - " border: 1px solid @bg_surface1;" - " border-radius: 24px;" - " padding: 24px 34px;" - " box-shadow: 0 10px 30px alpha(@bg_crust, 0.20);" - "}" - ".notes-kicker {" - " color: @accent2;" - " font-weight: 800;" - " letter-spacing: 2px;" - "}" - ".notes-title {" - " color: @fg_text;" - " font-weight: 800;" - "}" - ".notes-body {" - " color: @fg_text;" - "}" - ".notes-tip {" - " color: @success2;" - " font-weight: 700;" - "}" - ".dlg-speaker {" - " color: @accent;" - " font-weight: 700;" - " font-size: 15px;" - "}" - ".number-big {" - " color: @warning;" - " font-size: 26px;" - " font-weight: 800;" - "}" - ".hm-word {" - " color: @fg_text;" - " font-family: monospace;" - " font-size: 30px;" - " font-weight: 800;" - "}" - ".chain {" - " color: @warning;" - " font-size: 20px;" - " font-weight: 700;" - " letter-spacing: 1px;" - "}" - "entry {" - " background-color: @bg_mantle;" - " color: @fg_text;" - " border: 1px solid @bg_surface1;" - " border-radius: 10px;" - " padding: 8px 12px;" - " font-size: 15px;" - " caret-color: @accent;" - "}" - "entry:focus {" - " border-color: @accent;" - "}" - "combobox {" - " background-color: @bg_mantle;" - " color: @fg_text;" - "}" - "combobox button {" - " background-image: none;" - " background-color: @bg_mantle;" - " color: @fg_text;" - " border: 1px solid @bg_surface1;" - " border-radius: 10px;" - " padding: 4px 10px;" - "}" - "combobox button:hover {" - " background-image: none;" - " border-color: @accent;" - "}" - "combobox.answer-ok button {" - " border-color: @success;" - " background-color: alpha(@success, 0.12);" - "}" - "combobox.answer-wrong button {" - " border-color: @error;" - " background-color: alpha(@error, 0.12);" - "}" - "entry.answer-ok {" - " border-color: @success;" - " background-color: alpha(@success, 0.08);" - "}" - "entry.answer-wrong {" - " border-color: @error;" - " background-color: alpha(@error, 0.08);" - "}" - ".pill {" - " background-image: none;" - " background-color: @node_bg;" - " color: @fg_text;" - " border: 2px solid @locked_border;" - " border-radius: 999px;" - " padding: 6px 18px;" - " font-size: 15px;" - " transition: border-color 150ms ease, box-shadow 150ms ease;" - "}" - ".pill:hover {" - " background-image: none;" - " background-color: @node_bg;" - " border-color: @accent;" - " box-shadow: none;" - "}" - ".pill:active {" - " background-image: none;" - " background-color: @bg_surface1;" - " box-shadow: none;" - "}" - ".pill:checked {" - " background-image: linear-gradient(135deg, @accent 0%%, @accent2 100%%);" - " color: @on_accent;" - " border-color: @accent;" - " font-weight: 700;" - "}" - ".pill.ok {" - " border-color: @success;" - " box-shadow: 0 0 0 2px alpha(@success, 0.4);" - "}" - ".pill.wrong {" - " border-color: @error;" - " box-shadow: 0 0 0 2px alpha(@error, 0.4);" - "}" - ".pill.hm-key {" - " padding: 4px 5px;" - " min-width: 32px;" - " font-size: 13px;" - "}" - ".chip {" - " background-image: none;" - " background-color: @bg_surface1;" - " color: @fg_text;" - " border: 1px solid @bg_surface2;" - " border-radius: 999px;" - " padding: 6px 14px;" - " font-size: 14px;" - " font-weight: 600;" - " transition: background-color 150ms ease, border-color 150ms ease;" - "}" - ".chip:hover {" - " background-image: none;" - " background-color: @bg_surface2;" - " border-color: @accent;" - " box-shadow: none;" - "}" - ".chip:active {" - " background-image: none;" - " background-color: @bg_surface2;" - " box-shadow: none;" - "}" - ".chip.ghost-host {" - " opacity: 0;" - "}" - ".group-btn {" - " background-image: none;" - " background-color: @node_bg;" - " color: @fg_text;" - " border: 2px solid @locked_border;" - " border-radius: 12px;" - " padding: 8px 18px;" - " font-size: 14px;" - " font-weight: 700;" - " transition: border-color 150ms ease, box-shadow 150ms ease;" - "}" - ".group-btn:hover {" - " background-image: none;" - " background-color: @node_bg;" - " border-color: @accent;" - " box-shadow: none;" - "}" - ".group-btn:active {" - " background-image: none;" - " background-color: @bg_surface1;" - " box-shadow: none;" - "}" - ".group-btn:checked {" - " background-image: linear-gradient(135deg, @accent3 0%%, @success2 100%%);" - " color: @on_accent;" - " border-color: @accent3;" - "}" - ".group-panel {" - " background-color: alpha(@bg_surface0, 0.5);" - " border: 2px dashed @bg_surface1;" - " border-radius: 14px;" - " padding: 10px;" - "}" - ".group-panel-label {" - " color: @fg_subtext;" - " font-size: 13px;" - " font-weight: 700;" - "}" - ".sent-group {" - " background-color: alpha(@bg_surface0, 0.5);" - " border: 1px solid @bg_surface1;" - " border-radius: 14px;" - " padding: 12px;" - "}" - ".sent-group.ok {" - " border-color: @success;" - "}" - ".sent-group.wrong {" - " border-color: @error;" - "}" - ".feedback-ok {" - " color: @success;" - " font-size: 15px;" - " font-weight: 700;" - "}" - ".feedback-err {" - " color: @error;" - " font-size: 15px;" - " font-weight: 700;" - "}" - ".stats-page {" - " padding: 8px 28px 24px 28px;" - "}" - ".stats-summary {" - " margin-top: 8px;" - " margin-bottom: 18px;" - "}" - ".stats-metric {" - " background-color: alpha(@bg_surface0, 0.92);" - " border: 1px solid @bg_surface1;" - " border-radius: 16px;" - " padding: 16px 18px;" - " min-width: 120px;" - "}" - ".stats-metric-value {" - " color: @fg_text;" - " font-size: 26px;" - " font-weight: 800;" - " letter-spacing: -0.4px;" - "}" - ".stats-metric-label {" - " color: @fg_overlay;" - " font-size: 11px;" - " font-weight: 700;" - " letter-spacing: 0.8px;" - " margin-top: 4px;" - "}" - ".stats-overall-bar," - ".stats-overall-bar trough," - ".stats-unit-bar," - ".stats-unit-bar trough {" - " min-height: 10px;" - " border-radius: 999px;" - " background-color: alpha(@bg_surface1, 0.85);" - " border: none;" - " padding: 0;" - "}" - ".stats-overall-bar {" - " margin-bottom: 22px;" - " min-height: 12px;" - "}" - ".stats-overall-bar progress," - ".stats-unit-bar progress {" - " min-height: 10px;" - " border-radius: 999px;" - " background-image: linear-gradient(90deg, @accent 0%%, @accent2 100%%);" - " background-color: @accent;" - " border: none;" - "}" - ".stats-unit-bar.done progress {" - " background-image: linear-gradient(90deg, @success 0%%, @success2 100%%);" - " background-color: @success;" - "}" - ".stats-section {" - " color: @fg_overlay;" - " font-size: 11px;" - " font-weight: 700;" - " letter-spacing: 0.8px;" - " margin-bottom: 10px;" - "}" - ".stats-subject {" - " margin-bottom: 20px;" - "}" - ".stats-subject-name {" - " color: @fg_text;" - " font-size: 16px;" - " font-weight: 800;" - "}" - ".stats-subject-count {" - " color: @fg_subtext;" - " font-size: 13px;" - " font-weight: 700;" - "}" - ".stats-subject-count.locked {" - " color: @fg_overlay;" - "}" - ".stats-subject-bar," - ".stats-subject-bar trough {" - " min-height: 12px;" - " border-radius: 999px;" - " background-color: alpha(@bg_surface1, 0.85);" - " border: none;" - " padding: 0;" - "}" - ".stats-subject-bar {" - " margin: 4px 0 0 0;" - "}" - ".stats-subject-bar progress {" - " min-height: 10px;" - " border-radius: 999px;" - " background-image: linear-gradient(90deg, @accent 0%%, @accent2 100%%);" - " background-color: @accent;" - " border: none;" - "}" - ".stats-units {" - " margin-top: 8px;" - " border-left: 2px solid alpha(@fg_overlay, 0.22);" - " padding-left: 12px;" - "}" - ".stats-unit-row {" - " background-color: alpha(@bg_surface0, 0.72);" - " border: 1px solid @bg_surface1;" - " border-radius: 14px;" - " padding: 12px 14px;" - " margin-bottom: 8px;" - "}" - ".stats-unit-row.locked {" - " opacity: 0.55;" - "}" - ".stats-unit-row.done {" - " border-color: alpha(@success, 0.55);" - "}" - ".stats-unit-index {" - " color: @fg_overlay;" - " font-size: 13px;" - " font-weight: 700;" - " min-width: 22px;" - "}" - ".stats-unit-name {" - " color: @fg_text;" - " font-size: 15px;" - " font-weight: 700;" - "}" - ".stats-unit-count {" - " color: @fg_subtext;" - " font-size: 13px;" - " font-weight: 600;" - "}" - ".stats-unit-badge {" - " color: @success;" - " font-size: 12px;" - " font-weight: 700;" - "}" - "tooltip {" - " background-color: @bg_mantle;" - " border: 1px solid @bg_surface1;" - " border-radius: 10px;" - " color: @fg_text;" - "}", + char *palette = g_strdup_printf( + "@define-color bg_crust #%06x;\n" + "@define-color bg_base #%06x;\n" + "@define-color bg_mantle #%06x;\n" + "@define-color bg_surface0 #%06x;\n" + "@define-color bg_surface1 #%06x;\n" + "@define-color bg_surface2 #%06x;\n" + "@define-color fg_overlay #%06x;\n" + "@define-color fg_text #%06x;\n" + "@define-color fg_subtext #%06x;\n" + "@define-color accent #%06x;\n" + "@define-color accent2 #%06x;\n" + "@define-color accent3 #%06x;\n" + "@define-color success #%06x;\n" + "@define-color success2 #%06x;\n" + "@define-color warning #%06x;\n" + "@define-color error #%06x;\n" + "@define-color on_accent #%06x;\n" + "@define-color node_bg #%06x;\n" + "@define-color locked_bg #%06x;\n" + "@define-color locked_border #%06x;\n", p->crust, p->base, p->mantle, p->surface0, p->surface1, p->surface2, p->overlay, p->text, p->subtext, p->accent, p->accent2, p->accent3, p->success, p->success2, p->warning, p->error, p->on_accent, p->node, p->locked_bg, p->locked_border); + char *rules = read_css_file(); + char *css = g_strconcat(palette, rules, NULL); + + g_free(palette); + g_free(rules); + return css; } static void save_settings(void) { diff --git a/style.css b/style.css new file mode 100644 index 0000000..a0808f1 --- /dev/null +++ b/style.css @@ -0,0 +1,739 @@ +window { + background-color: @bg_base; + background-image: linear-gradient(160deg, @bg_crust 0%, @bg_base 40%, @bg_surface0 100%); +} +button { + background-image: none; +} +.app-title { + color: @fg_text; + font-weight: 700; + font-size: 14px; +} +.settings-btn, +.settings-btn:hover, +.settings-btn:active, +.settings-btn:checked, +.settings-btn:focus, +.settings-btn:focus-visible { + min-width: 34px; + min-height: 34px; + padding: 0; + border-radius: 999px; + background: transparent; + background-color: transparent; + background-image: none; + border: none; + outline: none; + box-shadow: none; + color: @fg_text; +} +.settings-btn:hover { + color: @accent; + background-color: alpha(@bg_surface0, 0.55); +} +.settings-btn:active { + color: @accent; + background-color: alpha(@bg_surface1, 0.4); +} +popover, +popover.background, +.settings-popover { + background: @bg_mantle; + background-color: @bg_mantle; + color: @fg_text; + border: 1px solid @bg_surface1; + border-radius: 18px; + box-shadow: 0 18px 40px alpha(@bg_crust, 0.45); +} +popover > contents, +.settings-popover > contents { + background: transparent; + background-color: transparent; + border: none; + border-radius: 18px; + box-shadow: none; + padding: 0; +} +.settings-box { + background-color: transparent; + color: @fg_text; + min-width: 292px; + padding: 16px 16px 14px 16px; +} +.settings-title { + color: @fg_text; + font-size: 17px; + font-weight: 800; + letter-spacing: -0.2px; +} +.settings-label { + color: @fg_overlay; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.8px; +} +.settings-seg { + background-color: alpha(@bg_surface0, 0.55); + border: none; + border-radius: 12px; + padding: 3px; +} +.mode-chip { + background-image: none; + background-color: transparent; + color: @fg_subtext; + border: none; + border-radius: 10px; + padding: 9px 12px; + font-size: 13px; + font-weight: 600; + min-width: 0; + box-shadow: none; + transition: background-color 140ms ease, color 140ms ease, + box-shadow 140ms ease; +} +.mode-chip:hover { + background-color: alpha(@bg_surface1, 0.28); + color: @fg_text; +} +.mode-chip:checked { + background-image: linear-gradient(135deg, @accent 0%, @accent2 100%); + color: @on_accent; + font-weight: 800; + box-shadow: 0 4px 12px alpha(@accent, 0.25); +} +.mode-chip:checked:hover { + background-image: linear-gradient(135deg, @accent 0%, @accent2 100%); + color: @on_accent; +} +.theme-grid { + background-color: transparent; + border: none; + padding: 0; +} +flowboxchild { + padding: 0; + margin: 0; + background: transparent; + border: none; + box-shadow: none; +} +.theme-card { + background-image: none; + background-color: transparent; + color: @fg_text; + border: none; + border-radius: 12px; + padding: 6px 4px; + min-width: 0; + box-shadow: none; + transition: background-color 140ms ease; +} +.theme-card:hover { + background-color: alpha(@bg_surface0, 0.45); +} +.theme-card:checked { + background-color: transparent; + box-shadow: none; +} +.theme-card:checked:hover { + background-color: alpha(@bg_surface0, 0.35); +} +.theme-card-name { + color: @fg_subtext; + font-size: 11px; + font-weight: 600; +} +.theme-card:checked .theme-card-name { + color: @fg_text; + font-weight: 700; +} +headerbar, +headerbar.titlebar, +.titlebar { + background-color: @bg_base; + background-image: none; + color: @fg_text; + box-shadow: none; + border: none; +} +windowcontrols button:not(:hover) { + color: @fg_text; +} +windowcontrols button:not(:hover) image { + color: @fg_text; +} +.heading { + color: @fg_text; + font-size: 32px; + font-weight: 800; +} +.card { + background-color: alpha(@bg_surface0, 0.92); + border: 1px solid @bg_surface1; + border-radius: 18px; + padding: 30px 34px; + color: @fg_text; + font-size: 16px; +} +.btn-primary { + background-image: linear-gradient(135deg, @accent 0%, @accent2 100%); + color: @on_accent; + font-size: 16px; + font-weight: 700; + padding: 12px 40px; + border: none; + border-radius: 999px; + box-shadow: 0 6px 18px alpha(@accent, 0.25); + transition: box-shadow 150ms ease, background-image 150ms ease; +} +.btn-primary:hover { + box-shadow: 0 8px 24px alpha(@accent, 0.45); + background-image: linear-gradient(135deg, @accent2 0%, @accent3 100%); +} +.btn-primary:active { + box-shadow: 0 3px 10px alpha(@accent, 0.35); +} +.roadmap-title { + color: @fg_text; + font-size: 28px; + font-weight: 800; +} +.roadmap-sub { + color: @fg_subtext; + font-size: 14px; +} +.back-btn { + min-width: 42px; + min-height: 42px; + padding: 0; + border-radius: 999px; + background-color: alpha(@bg_surface0, 0.9); + border: 1px solid @bg_surface1; + color: @fg_text; + box-shadow: 0 4px 14px alpha(@bg_crust, 0.35); + transition: background-color 150ms ease, color 150ms ease, + border-color 150ms ease, box-shadow 150ms ease; +} +.back-btn:hover { + background-color: @bg_surface1; + border-color: @bg_surface2; + color: @accent; + box-shadow: 0 6px 20px alpha(@accent, 0.35); +} +.back-btn:active { + background-color: @bg_surface0; + box-shadow: 0 2px 8px alpha(@bg_crust, 0.35); +} +.unit-node { + background-image: none; + background-color: @node_bg; + color: @fg_text; + border: 2px solid @locked_border; + border-radius: 999px; + padding: 0; + box-shadow: 0 4px 14px alpha(@bg_crust, 0.35); + transition: transform 150ms ease, box-shadow 150ms ease, + border-color 150ms ease; +} +.unit-node.done { + background-image: linear-gradient(135deg, @success 0%, @success2 100%); + border-color: @success; + color: @on_accent; + box-shadow: 0 4px 16px alpha(@success, 0.35); +} +.unit-node.current { + background-image: linear-gradient(135deg, @accent 0%, @accent2 55%, @accent3 100%); + border-color: @accent; + color: @on_accent; + box-shadow: 0 4px 18px alpha(@accent, 0.5), + 0 0 0 4px alpha(@bg_base, 0.9), + 0 0 0 7px alpha(@accent, 0.45); +} +.unit-node.done:hover, +.unit-node.current:hover { + transition: box-shadow 150ms ease, border-color 150ms ease; +} +.unit-node.done:hover { + box-shadow: 0 8px 22px alpha(@success, 0.5); +} +.unit-node.current:hover { + box-shadow: 0 8px 26px alpha(@accent, 0.65), + 0 0 0 4px alpha(@bg_base, 0.9), + 0 0 0 7px alpha(@accent, 0.55); +} +.unit-node.locked { + background-image: none; + background-color: @locked_bg; + border: 2px solid @locked_border; + color: @fg_overlay; + box-shadow: none; +} +.unit-node.locked:hover { + transform: none; + box-shadow: none; +} +.unit-node.finish { + background-color: transparent; + background-image: none; + border: none; + color: @fg_overlay; + box-shadow: 0 4px 14px alpha(@bg_crust, 0.35); +} +.unit-node.finish:hover { + transform: none; + box-shadow: 0 4px 14px alpha(@bg_crust, 0.35); +} +.unit-node:focus, +.unit-node:focus-visible, +.unit-node:hover { + outline: none; +} +.unit-number { + font-size: 24px; + font-weight: 800; +} +.unit-node.done .unit-number, +.unit-node.current .unit-number { + color: @on_accent; +} +.unit-node.locked .unit-number { + color: @fg_overlay; +} +.state-icon { + color: @on_accent; +} +.unit-node.locked .lock-icon { + color: @bg_surface2; +} +.unit-name { + color: @fg_text; + font-size: 13px; + font-weight: 600; +} +.unit-name-locked { + color: @fg_overlay; +} +.ex-bubble { + min-width: 64px; + min-height: 64px; + padding: 0; + border-radius: 999px; + background-color: @node_bg; + color: @fg_text; + border: 2px solid @locked_border; + box-shadow: 0 4px 14px alpha(@bg_crust, 0.35); + transition: border-color 150ms ease, box-shadow 150ms ease; +} +.ex-bubble:hover { + background-image: none; + border-color: @accent; + box-shadow: 0 6px 20px alpha(@accent, 0.35); +} +.ex-bubble.done { + background-image: linear-gradient(135deg, @success 0%, @success2 100%); + border-color: @success; + color: @on_accent; + box-shadow: 0 4px 16px alpha(@success, 0.35); +} +.ex-bubble.done:hover { + box-shadow: 0 8px 22px alpha(@success, 0.5); +} +.ex-bubble:focus, +.ex-bubble:focus-visible { + outline: none; +} +.bubble-number { + font-size: 22px; + font-weight: 800; +} +.ex-bubble.done .bubble-number { + color: @on_accent; +} +.bubble-icon { + color: @on_accent; +} +.ex-label { + color: @fg_text; + font-size: 12px; + font-weight: 600; +} +.ex-prompt { + color: @fg_text; + font-size: 16px; + font-weight: 500; +} +.ex-sub { + color: @fg_subtext; + font-size: 13px; + font-weight: 700; +} +.hint { + color: @fg_subtext; + font-size: 13px; + font-style: italic; +} +.meaning { + color: @fg_subtext; + font-size: 13px; + font-style: italic; +} +.u3-answer { + color: @success; + font-size: 13px; + font-weight: 700; +} +.notes-card { + background-color: alpha(@bg_surface0, 0.9); + border: 1px solid @bg_surface1; + border-radius: 24px; + padding: 24px 34px; + box-shadow: 0 10px 30px alpha(@bg_crust, 0.20); +} +.notes-kicker { + color: @accent2; + font-weight: 800; + letter-spacing: 2px; +} +.notes-title { + color: @fg_text; + font-weight: 800; +} +.notes-body { + color: @fg_text; +} +.notes-tip { + color: @success2; + font-weight: 700; +} +.dlg-speaker { + color: @accent; + font-weight: 700; + font-size: 15px; +} +.number-big { + color: @warning; + font-size: 26px; + font-weight: 800; +} +.hm-word { + color: @fg_text; + font-family: monospace; + font-size: 30px; + font-weight: 800; +} +.chain { + color: @warning; + font-size: 20px; + font-weight: 700; + letter-spacing: 1px; +} +entry { + background-color: @bg_mantle; + color: @fg_text; + border: 1px solid @bg_surface1; + border-radius: 10px; + padding: 8px 12px; + font-size: 15px; + caret-color: @accent; +} +entry:focus { + border-color: @accent; +} +combobox { + background-color: @bg_mantle; + color: @fg_text; +} +combobox button { + background-image: none; + background-color: @bg_mantle; + color: @fg_text; + border: 1px solid @bg_surface1; + border-radius: 10px; + padding: 4px 10px; +} +combobox button:hover { + background-image: none; + border-color: @accent; +} +combobox.answer-ok button { + border-color: @success; + background-color: alpha(@success, 0.12); +} +combobox.answer-wrong button { + border-color: @error; + background-color: alpha(@error, 0.12); +} +entry.answer-ok { + border-color: @success; + background-color: alpha(@success, 0.08); +} +entry.answer-wrong { + border-color: @error; + background-color: alpha(@error, 0.08); +} +.pill { + background-image: none; + background-color: @node_bg; + color: @fg_text; + border: 2px solid @locked_border; + border-radius: 999px; + padding: 6px 18px; + font-size: 15px; + transition: border-color 150ms ease, box-shadow 150ms ease; +} +.pill:hover { + background-image: none; + background-color: @node_bg; + border-color: @accent; + box-shadow: none; +} +.pill:active { + background-image: none; + background-color: @bg_surface1; + box-shadow: none; +} +.pill:checked { + background-image: linear-gradient(135deg, @accent 0%, @accent2 100%); + color: @on_accent; + border-color: @accent; + font-weight: 700; +} +.pill.ok { + border-color: @success; + box-shadow: 0 0 0 2px alpha(@success, 0.4); +} +.pill.wrong { + border-color: @error; + box-shadow: 0 0 0 2px alpha(@error, 0.4); +} +.pill.hm-key { + padding: 4px 5px; + min-width: 32px; + font-size: 13px; +} +.chip { + background-image: none; + background-color: @bg_surface1; + color: @fg_text; + border: 1px solid @bg_surface2; + border-radius: 999px; + padding: 6px 14px; + font-size: 14px; + font-weight: 600; + transition: background-color 150ms ease, border-color 150ms ease; +} +.chip:hover { + background-image: none; + background-color: @bg_surface2; + border-color: @accent; + box-shadow: none; +} +.chip:active { + background-image: none; + background-color: @bg_surface2; + box-shadow: none; +} +.chip.ghost-host { + opacity: 0; +} +.group-btn { + background-image: none; + background-color: @node_bg; + color: @fg_text; + border: 2px solid @locked_border; + border-radius: 12px; + padding: 8px 18px; + font-size: 14px; + font-weight: 700; + transition: border-color 150ms ease, box-shadow 150ms ease; +} +.group-btn:hover { + background-image: none; + background-color: @node_bg; + border-color: @accent; + box-shadow: none; +} +.group-btn:active { + background-image: none; + background-color: @bg_surface1; + box-shadow: none; +} +.group-btn:checked { + background-image: linear-gradient(135deg, @accent3 0%, @success2 100%); + color: @on_accent; + border-color: @accent3; +} +.group-panel { + background-color: alpha(@bg_surface0, 0.5); + border: 2px dashed @bg_surface1; + border-radius: 14px; + padding: 10px; +} +.group-panel-label { + color: @fg_subtext; + font-size: 13px; + font-weight: 700; +} +.sent-group { + background-color: alpha(@bg_surface0, 0.5); + border: 1px solid @bg_surface1; + border-radius: 14px; + padding: 12px; +} +.sent-group.ok { + border-color: @success; +} +.sent-group.wrong { + border-color: @error; +} +.feedback-ok { + color: @success; + font-size: 15px; + font-weight: 700; +} +.feedback-err { + color: @error; + font-size: 15px; + font-weight: 700; +} +.stats-page { + padding: 8px 28px 24px 28px; +} +.stats-summary { + margin-top: 8px; + margin-bottom: 18px; +} +.stats-metric { + background-color: alpha(@bg_surface0, 0.92); + border: 1px solid @bg_surface1; + border-radius: 16px; + padding: 16px 18px; + min-width: 120px; +} +.stats-metric-value { + color: @fg_text; + font-size: 26px; + font-weight: 800; + letter-spacing: -0.4px; +} +.stats-metric-label { + color: @fg_overlay; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.8px; + margin-top: 4px; +} +.stats-overall-bar, +.stats-overall-bar trough, +.stats-unit-bar, +.stats-unit-bar trough { + min-height: 10px; + border-radius: 999px; + background-color: alpha(@bg_surface1, 0.85); + border: none; + padding: 0; +} +.stats-overall-bar { + margin-bottom: 22px; + min-height: 12px; +} +.stats-overall-bar progress, +.stats-unit-bar progress { + min-height: 10px; + border-radius: 999px; + background-image: linear-gradient(90deg, @accent 0%, @accent2 100%); + background-color: @accent; + border: none; +} +.stats-unit-bar.done progress { + background-image: linear-gradient(90deg, @success 0%, @success2 100%); + background-color: @success; +} +.stats-section { + color: @fg_overlay; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.8px; + margin-bottom: 10px; +} +.stats-subject { + margin-bottom: 20px; +} +.stats-subject-name { + color: @fg_text; + font-size: 16px; + font-weight: 800; +} +.stats-subject-count { + color: @fg_subtext; + font-size: 13px; + font-weight: 700; +} +.stats-subject-count.locked { + color: @fg_overlay; +} +.stats-subject-bar, +.stats-subject-bar trough { + min-height: 12px; + border-radius: 999px; + background-color: alpha(@bg_surface1, 0.85); + border: none; + padding: 0; +} +.stats-subject-bar { + margin: 4px 0 0 0; +} +.stats-subject-bar progress { + min-height: 10px; + border-radius: 999px; + background-image: linear-gradient(90deg, @accent 0%, @accent2 100%); + background-color: @accent; + border: none; +} +.stats-units { + margin-top: 8px; + border-left: 2px solid alpha(@fg_overlay, 0.22); + padding-left: 12px; +} +.stats-unit-row { + background-color: alpha(@bg_surface0, 0.72); + border: 1px solid @bg_surface1; + border-radius: 14px; + padding: 12px 14px; + margin-bottom: 8px; +} +.stats-unit-row.locked { + opacity: 0.55; +} +.stats-unit-row.done { + border-color: alpha(@success, 0.55); +} +.stats-unit-index { + color: @fg_overlay; + font-size: 13px; + font-weight: 700; + min-width: 22px; +} +.stats-unit-name { + color: @fg_text; + font-size: 15px; + font-weight: 700; +} +.stats-unit-count { + color: @fg_subtext; + font-size: 13px; + font-weight: 600; +} +.stats-unit-badge { + color: @success; + font-size: 12px; + font-weight: 700; +} +tooltip { + background-color: @bg_mantle; + border: 1px solid @bg_surface1; + border-radius: 10px; + color: @fg_text; +}