diff --git a/.github/actions/setup-python/action.yml b/.github/actions/setup-python/action.yml
index f451497..d1c358a 100644
--- a/.github/actions/setup-python/action.yml
+++ b/.github/actions/setup-python/action.yml
@@ -4,7 +4,7 @@ runs:
using: "composite"
steps:
- name: Set up Python
- uses: actions/setup-python@v6
+ uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version-file: "pyproject.toml"
- name: Install uv
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index e52f25a..b8f8db7 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -2,11 +2,17 @@ name: checks
on:
pull_request:
workflow_dispatch:
+
+permissions:
+ contents: read
+
jobs:
lint:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
+ with:
+ persist-credentials: false
- uses: ./.github/actions/setup-python
- run: |
uv run ruff check --fix
@@ -15,13 +21,17 @@ jobs:
typecheck:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
+ with:
+ persist-credentials: false
- uses: ./.github/actions/setup-python
- run: uv run ty check
vulture:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
+ with:
+ persist-credentials: false
- uses: ./.github/actions/setup-python
- run: uv run vulture --min-confidence 100 --exclude ".venv" .
diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml
index 73ddf28..d49809e 100644
--- a/.github/workflows/pytest.yml
+++ b/.github/workflows/pytest.yml
@@ -10,11 +10,24 @@ jobs:
pytest:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
+ with:
+ persist-credentials: false
- uses: ./.github/actions/setup-python
- run: |
uv run pytest
+ # Browser/Playwright tests are excluded by default (pytest.ini) and run
+ # only on pull requests, where we install Chromium for them.
+ - name: Install Playwright browser
+ if: github.event_name == 'pull_request'
+ run: |
+ uv run playwright install --with-deps chromium
+ - name: Run browser tests
+ if: github.event_name == 'pull_request'
+ run: |
+ uv run pytest -m browser
+
- name: Run tests (with coverage)
run: |
uv run pytest \
@@ -24,7 +37,7 @@ jobs:
| tee pytest-coverage.txt
- name: Pytest coverage comment
- uses: MishaKav/pytest-coverage-comment@main
+ uses: MishaKav/pytest-coverage-comment@dda025d84b1dd831193ed9cd5b1d7e33c79efa9b # v1.1.59
with:
pytest-coverage-path: ./pytest-coverage.txt
junitxml-path: ./pytest.xml
diff --git a/docs/schema/cheatsheet.schema.json b/docs/schema/cheatsheet.schema.json
index e255089..a988c01 100644
--- a/docs/schema/cheatsheet.schema.json
+++ b/docs/schema/cheatsheet.schema.json
@@ -22,6 +22,20 @@
"default": false,
"description": "When true, free-form shortcut text is allowed; requires RenderKeys=false."
},
+ "theme": {
+ "type": "string",
+ "minLength": 1,
+ "default": "catppuccin",
+ "description": "Theme name: a built-in (e.g. 'catppuccin') or a user theme in the project's themes/ directory. Defaults to 'catppuccin'."
+ },
+ "custom_css": {
+ "type": "string",
+ "description": "Filename of a custom CSS file (resolved from the project's styles/ directory), layered after the theme."
+ },
+ "custom_css_inline": {
+ "type": "string",
+ "description": "Inline custom CSS, emitted last in the cascade so it overrides the theme and the custom CSS file."
+ },
"layout": {
"type": "object",
"additionalProperties": false,
diff --git a/pyproject.toml b/pyproject.toml
index 74a8320..e323fc9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -11,6 +11,7 @@ dev = [
"prek>=0.3.6",
"pytest>=8.4.2",
"pytest-cov>=7.1.0",
+ "pytest-playwright>=0.7.1",
"ruff>=0.14.0",
"ty>=0.0.1a22",
"vulture>=2.14",
diff --git a/pytest.ini b/pytest.ini
index edcbd06..19f1050 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -3,9 +3,12 @@ testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
-addopts =
+addopts =
-v
--tb=short
--strict-markers
--disable-warnings
+ -m "not browser"
+markers =
+ browser: browser-based Playwright tests
diff --git a/src/koalakeys/generate_cheatsheet.py b/src/koalakeys/generate_cheatsheet.py
index 0aeb394..3c56305 100644
--- a/src/koalakeys/generate_cheatsheet.py
+++ b/src/koalakeys/generate_cheatsheet.py
@@ -10,6 +10,7 @@
from koalakeys.logger import get_logger
from koalakeys.template_renderer import render_template
+from koalakeys.theming import ThemeError, resolve_theme, sanitize_css
from koalakeys.validate_yaml import lint_yaml, validate_yaml
yaml_safe = YAML(typ="safe")
@@ -25,6 +26,8 @@
OUTPUT_DIR = Path(os.getenv("CHEATSHEET_OUTPUT_DIR") or PROJECT_ROOT / "output")
CHEATSHEETS_DIR = PROJECT_ROOT / "cheatsheets"
+THEMES_DIR = PROJECT_ROOT / "themes"
+STYLES_DIR = PROJECT_ROOT / "styles"
LAYOUTS_DIR = PACKAGE_DIR / "layouts"
OUTPUT_DIR.mkdir(exist_ok=True, parents=True)
@@ -135,6 +138,41 @@ def get_layout_info(data):
}
+def load_custom_css_file(filename):
+ if not filename:
+ return ""
+ path = STYLES_DIR / filename
+ try:
+ return sanitize_css(path.read_text(encoding="utf-8"))
+ except FileNotFoundError:
+ logging.error(f"Custom CSS file not found: {path}")
+ return ""
+ except OSError as e:
+ logging.error(f"Error reading custom CSS file '{path}': {e}")
+ return ""
+
+
+def apply_styling(data):
+ """Resolve the cheatsheet's theme and custom CSS into render-ready context.
+
+ Returns True on success; False if the theme cannot be resolved.
+ """
+ try:
+ theme = resolve_theme(data.get("theme"), themes_dir=THEMES_DIR)
+ except ThemeError as e:
+ logging.error(f"Theme error: {e}")
+ return False
+
+ data["theme_token_css"] = theme.render_token_css()
+ data["theme_font_url"] = theme.font_url
+ data["theme_default_is_dark"] = theme.default_is_dark
+ data["theme_both_modes"] = theme.both_modes
+ data["theme_custom_css"] = theme.custom_css
+ data["custom_css_file"] = load_custom_css_file(data.get("custom_css"))
+ data["custom_css_inline"] = sanitize_css(str(data.get("custom_css_inline") or ""))
+ return True
+
+
def generate_html(data, keyboard_layouts, system_mappings):
template_path = "cheatsheets/cheatsheet-template.html"
layout_info = get_layout_info(data)
@@ -144,6 +182,9 @@ def generate_html(data, keyboard_layouts, system_mappings):
data["render_keys"] = data.get("RenderKeys", True)
data["allow_text"] = data.get("AllowText", False)
+ if not apply_styling(data):
+ return None
+
return render_template(template_path, data)
diff --git a/src/koalakeys/templates/base.html b/src/koalakeys/templates/base.html
index 26c756c..3f2b25b 100644
--- a/src/koalakeys/templates/base.html
+++ b/src/koalakeys/templates/base.html
@@ -4,11 +4,11 @@
-
+ {% block fonts %}{% endblock %}
{% block title %}{% endblock %}
{% block page_styles %}{% endblock %}
-
+
{% block content %}{% endblock %}
{% block javascript %}{% endblock %}
diff --git a/src/koalakeys/templates/cheatsheets/assets/cheatsheets.css b/src/koalakeys/templates/cheatsheets/assets/cheatsheets.css
index 6a6d792..d3c5aa9 100644
--- a/src/koalakeys/templates/cheatsheets/assets/cheatsheets.css
+++ b/src/koalakeys/templates/cheatsheets/assets/cheatsheets.css
@@ -1,89 +1,23 @@
-/* Catppuccin Latte (Light) Theme */
-:root {
- --latte-rosewater: #dc8a78;
- --latte-flamingo: #dd7878;
- --latte-pink: #ea76cb;
- --latte-mauve: #8839ef;
- --latte-red: #d20f39;
- --latte-maroon: #e64553;
- --latte-peach: #fe640b;
- --latte-yellow: #df8e1d;
- --latte-green: #40a02b;
- --latte-teal: #179299;
- --latte-sky: #04a5e5;
- --latte-sapphire: #209fb5;
- --latte-blue: #1e66f5;
- --latte-lavender: #7287fd;
- --latte-text: #4c4f69;
- --latte-subtext1: #5c5f77;
- --latte-subtext0: #6c6f85;
- --latte-overlay2: #7c7f93;
- --latte-overlay1: #8c8fa1;
- --latte-overlay0: #9ca0b0;
- --latte-surface2: #acb0be;
- --latte-surface1: #bcc0cc;
- --latte-surface0: #ccd0da;
- --latte-base: #eff1f5;
- --latte-mantle: #e6e9ef;
- --latte-crust: #dce0e8;
-}
-
-/* Catppuccin Mocha (Dark) Theme */
-.dark-mode {
- --mocha-rosewater: #f5e0dc;
- --mocha-flamingo: #f2cdcd;
- --mocha-pink: #f5c2e7;
- --mocha-mauve: #cba6f7;
- --mocha-red: #f38ba8;
- --mocha-maroon: #eba0ac;
- --mocha-peach: #fab387;
- --mocha-yellow: #f9e2af;
- --mocha-green: #a6e3a1;
- --mocha-teal: #94e2d5;
- --mocha-sky: #89dceb;
- --mocha-sapphire: #74c7ec;
- --mocha-blue: #89b4fa;
- --mocha-lavender: #b4befe;
- --mocha-text: #cdd6f4;
- --mocha-subtext1: #bac2de;
- --mocha-subtext0: #a6adc8;
- --mocha-overlay2: #9399b2;
- --mocha-overlay1: #7f849c;
- --mocha-overlay0: #6c7086;
- --mocha-surface2: #585b70;
- --mocha-surface1: #45475a;
- --mocha-surface0: #313244;
- --mocha-base: #1e1e2e;
- --mocha-mantle: #181825;
- --mocha-crust: #11111b;
-}
+/*
+ * Component stylesheet — consumes only the --kk-* semantic token contract.
+ * Token VALUES are emitted per-theme by the generator (see koalakeys/themes.py);
+ * this file never references palette-specific variables or mode overrides.
+ * Token reference: docs/themes/tokens.md
+ */
body {
- font-family: "Iosevka Web", monospace;
- line-height: 1.6;
- color: var(--latte-text);
+ font-family: var(--kk-font);
+ font-size: var(--kk-font-size);
+ line-height: var(--kk-line-height);
+ color: var(--kk-text);
max-width: 1400px;
margin: 0 auto;
padding: 0 10px 20px 90px;
- background-color: var(--latte-base);
+ background-color: var(--kk-bg);
transition:
background-color 0.3s,
color 0.3s;
}
-body:not(.dark-mode) {
- --text: var(--latte-text);
- --subtext1: var(--latte-subtext1);
- --subtext0: var(--latte-subtext0);
- --overlay2: var(--latte-overlay2);
- --overlay1: var(--latte-overlay1);
- --overlay0: var(--latte-overlay0);
- --surface2: var(--latte-surface2);
- --surface1: var(--latte-surface1);
- --surface0: var(--latte-surface0);
- --base: var(--latte-base);
- --mantle: var(--latte-mantle);
- --crust: var(--latte-crust);
-}
#content-wrapper {
transition: padding-left 0.3s ease;
padding-left: 0;
@@ -91,12 +25,8 @@ body:not(.dark-mode) {
body.nav-active #content-wrapper {
padding-left: 200px;
}
-body.dark-mode {
- background-color: var(--mocha-base);
- color: var(--mocha-text);
-}
h1 {
- color: var(--latte-mauve);
+ color: var(--kk-accent);
text-align: center;
margin-bottom: 10px;
font-weight: 700;
@@ -112,42 +42,30 @@ h1 {
padding: 0;
transform: translateX(-50%);
}
-
-body.dark-mode h1 {
- color: var(--mocha-mauve);
-}
.section {
- background-color: var(--mantle);
- border-radius: 6px;
+ background-color: var(--kk-surface);
+ border-radius: var(--kk-radius);
padding: 8px;
- margin-bottom: 15px;
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+ margin-bottom: var(--kk-section-gap);
+ box-shadow: 0 4px 6px var(--kk-shadow);
transition:
background-color 0.3s,
color 0.3s,
box-shadow 0.3s;
}
-body.dark-mode .section {
- background-color: var(--mocha-surface0);
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
-}
.section h2 {
- color: var(--latte-lavender);
- border-bottom: 1px solid var(--latte-lavender);
+ color: var(--kk-accent-2);
+ border-bottom: 1px solid var(--kk-accent-2);
padding-bottom: 5px;
margin-top: 0;
margin-bottom: 10px;
font-weight: 600;
font-size: 1.3rem;
}
-body.dark-mode .section h2 {
- color: var(--mocha-lavender);
- border-bottom-color: var(--mocha-lavender);
-}
.shortcuts-container {
display: grid;
grid-template-columns: repeat(4, 1fr);
- gap: 12px;
+ gap: var(--kk-gap);
}
@media (max-width: 1200px) {
.shortcuts-container {
@@ -165,32 +83,24 @@ body.dark-mode .section h2 {
}
}
.shortcut {
- background-color: var(--crust);
- border-radius: 5px;
+ background-color: var(--kk-card);
+ border-radius: var(--kk-radius);
cursor: pointer;
transition:
background-color 0.3s,
transform 0.2s,
box-shadow 0.2s;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+ box-shadow: 0 2px 4px var(--kk-shadow);
display: flex;
justify-content: space-between;
align-items: center;
min-height: 40px;
overflow: hidden;
}
-body.dark-mode .shortcut {
- background-color: var(--mocha-surface1);
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
-}
.shortcut:hover {
- background-color: var(--surface1);
+ background-color: var(--kk-card-hover);
transform: translateY(-2px);
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
-}
-body.dark-mode .shortcut:hover {
- background-color: var(--mocha-surface2);
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4);
+ box-shadow: 0 4px 8px var(--kk-shadow-strong);
}
.shortcut:active {
transform: translateY(0);
@@ -198,8 +108,8 @@ body.dark-mode .shortcut:hover {
.shortcut-key {
font-weight: 600;
font-size: 0.95rem;
- color: var(--latte-mauve);
- font-family: "Iosevka Web", monospace;
+ color: var(--kk-key-text);
+ font-family: var(--kk-font);
transition:
color 0.3s,
font-size 0.3s;
@@ -213,26 +123,15 @@ body.dark-mode .shortcut:hover {
.text-shortcut {
font-size: 0.95rem;
- color: var(--latte-text);
-}
-
-body.dark-mode .text-shortcut {
- color: var(--mocha-text);
-}
-body.dark-mode .shortcut-key {
- color: var(--mocha-red);
+ color: var(--kk-text);
}
.shortcut-key .key-part {
- color: var(--latte-mauve);
-}
-
-body.dark-mode .shortcut-key .key-part {
- color: var(--mocha-red);
+ color: var(--kk-key-text);
}
.shortcut-key .separator {
- color: var(--latte-subtext0);
+ color: var(--kk-text-muted);
font-weight: normal;
margin-right: 0.3em;
margin-left: 0.3em;
@@ -242,10 +141,6 @@ body.dark-mode .shortcut-key .key-part {
white-space-collapse: preserve;
}
-body.dark-mode .shortcut-key .separator {
- color: var(--mocha-subtext0);
-}
-
.shortcut-key .modifier-symbol {
font-size: 1.5em;
display: inline-block;
@@ -285,16 +180,12 @@ body.dark-mode .shortcut-key .separator {
max-width: 1000px;
margin: 0 auto;
border-radius: 10px;
- background: var(--crust);
+ background: var(--kk-keyboard-bg);
padding: 5px;
transition:
background-color 0.3s,
box-shadow 0.3s;
- box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
-}
-body.dark-mode #keyboard {
- background: var(--mocha-crust);
- box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3);
+ box-shadow: 0 6px 12px var(--kk-shadow);
}
.row {
display: flex;
@@ -302,11 +193,11 @@ body.dark-mode #keyboard {
margin-bottom: 5px;
}
.key {
- width: 50px;
- height: 50px;
- background: var(--crust);
- border: 1px solid var(--overlay0);
- border-radius: 4px;
+ width: var(--kk-key-size);
+ height: var(--kk-key-size);
+ background: var(--kk-key-bg);
+ border: 1px solid var(--kk-border);
+ border-radius: calc(var(--kk-radius) - 2px);
display: flex;
justify-content: center;
align-items: center;
@@ -317,8 +208,8 @@ body.dark-mode #keyboard {
color 0.3s,
border-color 0.3s,
box-shadow 0.3s;
- color: var(--text);
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+ color: var(--kk-text);
+ box-shadow: 0 2px 4px var(--kk-shadow);
cursor: pointer;
flex-grow: 1;
margin-right: 4px;
@@ -326,87 +217,39 @@ body.dark-mode #keyboard {
.key:last-child {
margin-right: 0;
}
-body.dark-mode .key {
- background: var(--mocha-surface0);
- color: var(--mocha-text);
- border-color: var(--mocha-overlay0);
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
-}
-.key.active {
- background: var(--latte-green);
- color: var(--latte-base);
- box-shadow: 0 0 8px var(--latte-green);
-}
-body.dark-mode .key.active {
- background: var(--mocha-green);
- color: var(--mocha-base);
- box-shadow: 0 0 8px var(--mocha-green);
-}
+.key.active,
.key.active-step-1 {
- background: var(--latte-green);
- color: var(--latte-base);
- box-shadow: 0 0 8px var(--latte-green);
-}
-body.dark-mode .key.active-step-1 {
- background: var(--mocha-green);
- color: var(--mocha-base);
- box-shadow: 0 0 8px var(--mocha-green);
+ background: var(--kk-step-1);
+ color: var(--kk-step-text);
+ box-shadow: 0 0 8px var(--kk-step-1);
}
.key.active-step-2 {
- background: var(--latte-sapphire);
- color: var(--latte-base);
- box-shadow: 0 0 8px var(--latte-sapphire);
-}
-body.dark-mode .key.active-step-2 {
- background: var(--mocha-sapphire);
- color: var(--mocha-base);
- box-shadow: 0 0 8px var(--mocha-sapphire);
+ background: var(--kk-step-2);
+ color: var(--kk-step-text);
+ box-shadow: 0 0 8px var(--kk-step-2);
}
.key.active-step-3 {
- background: var(--latte-peach);
- color: var(--latte-base);
- box-shadow: 0 0 8px var(--latte-peach);
-}
-body.dark-mode .key.active-step-3 {
- background: var(--mocha-peach);
- color: var(--mocha-base);
- box-shadow: 0 0 8px var(--mocha-peach);
+ background: var(--kk-step-3);
+ color: var(--kk-step-text);
+ box-shadow: 0 0 8px var(--kk-step-3);
}
.key.active-step-4 {
- background: var(--latte-mauve);
- color: var(--latte-base);
- box-shadow: 0 0 8px var(--latte-mauve);
-}
-body.dark-mode .key.active-step-4 {
- background: var(--mocha-mauve);
- color: var(--mocha-base);
- box-shadow: 0 0 8px var(--mocha-mauve);
+ background: var(--kk-step-4);
+ color: var(--kk-step-text);
+ box-shadow: 0 0 8px var(--kk-step-4);
}
.key.active-step-5 {
- background: var(--latte-lavender);
- color: var(--latte-base);
- box-shadow: 0 0 8px var(--latte-lavender);
-}
-body.dark-mode .key.active-step-5 {
- background: var(--mocha-lavender);
- color: var(--mocha-base);
- box-shadow: 0 0 8px var(--mocha-lavender);
+ background: var(--kk-step-5);
+ color: var(--kk-step-text);
+ box-shadow: 0 0 8px var(--kk-step-5);
}
/* Default style for steps greater than 5 */
.key[class*="active-step-"]:not(.active-step-1):not(.active-step-2):not(
.active-step-3
):not(.active-step-4):not(.active-step-5) {
- background: var(--latte-yellow);
- color: var(--latte-base);
- box-shadow: 0 0 8px var(--latte-yellow);
-}
-body.dark-mode
- .key[class*="active-step-"]:not(.active-step-1):not(.active-step-2):not(
- .active-step-3
- ):not(.active-step-4):not(.active-step-5) {
- background: var(--mocha-yellow);
- color: var(--mocha-base);
- box-shadow: 0 0 8px var(--mocha-yellow);
+ background: var(--kk-step-overflow);
+ color: var(--kk-step-text);
+ box-shadow: 0 0 8px var(--kk-step-overflow);
}
.key__wide {
width: 75px;
@@ -468,10 +311,7 @@ body.dark-mode
text-align: center;
margin-bottom: 20px;
font-size: 1.1rem;
- color: var(--latte-subtext1);
-}
-body.dark-mode .layout-info {
- color: var(--mocha-subtext1);
+ color: var(--kk-text-muted);
}
.top-content {
@@ -500,10 +340,10 @@ body.nav-active .search-container {
width: 100%;
padding: 10px;
font-size: 16px;
- border: 2px solid var(--latte-surface2);
- border-radius: 5px;
- background-color: var(--latte-base);
- color: var(--latte-text);
+ border: 2px solid var(--kk-border-input);
+ border-radius: var(--kk-radius);
+ background-color: var(--kk-bg);
+ color: var(--kk-text);
transition:
border-color 0.3s,
background-color 0.3s,
@@ -519,26 +359,15 @@ body.nav-active #shortcut-search {
width: calc(100% - 200px);
}
-body.dark-mode #shortcut-search {
- border-color: var(--mocha-surface2);
- background-color: var(--mocha-base);
- color: var(--mocha-text);
-}
-
#shortcut-search:focus {
outline: none;
- border-color: var(--latte-lavender);
- box-shadow: 0 0 5px var(--latte-lavender);
-}
-
-body.dark-mode #shortcut-search:focus {
- border-color: var(--mocha-lavender);
- box-shadow: 0 0 5px var(--mocha-lavender);
+ border-color: var(--kk-accent-2);
+ box-shadow: 0 0 5px var(--kk-accent-2);
}
#content-wrapper {
padding-top: 10px;
- max-width: 1200px;
+ max-width: var(--kk-max-width);
margin-left: auto;
margin-right: auto;
transition: padding-left 0.3s ease;
@@ -556,14 +385,10 @@ body.nav-active #content-wrapper {
border: none;
cursor: pointer;
font-size: 24px;
- color: var(--text);
+ color: var(--kk-text);
transition: color 0.3s;
}
-body.dark-mode #dark-mode-toggle {
- color: var(--mocha-text);
-}
-
#category-nav-toggle {
flex: 0 0 auto;
}
@@ -572,7 +397,7 @@ body.dark-mode #dark-mode-toggle {
top: 20px;
left: 10px;
z-index: 1000;
- background: var(--latte-surface1);
+ background: var(--kk-card-hover);
border: none;
border-radius: 50%;
width: 40px;
@@ -581,15 +406,12 @@ body.dark-mode #dark-mode-toggle {
display: flex;
align-items: center;
justify-content: center;
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
+ box-shadow: 0 2px 5px var(--kk-shadow-strong);
transition:
background-color 0.3s,
transform 0.2s,
left 0.3s;
}
-body.dark-mode #category-nav-toggle {
- background: var(--mocha-surface1);
-}
#category-nav-toggle:hover {
transform: scale(1.1);
}
@@ -608,32 +430,22 @@ body.dark-mode #category-nav-toggle {
left: -200px;
width: 200px;
height: 100vh;
- background: var(--surface0);
+ background: var(--kk-surface);
overflow-y: auto;
transition: left 0.3s ease;
z-index: 998;
padding: 70px 15px 20px;
- box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
+ box-shadow: 2px 0 5px var(--kk-shadow);
}
#category-nav.active {
left: 0;
}
-body.dark-mode #category-nav {
- background: var(--mocha-surface0);
- box-shadow: 2px 0 5px rgba(0, 0, 0, 0.3);
-}
#category-nav ul {
display: none;
}
#category-nav.active ul {
display: block;
}
-body.dark-mode #category-nav {
- background: var(--mocha-surface0);
-}
-#category-nav.active {
- left: 0;
-}
#category-nav-toggle {
z-index: 999;
}
@@ -646,17 +458,11 @@ body.dark-mode #category-nav {
margin-bottom: 10px;
}
#category-nav a {
- color: var(--latte-text);
+ color: var(--kk-text);
text-decoration: none;
font-size: 1rem;
transition: color 0.2s;
}
-body.dark-mode #category-nav a {
- color: var(--mocha-text);
-}
#category-nav a:hover {
- color: var(--latte-lavender);
-}
-body.dark-mode #category-nav a:hover {
- color: var(--mocha-lavender);
+ color: var(--kk-accent-2);
}
diff --git a/src/koalakeys/templates/cheatsheets/cheatsheet-template.html b/src/koalakeys/templates/cheatsheets/cheatsheet-template.html
index 4263047..b9aee56 100644
--- a/src/koalakeys/templates/cheatsheets/cheatsheet-template.html
+++ b/src/koalakeys/templates/cheatsheets/cheatsheet-template.html
@@ -2,9 +2,21 @@
{% block title %}{{ title }} Cheatsheet{% endblock %}
+{% block body_class %}{% if theme_default_is_dark %}dark-mode{% endif %}{% endblock %}
+
+{% block fonts %}{% if theme_font_url %}{% endif %}{% endblock %}
+
{% block page_styles %}
{% endblock %}
diff --git a/src/koalakeys/templates/cheatsheets/components/body.html b/src/koalakeys/templates/cheatsheets/components/body.html
index db6d7ff..7e1f06f 100644
--- a/src/koalakeys/templates/cheatsheets/components/body.html
+++ b/src/koalakeys/templates/cheatsheets/components/body.html
@@ -18,11 +18,13 @@ {{ title }}
+ {% if theme_both_modes %}
+ {% endif %}