Willem van den Ende 54651d2349 Add highlight.js syntax highlighting to source viewer
- Install highlight.js via npm with 8 language definitions
- SourceViewer hook applies syntax highlighting on mount/update
- Atom One Dark theme for dark mode, Atom One Light for light mode
- Add hooks directory, package.json, and package-lock.json
- Add demo document and screenshots
- Add rodney-docker.md documentation
- Ignore .rodney/ chrome data directory
2026-05-14 13:17:10 +01:00

410 lines
12 KiB
CSS

/* See the Tailwind configuration guide for advanced usage
https://tailwindcss.com/docs/configuration */
@import "tailwindcss" source(none);
@source "../css";
@source "../js";
@source "../../lib/firehose_web";
/* A Tailwind plugin that makes "hero-#{ICON}" classes available.
The heroicons installation itself is managed by your mix.exs */
@plugin "../vendor/heroicons";
/* daisyUI Tailwind Plugin. You can update this file by fetching the latest version with:
curl -sLO https://github.com/saadeghi/daisyui/releases/latest/download/daisyui.js
Make sure to look at the daisyUI changelog: https://daisyui.com/docs/changelog/ */
@plugin "../vendor/daisyui" {
themes: false;
}
/* Warm light theme — amber/terracotta + cream */
@plugin "../vendor/daisyui-theme" {
name: "light";
default: true;
prefersdark: false;
color-scheme: "light";
--color-base-100: oklch(97% 0.008 75);
--color-base-200: oklch(94% 0.012 75);
--color-base-300: oklch(90% 0.016 75);
--color-base-content: oklch(25% 0.015 50);
--color-primary: oklch(58% 0.16 45);
--color-primary-content: oklch(98% 0.01 75);
--color-secondary: oklch(55% 0.12 30);
--color-secondary-content: oklch(98% 0.008 30);
--color-accent: oklch(62% 0.19 55);
--color-accent-content: oklch(98% 0.01 55);
--color-neutral: oklch(40% 0.02 50);
--color-neutral-content: oklch(97% 0.005 75);
--color-info: oklch(62% 0.17 245);
--color-info-content: oklch(97% 0.01 245);
--color-success: oklch(65% 0.15 155);
--color-success-content: oklch(98% 0.01 155);
--color-warning: oklch(72% 0.16 70);
--color-warning-content: oklch(25% 0.02 70);
--color-error: oklch(58% 0.22 25);
--color-error-content: oklch(97% 0.01 25);
--radius-selector: 0.375rem;
--radius-field: 0.375rem;
--radius-box: 0.75rem;
--size-selector: 0.25rem;
--size-field: 0.25rem;
--border: 1px;
--depth: 1;
--noise: 0;
}
/* Warm dark theme */
@plugin "../vendor/daisyui-theme" {
name: "dark";
default: false;
prefersdark: true;
color-scheme: "dark";
--color-base-100: oklch(24% 0.015 50);
--color-base-200: oklch(20% 0.012 50);
--color-base-300: oklch(16% 0.01 50);
--color-base-content: oklch(92% 0.015 75);
--color-primary: oklch(68% 0.16 45);
--color-primary-content: oklch(18% 0.02 45);
--color-secondary: oklch(62% 0.12 30);
--color-secondary-content: oklch(18% 0.01 30);
--color-accent: oklch(70% 0.17 55);
--color-accent-content: oklch(18% 0.01 55);
--color-neutral: oklch(32% 0.02 50);
--color-neutral-content: oklch(92% 0.008 75);
--color-info: oklch(62% 0.17 245);
--color-info-content: oklch(97% 0.01 245);
--color-success: oklch(65% 0.15 155);
--color-success-content: oklch(98% 0.01 155);
--color-warning: oklch(72% 0.16 70);
--color-warning-content: oklch(25% 0.02 70);
--color-error: oklch(58% 0.22 25);
--color-error-content: oklch(97% 0.01 25);
--radius-selector: 0.375rem;
--radius-field: 0.375rem;
--radius-box: 0.75rem;
--size-selector: 0.25rem;
--size-field: 0.25rem;
--border: 1px;
--depth: 1;
--noise: 0;
}
/* Add variants based on LiveView classes */
@custom-variant phx-click-loading (.phx-click-loading&, .phx-click-loading &);
@custom-variant phx-submit-loading (.phx-submit-loading&, .phx-submit-loading &);
@custom-variant phx-change-loading (.phx-change-loading&, .phx-change-loading &);
/* Use the data attribute for dark mode */
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));
/* Make LiveView wrapper divs transparent for layout */
[data-phx-session], [data-phx-teleported-src] { display: contents }
/* Self-hosted fonts — no external requests */
@font-face {
font-family: 'Fraunces';
font-style: normal;
font-weight: 300 900;
font-display: swap;
src: url(/fonts/fraunces.woff2) format('woff2');
}
@font-face {
font-family: 'Fraunces';
font-style: italic;
font-weight: 300 900;
font-display: swap;
src: url(/fonts/fraunces-italic.woff2) format('woff2');
}
@font-face {
font-family: 'Source Sans 3';
font-style: normal;
font-weight: 300 900;
font-display: swap;
src: url(/fonts/sourcesans3.woff2) format('woff2');
}
@font-face {
font-family: 'Source Sans 3';
font-style: italic;
font-weight: 300 900;
font-display: swap;
src: url(/fonts/sourcesans3-italic.woff2) format('woff2');
}
/* Typography */
.font-display { font-family: 'Fraunces', serif; }
body { font-family: 'Source Sans 3', sans-serif; }
/* Blogex component styling */
.blogex-post-index { display: flex; flex-direction: column; gap: 1.5rem; }
.blogex-post-preview header h2 {
font-family: 'Fraunces', serif;
font-size: 1.25rem;
font-weight: 600;
}
.blogex-post-preview header h2 a {
color: oklch(var(--color-base-content));
text-decoration: none;
}
.blogex-post-preview header h2 a:hover {
color: oklch(var(--color-primary));
}
.blogex-post-description {
margin-top: 0.25rem;
opacity: 0.7;
}
.blogex-post-meta {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
opacity: 0.6;
margin-top: 0.25rem;
}
.blogex-tag {
display: inline-block;
padding: 0.125rem 0.5rem;
border-radius: 9999px;
font-size: 0.75rem;
background: oklch(var(--color-base-200));
}
.blogex-post-header h1 {
font-family: 'Fraunces', serif;
font-size: 2rem;
font-weight: 700;
line-height: 1.2;
}
.blogex-post-body {
margin-top: 2rem;
line-height: 1.75;
}
.blogex-post-body h1 { font-family: 'Fraunces', serif; font-size: 1.75rem; font-weight: 700; margin-top: 2.5rem; margin-bottom: 0.5rem; }
.blogex-post-body h2 { font-family: 'Fraunces', serif; font-size: 1.5rem; font-weight: 600; margin-top: 2rem; margin-bottom: 0.5rem; }
.blogex-post-body h3 { font-family: 'Fraunces', serif; font-size: 1.25rem; font-weight: 600; margin-top: 1.5rem; margin-bottom: 0.5rem; }
.blogex-post-body p { margin-top: 1rem; }
.blogex-post-body ul { list-style: disc; padding-left: 1.5rem; margin-top: 0.5rem; }
.blogex-post-body ol { list-style: decimal; padding-left: 1.5rem; margin-top: 0.5rem; }
.blogex-post-body pre { background: oklch(var(--color-base-200)); padding: 1rem; border-radius: 0.5rem; overflow-x: auto; margin-top: 1rem; }
.blogex-post-body code { font-size: 0.875em; }
.blogex-post-body a { color: oklch(var(--color-primary)); text-decoration: underline; }
.blogex-post-body blockquote { border-left: 3px solid oklch(var(--color-primary)); padding-left: 1rem; margin-top: 1rem; margin-bottom: 1rem; font-style: italic; opacity: 0.85; }
.blogex-tag-list {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.blogex-tag-link {
display: inline-block;
padding: 0.25rem 0.75rem;
border-radius: 9999px;
font-size: 0.875rem;
background: oklch(var(--color-base-200));
text-decoration: none;
}
.blogex-tag-link:hover, .blogex-tag-active {
background: oklch(var(--color-primary));
color: oklch(var(--color-primary-content));
}
.blogex-pagination {
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
padding-top: 1rem;
font-size: 0.875rem;
}
.blogex-pagination a {
color: oklch(var(--color-primary));
text-decoration: none;
}
.blogex-pagination a:hover {
text-decoration: underline;
}
/* Source Viewer — line-numbered code display */
.source-viewer {
--sv-bg: oklch(20.15% 0.012 254.09);
--sv-text: oklch(90% 0.01 240);
--sv-line-num: oklch(55% 0.02 240);
--sv-highlight: oklch(30% 0.02 240);
}
[data-theme="dark"] .source-viewer {
--sv-bg: oklch(16% 0.01 254);
--sv-text: oklch(88% 0.01 240);
--sv-line-num: oklch(50% 0.02 240);
--sv-highlight: oklch(25% 0.02 240);
}
.sv-lines {
display: flex;
flex-direction: column;
}
.sv-line {
display: flex;
align-items: baseline;
min-height: 1.25rem;
padding: 0 0.25rem;
transition: background-color 0.2s ease;
}
.sv-line-highlighted {
background-color: var(--sv-highlight);
}
.sv-line-number {
display: inline-block;
width: 2.5rem;
text-align: right;
padding-right: 0.75rem;
color: var(--sv-line-num);
user-select: none;
opacity: 0.6;
flex-shrink: 0;
}
.sv-line-content {
white-space: pre;
flex: 1;
min-width: 0;
}
/* highlight.js — Atom One Dark (dark theme) */
.source-viewer code.hljs {
color: #abb2bf;
background: transparent;
padding: 0;
}
.source-viewer code.hljs .hljs-comment,
.source-viewer code.hljs .hljs-quote {
color: #5c6370;
font-style: italic;
}
.source-viewer code.hljs .hljs-doctag,
.source-viewer code.hljs .hljs-keyword,
.source-viewer code.hljs .hljs-formula {
color: #c678dd;
}
.source-viewer code.hljs .hljs-section,
.source-viewer code.hljs .hljs-name,
.source-viewer code.hljs .hljs-selector-tag,
.source-viewer code.hljs .hljs-deletion,
.source-viewer code.hljs .hljs-subst {
color: #e06c75;
}
.source-viewer code.hljs .hljs-literal {
color: #56b6c2;
}
.source-viewer code.hljs .hljs-string,
.source-viewer code.hljs .hljs-regexp,
.source-viewer code.hljs .hljs-addition,
.source-viewer code.hljs .hljs-attribute,
.source-viewer code.hljs .hljs-meta .hljs-string {
color: #98c379;
}
.source-viewer code.hljs .hljs-attr,
.source-viewer code.hljs .hljs-variable,
.source-viewer code.hljs .hljs-template-variable,
.source-viewer code.hljs .hljs-type,
.source-viewer code.hljs .hljs-selector-class,
.source-viewer code.hljs .hljs-selector-attr,
.source-viewer code.hljs .hljs-selector-pseudo,
.source-viewer code.hljs .hljs-number {
color: #d19a66;
}
.source-viewer code.hljs .hljs-symbol,
.source-viewer code.hljs .hljs-link,
.source-viewer code.hljs .hljs-meta,
.source-viewer code.hljs .hljs-selector-id,
.source-viewer code.hljs .hljs-title {
color: #61aeee;
}
.source-viewer code.hljs .hljs-built_in,
.source-viewer code.hljs .hljs-title.class_,
.source-viewer code.hljs .hljs-class .hljs-title {
color: #e6c07b;
}
.source-viewer code.hljs .hljs-emphasis {
font-style: italic;
}
.source-viewer code.hljs .hljs-strong {
font-weight: bold;
}
/* highlight.js — Atom One Light (light theme) */
[data-theme="light"] .source-viewer code.hljs {
color: #383a42;
background: transparent;
}
[data-theme="light"] .source-viewer code.hljs .hljs-comment,
[data-theme="light"] .source-viewer code.hljs .hljs-quote {
color: #a0a1a7;
font-style: italic;
}
[data-theme="light"] .source-viewer code.hljs .hljs-doctag,
[data-theme="light"] .source-viewer code.hljs .hljs-keyword,
[data-theme="light"] .source-viewer code.hljs .hljs-formula {
color: #a626a4;
}
[data-theme="light"] .source-viewer code.hljs .hljs-section,
[data-theme="light"] .source-viewer code.hljs .hljs-name,
[data-theme="light"] .source-viewer code.hljs .hljs-selector-tag,
[data-theme="light"] .source-viewer code.hljs .hljs-deletion,
[data-theme="light"] .source-viewer code.hljs .hljs-subst {
color: #e45649;
}
[data-theme="light"] .source-viewer code.hljs .hljs-literal {
color: #0184bc;
}
[data-theme="light"] .source-viewer code.hljs .hljs-string,
[data-theme="light"] .source-viewer code.hljs .hljs-regexp,
[data-theme="light"] .source-viewer code.hljs .hljs-addition,
[data-theme="light"] .source-viewer code.hljs .hljs-attribute,
[data-theme="light"] .source-viewer code.hljs .hljs-meta .hljs-string {
color: #50a14f;
}
[data-theme="light"] .source-viewer code.hljs .hljs-attr,
[data-theme="light"] .source-viewer code.hljs .hljs-variable,
[data-theme="light"] .source-viewer code.hljs .hljs-template-variable,
[data-theme="light"] .source-viewer code.hljs .hljs-type,
[data-theme="light"] .source-viewer code.hljs .hljs-selector-class,
[data-theme="light"] .source-viewer code.hljs .hljs-selector-attr,
[data-theme="light"] .source-viewer code.hljs .hljs-selector-pseudo,
[data-theme="light"] .source-viewer code.hljs .hljs-number {
color: #986801;
}
[data-theme="light"] .source-viewer code.hljs .hljs-symbol,
[data-theme="light"] .source-viewer code.hljs .hljs-link,
[data-theme="light"] .source-viewer code.hljs .hljs-meta,
[data-theme="light"] .source-viewer code.hljs .hljs-selector-id,
[data-theme="light"] .source-viewer code.hljs .hljs-title {
color: #4078f2;
}
[data-theme="light"] .source-viewer code.hljs .hljs-built_in,
[data-theme="light"] .source-viewer code.hljs .hljs-title.class_,
[data-theme="light"] .source-viewer code.hljs .hljs-class .hljs-title {
color: #c18401;
}
[data-theme="light"] .source-viewer code.hljs .hljs-emphasis {
font-style: italic;
}
[data-theme="light"] .source-viewer code.hljs .hljs-strong {
font-weight: bold;
}