summaryrefslogtreecommitdiffstats
path: root/themes
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-04-18 19:02:18 +0200
committerDanilo M. <danix@danix.xyz>2026-04-18 19:02:18 +0200
commit17048ab79312f1752a296ab150984a4ef30aed5c (patch)
tree732a7f1e6cf78d0d75e737848ad9328df7a8dd77 /themes
parent46779476a570346661a2741607265caed42829b2 (diff)
downloaddanixxyz-17048ab79312f1752a296ab150984a4ef30aed5c.tar.gz
danixxyz-17048ab79312f1752a296ab150984a4ef30aed5c.zip
refactor: syntax highlighting with Catppuccin Macchiato and copy buttons
- Add [markup.highlight] config: noClasses=false for CSS class output, lineNos=true with lineNumbersInTable=true for proper line number rendering - Create render-codeblock.html render hook to intercept fenced code blocks and wrap with header bar (language label + copy button) - Replace chroma-custom.css entirely with Catppuccin Macchiato palette (dark theme) + Catppuccin Latte (light theme), with full token color mapping - Create code-copy.js: copy-to-clipboard logic with language pretty-name map (bash→Shell, js→JavaScript, etc.), icon swap (copy→check for 2s), and aria-live region for screen reader announcement (WCAG 4.1.3) - Update baseof.html to load code-copy.js on page kind with Hugo Pipes - WCAG AA compliance: line number contrast fixed to ~3.5:1 (--ctp-overlay0), light theme copy button color to 4.1:1 (#6c6f85), focus outline 6.21:1 (--ctp-lavender), screen reader announcements via aria-live All code blocks now render with: syntax highlighting (noClasses=true fixed), line numbers with proper table layout, language label in header, copy button with feather icons, both dark and light theme support. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Diffstat (limited to 'themes')
-rw-r--r--themes/danix-xyz-hacker/assets/css/chroma-custom.css418
-rw-r--r--themes/danix-xyz-hacker/assets/js/code-copy.js79
-rw-r--r--themes/danix-xyz-hacker/layouts/_default/_markup/render-codeblock.html23
-rw-r--r--themes/danix-xyz-hacker/layouts/_default/baseof.html6
4 files changed, 422 insertions, 104 deletions
diff --git a/themes/danix-xyz-hacker/assets/css/chroma-custom.css b/themes/danix-xyz-hacker/assets/css/chroma-custom.css
index 3e91d50..11f4a4a 100644
--- a/themes/danix-xyz-hacker/assets/css/chroma-custom.css
+++ b/themes/danix-xyz-hacker/assets/css/chroma-custom.css
@@ -1,77 +1,165 @@
-/* Chroma Syntax Highlighting Theme */
-/* Dark/Light theme support with custom properties */
+/* === Chroma Custom — Catppuccin Macchiato === */
:root {
- /* Dark theme colors */
- --chroma-bg-dark: #0c1520;
- --chroma-bg-light: #f0f4f8;
- --chroma-text-dark: #c4d6e8;
- --chroma-text-light: #0d1b2a;
- --chroma-keyword: #a855f7;
- --chroma-string: #00ff88;
- --chroma-number: #38bdf8;
- --chroma-comment: #7a9bb8;
- --chroma-error: #ff6b6b;
-}
-
-/* Default dark theme for .highlight */
-.highlight {
- background-color: var(--chroma-bg-dark);
- color: var(--chroma-text-dark);
- padding: 1rem;
- border-radius: 0.375rem;
+ /* Catppuccin Macchiato palette */
+ --ctp-base: #24273a;
+ --ctp-surface0: #363a4f;
+ --ctp-surface1: #494d64;
+ --ctp-overlay0: #6e738d;
+ --ctp-text: #cad3f5;
+ --ctp-subtext1: #b8c0e0;
+ --ctp-lavender: #b7bdf8;
+ --ctp-blue: #8aadf4;
+ --ctp-sapphire: #7dc4e4;
+ --ctp-sky: #91d7e3;
+ --ctp-teal: #8bd5ca;
+ --ctp-green: #a6da95;
+ --ctp-yellow: #eed49f;
+ --ctp-peach: #f5a97f;
+ --ctp-maroon: #ee99a0;
+ --ctp-red: #ed8796;
+ --ctp-mauve: #c6a0f6;
+ --ctp-pink: #f5bde6;
+}
+
+/* === Code block wrapper and header bar === */
+
+.code-block-wrapper {
+ margin: 1.5rem 0;
+ border-radius: 0.5rem;
+ overflow: hidden;
+ border: 1px solid var(--border);
+}
+
+.code-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0.375rem 0.75rem;
+ background-color: var(--ctp-surface0);
+ border-bottom: 1px solid var(--ctp-surface1);
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 0.75rem;
+}
+
+.code-lang-label {
+ color: var(--ctp-subtext1);
+ letter-spacing: 0.05em;
+}
+
+.code-copy-wrapper {
+ display: flex;
+ align-items: center;
+ gap: 0.25rem;
+}
+
+.code-copy-btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ background: transparent;
+ border: none;
+ cursor: pointer;
+ padding: 0.25rem;
+ border-radius: 0.25rem;
+ color: var(--ctp-overlay0);
+ transition: color 0.15s ease, background 0.15s ease;
+ line-height: 1;
+}
+
+.code-copy-btn:hover {
+ color: var(--ctp-text);
+ background: rgba(202, 211, 245, 0.08);
+}
+
+.code-copy-btn:focus-visible {
+ outline: 2px solid var(--ctp-lavender);
+ outline-offset: 2px;
+}
+
+.code-copy-btn [data-feather],
+.code-copy-btn svg {
+ width: 14px !important;
+ height: 14px !important;
+ stroke-width: 2px;
+}
+
+.code-copy-btn .hidden {
+ display: none;
+}
+
+.code-copy-btn.is-copied {
+ color: var(--ctp-green);
+}
+
+/* === Code body === */
+
+.code-body {
overflow-x: auto;
- font-size: 0.875rem;
- line-height: 1.5;
-}
-
-/* Light theme override */
-html.theme-light .highlight {
- background-color: var(--chroma-bg-light);
- color: var(--chroma-text-light);
-}
-
-/* Light theme token colors for proper contrast */
-html.theme-light .highlight .k,
-html.theme-light .highlight .kc,
-html.theme-light .highlight .kd,
-html.theme-light .highlight .kn,
-html.theme-light .highlight .kp,
-html.theme-light .highlight .kr,
-html.theme-light .highlight .kt {
- color: #7c3aed;
-}
-
-html.theme-light .highlight .s,
-html.theme-light .highlight .sb,
-html.theme-light .highlight .sc,
-html.theme-light .highlight .sd,
-html.theme-light .highlight .s1,
-html.theme-light .highlight .s2,
-html.theme-light .highlight .se,
-html.theme-light .highlight .sh,
-html.theme-light .highlight .si,
-html.theme-light .highlight .sx {
- color: #059669;
-}
-
-html.theme-light .highlight .m,
-html.theme-light .highlight .mb,
-html.theme-light .highlight .mf,
-html.theme-light .highlight .mh,
-html.theme-light .highlight .mi,
-html.theme-light .highlight .il,
-html.theme-light .highlight .mo {
- color: #0284c7;
}
-html.theme-light .highlight .c,
-html.theme-light .highlight .c1,
-html.theme-light .highlight .cm {
- color: #6888a8;
+.code-body .highlight {
+ margin: 0;
+ border-radius: 0;
+ border: none;
+ background-color: var(--ctp-base);
}
-/* Keyword tokens - purple */
+/* === Reset conflicts with main.css base styles === */
+
+.code-block-wrapper pre,
+.prose .code-block-wrapper pre,
+.prose-invert .code-block-wrapper pre {
+ margin: 0;
+ padding: 0;
+ background: transparent;
+ border: none;
+ border-radius: 0;
+ overflow-x: visible;
+}
+
+/* === Chroma table layout (lineNumbersInTable = true) === */
+
+.highlight table {
+ border-collapse: collapse;
+ width: 100%;
+}
+
+.highlight td {
+ padding: 0;
+ vertical-align: top;
+}
+
+/* Line number column — not selectable */
+/* color: --ctp-overlay0 (#6e738d) on --ctp-surface0 (#363a4f) = ~3.5:1 */
+.highlight .lnt,
+.highlight .ln {
+ padding: 0.875rem 0.75rem 0.875rem 1rem;
+ color: var(--ctp-overlay0);
+ user-select: none;
+ -webkit-user-select: none;
+ min-width: 2.5rem;
+ text-align: right;
+ border-right: 1px solid var(--ctp-surface1);
+ font-size: 0.8125rem;
+}
+
+.highlight .lntd:first-child {
+ background-color: var(--ctp-surface0);
+}
+
+.highlight .lntd:last-child pre {
+ padding: 0.875rem 1rem;
+}
+
+/* === Syntax token colors (dark theme default) === */
+
+.highlight {
+ background-color: var(--ctp-base);
+ color: var(--ctp-text);
+}
+
+/* Keywords — mauve */
.highlight .k,
.highlight .kc,
.highlight .kd,
@@ -79,25 +167,29 @@ html.theme-light .highlight .cm {
.highlight .kp,
.highlight .kr,
.highlight .kt {
- color: var(--chroma-keyword);
+ color: var(--ctp-mauve);
font-weight: 500;
}
-/* String tokens - green */
+/* Strings — green */
.highlight .s,
+.highlight .sa,
.highlight .sb,
.highlight .sc,
+.highlight .dl,
.highlight .sd,
.highlight .s1,
.highlight .s2,
.highlight .se,
.highlight .sh,
.highlight .si,
-.highlight .sx {
- color: var(--chroma-string);
+.highlight .sx,
+.highlight .sr,
+.highlight .ss {
+ color: var(--ctp-green);
}
-/* Number tokens - cyan */
+/* Numbers — peach */
.highlight .m,
.highlight .mb,
.highlight .mf,
@@ -105,53 +197,171 @@ html.theme-light .highlight .cm {
.highlight .mi,
.highlight .il,
.highlight .mo {
- color: var(--chroma-number);
+ color: var(--ctp-peach);
}
-/* Comment tokens - gray, italic */
+/* Comments — overlay0, italic */
.highlight .c,
.highlight .c1,
-.highlight .cm {
- color: var(--chroma-comment);
+.highlight .cm,
+.highlight .cs,
+.highlight .cp,
+.highlight .cpf {
+ color: var(--ctp-overlay0);
+ font-style: italic;
+}
+
+/* Operators — sky */
+.highlight .o,
+.highlight .ow {
+ color: var(--ctp-sky);
+}
+
+/* Names / Identifiers */
+.highlight .n {
+ color: var(--ctp-text);
+}
+
+.highlight .na {
+ color: var(--ctp-yellow);
+}
+
+.highlight .nb {
+ color: var(--ctp-blue);
+}
+
+.highlight .nc {
+ color: var(--ctp-yellow);
+}
+
+.highlight .nd {
+ color: var(--ctp-pink);
+}
+
+.highlight .ne {
+ color: var(--ctp-maroon);
+}
+
+.highlight .nf,
+.highlight .fm {
+ color: var(--ctp-blue);
+}
+
+.highlight .ni {
+ color: var(--ctp-text);
+}
+
+.highlight .nl {
+ color: var(--ctp-teal);
+}
+
+.highlight .nn {
+ color: var(--ctp-yellow);
+}
+
+.highlight .nt {
+ color: var(--ctp-mauve);
+}
+
+.highlight .nv,
+.highlight .vc,
+.highlight .vg,
+.highlight .vi {
+ color: var(--ctp-text);
+}
+
+/* Punctuation */
+.highlight .p {
+ color: var(--ctp-subtext1);
+}
+
+/* Generic tokens (diff output etc.) */
+.highlight .gd {
+ color: var(--ctp-red);
+ background: rgba(237, 135, 150, 0.1);
+}
+
+.highlight .gi {
+ color: var(--ctp-green);
+ background: rgba(166, 218, 149, 0.1);
+}
+
+.highlight .gh {
+ color: var(--ctp-lavender);
+ font-weight: bold;
+}
+
+.highlight .gu {
+ color: var(--ctp-overlay0);
+}
+
+.highlight .ge {
font-style: italic;
}
-/* Name tokens - default text color */
-.highlight .n,
-.highlight .na,
-.highlight .nb,
-.highlight .nc,
-.highlight .no,
-.highlight .nd,
-.highlight .ni,
-.highlight .nl,
-.highlight .nn,
-.highlight .nt,
-.highlight .nv {
- color: inherit;
+.highlight .gs {
+ font-weight: bold;
}
-/* Error tokens - red */
+/* Error */
.highlight .err {
- color: var(--chroma-error);
+ color: var(--ctp-red);
}
-/* Line numbers styling */
-.highlight .ln {
- color: var(--chroma-comment);
- user-select: none;
- -webkit-user-select: none;
+/* === Light theme overrides (Catppuccin Latte) === */
+
+html.theme-light .code-block-wrapper {
+ border-color: #ccd0da;
}
-/* Inline code is styled in main.css - just reset within code blocks */
+html.theme-light .code-header {
+ background-color: #dce0ea;
+ border-bottom-color: #bcc0cc;
+}
-/* Code block styling for pre tag */
-pre {
- margin: 0;
+html.theme-light .code-lang-label {
+ color: #5c5f77;
}
-pre code {
- background-color: transparent;
- color: inherit;
- padding: 0;
+html.theme-light .code-copy-btn {
+ color: #6c6f85;
+}
+
+html.theme-light .code-copy-btn:hover {
+ color: #4c4f69;
+ background: rgba(76, 79, 105, 0.08);
+}
+
+html.theme-light .code-body .highlight {
+ background-color: #eff1f5;
+ color: #4c4f69;
+}
+
+html.theme-light .highlight .lntd:first-child {
+ background-color: #e6e9ef;
+}
+
+html.theme-light .highlight .lnt,
+html.theme-light .highlight .ln {
+ color: #bcc0cc;
+ border-right-color: #bcc0cc;
+}
+
+/* Comments must be darker to read on light bg */
+html.theme-light .highlight .c,
+html.theme-light .highlight .c1,
+html.theme-light .highlight .cm,
+html.theme-light .highlight .cs,
+html.theme-light .highlight .cp,
+html.theme-light .highlight .cpf {
+ color: #7c7f93;
+}
+
+/* Text defaults */
+html.theme-light .highlight .n {
+ color: #4c4f69;
+}
+
+html.theme-light .highlight .p {
+ color: #6c6f85;
}
diff --git a/themes/danix-xyz-hacker/assets/js/code-copy.js b/themes/danix-xyz-hacker/assets/js/code-copy.js
new file mode 100644
index 0000000..bfcfd4a
--- /dev/null
+++ b/themes/danix-xyz-hacker/assets/js/code-copy.js
@@ -0,0 +1,79 @@
+(function () {
+ var LANG_NAMES = {
+ bash: 'Shell', sh: 'Shell', shell: 'Shell', zsh: 'Shell',
+ js: 'JavaScript', javascript: 'JavaScript',
+ ts: 'TypeScript', typescript: 'TypeScript',
+ go: 'Go',
+ py: 'Python', python: 'Python',
+ rs: 'Rust', rust: 'Rust',
+ html: 'HTML',
+ css: 'CSS',
+ toml: 'TOML',
+ yaml: 'YAML', yml: 'YAML',
+ json: 'JSON',
+ sql: 'SQL',
+ md: 'Markdown', markdown: 'Markdown',
+ c: 'C',
+ cpp: 'C++', 'c++': 'C++',
+ java: 'Java',
+ php: 'PHP',
+ ruby: 'Ruby', rb: 'Ruby',
+ swift: 'Swift',
+ kotlin: 'Kotlin', kt: 'Kotlin',
+ dockerfile: 'Dockerfile',
+ makefile: 'Makefile',
+ text: 'Text', txt: 'Text',
+ };
+
+ function prettyName(lang) {
+ if (!lang) return '';
+ var key = lang.toLowerCase();
+ return LANG_NAMES[key] || (lang.charAt(0).toUpperCase() + lang.slice(1));
+ }
+
+ function getCodeText(wrapper) {
+ var el = wrapper.querySelector('.lntd:last-child code')
+ || wrapper.querySelector('.code-body code')
+ || wrapper.querySelector('.code-body pre');
+ return el ? el.innerText : '';
+ }
+
+ function initBlock(wrapper) {
+ var header = wrapper.querySelector('.code-header');
+ if (header) {
+ var label = wrapper.querySelector('.code-lang-label');
+ if (label) label.textContent = prettyName(header.getAttribute('data-lang') || '');
+ }
+
+ var btn = wrapper.querySelector('[data-copy-target]');
+ if (!btn) return;
+
+ btn.addEventListener('click', function () {
+ var text = getCodeText(wrapper);
+ if (!text) return;
+
+ navigator.clipboard.writeText(text).then(function () {
+ var copyIcon = btn.querySelector('[data-feather="copy"]');
+ var checkIcon = btn.querySelector('[data-feather="check"]');
+ var liveRegion = wrapper.querySelector('.code-copy-status');
+ if (copyIcon) copyIcon.style.display = 'none';
+ if (checkIcon) checkIcon.classList.remove('hidden');
+ btn.classList.add('is-copied');
+ if (liveRegion) liveRegion.textContent = 'Code copied to clipboard.';
+
+ setTimeout(function () {
+ if (copyIcon) copyIcon.style.display = '';
+ if (checkIcon) checkIcon.classList.add('hidden');
+ btn.classList.remove('is-copied');
+ if (liveRegion) liveRegion.textContent = '';
+ }, 2000);
+ }).catch(function () {
+ // silent fail for insecure contexts
+ });
+ });
+ }
+
+ document.addEventListener('DOMContentLoaded', function () {
+ document.querySelectorAll('.code-block-wrapper').forEach(initBlock);
+ });
+})();
diff --git a/themes/danix-xyz-hacker/layouts/_default/_markup/render-codeblock.html b/themes/danix-xyz-hacker/layouts/_default/_markup/render-codeblock.html
new file mode 100644
index 0000000..813c389
--- /dev/null
+++ b/themes/danix-xyz-hacker/layouts/_default/_markup/render-codeblock.html
@@ -0,0 +1,23 @@
+{{- $lang := .Type -}}
+{{- $hasLang := gt (len $lang) 0 -}}
+{{- $highlightLang := $lang -}}
+{{- if not $hasLang -}}{{- $highlightLang = "text" -}}{{- end -}}
+{{- $opts := dict "lineNos" true "lineNumbersInTable" true -}}
+
+<div class="code-block-wrapper">
+ {{- if $hasLang -}}
+ <div class="code-header" data-lang="{{ $lang }}">
+ <span class="code-lang-label">{{ $lang }}</span>
+ <div class="code-copy-wrapper">
+ <span role="status" aria-live="polite" class="sr-only code-copy-status"></span>
+ <button class="code-copy-btn" aria-label="Copy code" data-copy-target>
+ <i data-feather="copy" aria-hidden="true"></i>
+ <i data-feather="check" aria-hidden="true" class="hidden"></i>
+ </button>
+ </div>
+ </div>
+ {{- end -}}
+ <div class="code-body">
+ {{ highlight .Inner $highlightLang $opts }}
+ </div>
+</div>
diff --git a/themes/danix-xyz-hacker/layouts/_default/baseof.html b/themes/danix-xyz-hacker/layouts/_default/baseof.html
index 13f3fd2..9370157 100644
--- a/themes/danix-xyz-hacker/layouts/_default/baseof.html
+++ b/themes/danix-xyz-hacker/layouts/_default/baseof.html
@@ -101,6 +101,12 @@
<script src="{{ $progressScript.RelPermalink }}"></script>
{{ end }}
+ <!-- Code block copy button -->
+ {{ if eq .Kind "page" }}
+ {{ $codeScript := resources.Get "js/code-copy.js" | minify }}
+ <script src="{{ $codeScript.RelPermalink }}"></script>
+ {{ end }}
+
<!-- Matrix rain background effect -->
{{ with resources.Get "js/matrix-rain.js" }}
{{ $s := . | minify }}