moved the css to an external file

This commit is contained in:
Martin Dvořák 2026-09-11 09:59:12 +02:00
commit 4cc68e3e46
5 changed files with 804 additions and 769 deletions

View file

@ -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
$<TARGET_FILE_DIR:maturita>/style.css)

View file

@ -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)"

View file

@ -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

View file

@ -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) {

739
style.css Normal file
View file

@ -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;
}