1
0
mirror of https://github.com/jaandrle/deka-dom-el synced 2025-04-01 19:55:53 +02:00

🔤 Docs UI/UX

This commit is contained in:
Jan Andrle 2025-03-03 19:06:23 +01:00
parent 7ec50e1660
commit bdb20ec298
8 changed files with 1096 additions and 176 deletions

View File

@ -1,28 +1,174 @@
import { registerClientFile, styles } from "../ssr.js";
const host= "."+code.name;
styles.css`
${host}{
--shiki-color-text: #e9eded;
--shiki-color-background: #212121;
/* Code block styling */
${host} {
/* Theme for dark mode - matches Flems/CodeMirror dark theme */
--shiki-color-text: #f8f8f2;
--shiki-color-background: var(--code-bg);
--shiki-token-constant: #82b1ff;
--shiki-token-string: #c3e88d;
--shiki-token-comment: #546e7a;
--shiki-token-keyword: #c792ea;
--shiki-token-parameter: #AA0000;
--shiki-token-parameter: #fd971f;
--shiki-token-function: #80cbae;
--shiki-token-string-expression: #c3e88d;
--shiki-token-punctuation: var(--code);
--shiki-token-link: #EE0000;
--shiki-token-punctuation: #89ddff;
--shiki-token-link: #82aaff;
--shiki-token-variable: #f8f8f2;
--shiki-token-number: #f78c6c;
--shiki-token-boolean: #82b1ff;
--shiki-token-tag: #f07178;
--shiki-token-attribute: #ffcb6b;
--shiki-token-property: #82b1ff;
--shiki-token-operator: #89ddff;
--shiki-token-regex: #c3e88d;
--shiki-token-class: #ffcb6b;
/* Basic styling */
white-space: pre;
tab-size: 2; /* TODO: allow custom tab size?! */
overflow: scroll;
tab-size: 2;
overflow: auto;
border-radius: var(--border-radius);
font-family: var(--font-mono);
font-size: 0.9rem;
line-height: 1.5;
position: relative;
}
${host}[data-js=todo]{
/* Light mode overrides to match GitHub-like theme */
@media (prefers-color-scheme: light) {
${host} {
--shiki-color-text: #24292e;
--shiki-color-background: var(--code-bg);
--shiki-token-constant: #005cc5;
--shiki-token-string: #22863a;
--shiki-token-comment: #6a737d;
--shiki-token-keyword: #d73a49;
--shiki-token-parameter: #e36209;
--shiki-token-function: #6f42c1;
--shiki-token-string-expression: #22863a;
--shiki-token-punctuation: #24292e;
--shiki-token-link: #0366d6;
--shiki-token-variable: #24292e;
--shiki-token-number: #005cc5;
--shiki-token-boolean: #005cc5;
--shiki-token-tag: #22863a;
--shiki-token-attribute: #005cc5;
--shiki-token-property: #005cc5;
--shiki-token-operator: #d73a49;
--shiki-token-regex: #032f62;
--shiki-token-class: #6f42c1;
}
}
/* Support for theme toggles */
html[data-theme="light"] ${host} {
--shiki-color-text: #24292e;
--shiki-color-background: var(--code-bg);
--shiki-token-constant: #005cc5;
--shiki-token-string: #22863a;
--shiki-token-comment: #6a737d;
--shiki-token-keyword: #d73a49;
--shiki-token-parameter: #e36209;
--shiki-token-function: #6f42c1;
--shiki-token-string-expression: #22863a;
--shiki-token-punctuation: #24292e;
--shiki-token-link: #0366d6;
--shiki-token-variable: #24292e;
--shiki-token-number: #005cc5;
--shiki-token-boolean: #005cc5;
--shiki-token-tag: #22863a;
--shiki-token-attribute: #005cc5;
--shiki-token-property: #005cc5;
--shiki-token-operator: #d73a49;
--shiki-token-regex: #032f62;
--shiki-token-class: #6f42c1;
}
html[data-theme="dark"] ${host} {
--shiki-color-text: #f8f8f2;
--shiki-color-background: var(--code-bg);
--shiki-token-constant: #82b1ff;
--shiki-token-string: #c3e88d;
--shiki-token-comment: #546e7a;
--shiki-token-keyword: #c792ea;
--shiki-token-parameter: #fd971f;
--shiki-token-function: #80cbae;
--shiki-token-string-expression: #c3e88d;
--shiki-token-punctuation: #89ddff;
--shiki-token-link: #82aaff;
--shiki-token-variable: #f8f8f2;
--shiki-token-number: #f78c6c;
--shiki-token-boolean: #82b1ff;
--shiki-token-tag: #f07178;
--shiki-token-attribute: #ffcb6b;
--shiki-token-property: #82b1ff;
--shiki-token-operator: #89ddff;
--shiki-token-regex: #c3e88d;
--shiki-token-class: #ffcb6b;
}
/* Code block with syntax highlighting waiting for JS */
${host}[data-js=todo] {
border: 1px solid var(--border);
border-radius: var(--standard-border-radius);
margin-bottom: 1rem;
margin-top: 18.4px; /* to fix shift when → dataJS=done */
padding: 1rem 1.4rem;
border-radius: var(--border-radius);
margin-bottom: 1.5rem;
margin-top: 1rem;
padding: 1rem;
background-color: var(--code-bg);
position: relative;
}
/* Add a subtle loading indicator */
${host}[data-js=todo]::before {
content: "Loading syntax highlighting...";
position: absolute;
top: 0.5rem;
right: 0.5rem;
font-size: 0.75rem;
color: var(--text-light);
background-color: var(--bg);
padding: 0.25rem 0.5rem;
border-radius: var(--border-radius);
opacity: 0.7;
}
/* All code blocks should have consistent font and sizing */
${host} code {
font-family: var(--font-mono);
font-size: 0.9rem;
line-height: 1.5;
}
/* Ensure line numbers (if added) are styled appropriately */
${host} .line-number {
user-select: none;
opacity: 0.5;
margin-right: 1rem;
min-width: 1.5rem;
display: inline-block;
text-align: right;
}
/* If there's a copy button, style it */
${host} .copy-button {
position: absolute;
top: 0.5rem;
right: 0.5rem;
background-color: var(--bg);
color: var(--text);
border: 1px solid var(--border);
border-radius: var(--border-radius);
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
cursor: pointer;
opacity: 0;
transition: opacity 0.2s ease;
}
${host}:hover .copy-button {
opacity: 1;
}
`;
import { el } from "deka-dom-el";
@ -52,12 +198,46 @@ let is_registered= {};
function registerClientPart(page_id){
if(is_registered[page_id]) return;
// Add Shiki with a more reliable loading method
document.head.append(
el("script", { src: "https://cdn.jsdelivr.net/npm/shiki@0.9", defer: true }),
// Use a newer version of Shiki with better performance
el("script", { src: "https://cdn.jsdelivr.net/npm/shiki@0.14.3/dist/index.unpkg.iife.js", defer: true }),
// Make sure we can match Flems styling in dark/light mode
el("style", `
/* Ensure CodeMirror and Shiki use the same font */
.CodeMirror *, .shiki * {
font-family: var(--font-mono) !important;
}
/* Style Shiki's output to match our theme */
.shiki {
background-color: var(--shiki-color-background) !important;
color: var(--shiki-color-text) !important;
padding: 1rem;
border-radius: var(--border-radius);
tab-size: 2;
}
/* Ensure Shiki code tokens use our CSS variables */
.shiki .keyword { color: var(--shiki-token-keyword) !important; }
.shiki .constant { color: var(--shiki-token-constant) !important; }
.shiki .string { color: var(--shiki-token-string) !important; }
.shiki .comment { color: var(--shiki-token-comment) !important; }
.shiki .function { color: var(--shiki-token-function) !important; }
.shiki .operator, .shiki .punctuation { color: var(--shiki-token-punctuation) !important; }
.shiki .parameter { color: var(--shiki-token-parameter) !important; }
.shiki .variable { color: var(--shiki-token-variable) !important; }
.shiki .property { color: var(--shiki-token-property) !important; }
`),
);
// Register our highlighting script to run after Shiki loads
const scriptElement = el("script", { type: "module" });
registerClientFile(
new URL("./code.js.js", import.meta.url),
el("script", { type: "module" })
scriptElement
);
is_registered[page_id]= true;
}

View File

@ -1,12 +1,61 @@
const highlighter= await globalThis.shiki.getHighlighter({
theme: "css-variables",
langs: ["js", "ts", "css", "html", "shell"],
});
const codeBlocks= document.querySelectorAll('code[class*="language-"]');
try {
// Initialize Shiki with our custom theme
const highlighter = await globalThis.shiki.getHighlighter({
theme: "css-variables",
langs: ["javascript", "typescript", "css", "html", "shell"],
});
codeBlocks.forEach((block)=> {
const lang= block.className.replace("language-", "");
block.parentElement.dataset.js= "done";
const html= highlighter.codeToHtml(block.textContent, { lang });
block.innerHTML= html;
});
// Find all code blocks that need highlighting
const codeBlocks = document.querySelectorAll('div[data-js="todo"] code[class*="language-"]');
// Process each code block
codeBlocks.forEach((block) => {
try {
// Get the language from the class
const langClass = block.className.match(/language-(\w+)/);
// Map the language to Shiki format
let lang = langClass ? langClass[1] : 'javascript';
if (lang === 'js') lang = 'javascript';
if (lang === 'ts') lang = 'typescript';
// Mark the container as processed
block.parentElement.dataset.js = "done";
// Highlight the code
const code = block.textContent;
const html = highlighter.codeToHtml(code, { lang });
// Insert the highlighted HTML
block.innerHTML = html;
// Add copy button functionality
const copyBtn = document.createElement('button');
copyBtn.className = 'copy-button';
copyBtn.textContent = 'Copy';
copyBtn.setAttribute('aria-label', 'Copy code to clipboard');
copyBtn.addEventListener('click', () => {
navigator.clipboard.writeText(code).then(() => {
copyBtn.textContent = 'Copied!';
setTimeout(() => {
copyBtn.textContent = 'Copy';
}, 2000);
});
});
// Add the copy button to the code block container
block.parentElement.appendChild(copyBtn);
} catch (err) {
console.error('Error highlighting code block:', err);
// Make sure we don't leave the block in a pending state
block.parentElement.dataset.js = "error";
}
});
} catch (err) {
console.error('Failed to initialize Shiki:', err);
// Fallback: at least mark blocks as processed so they don't show loading indicator
document.querySelectorAll('div[data-js="todo"]').forEach(block => {
block.dataset.js = "error";
});
}

View File

@ -1,16 +1,103 @@
import { styles } from "../ssr.js";
const host= "."+example.name;
styles.css`
${host}{
${host} {
grid-column: full-main;
width: 100%;
max-width: calc(9/5 * var(--body-max-width));
height: calc(3/5 * var(--body-max-width));
margin-inline: auto;
margin: 2rem auto;
border-radius: var(--border-radius);
box-shadow: var(--shadow);
border: 1px solid var(--border);
}
${host} .runtime {
background-color: whitesmoke;
}
/* CodeMirror styling to match our theme */
.CodeMirror {
height: 100% !important;
font-family: var(--font-mono) !important;
font-size: 0.95rem !important;
line-height: 1.5 !important;
}
/* Dark mode styles for CodeMirror */
.CodeMirror, .CodeMirror-gutters {
background: #212121 !important;
border: 1px solid white;
background: var(--code-bg) !important;
border: 1px solid var(--border) !important;
color: var(--text) !important;
}
/* Light mode adjustments for CodeMirror - using CSS variables */
@media (prefers-color-scheme: light) {
/* Core syntax elements */
.cm-s-material .cm-keyword { color: var(--shiki-token-keyword, #d73a49) !important; }
.cm-s-material .cm-atom { color: var(--shiki-token-constant, #005cc5) !important; }
.cm-s-material .cm-number { color: var(--shiki-token-number, #005cc5) !important; }
.cm-s-material .cm-def { color: var(--shiki-token-function, #6f42c1) !important; }
.cm-s-material .cm-variable { color: var(--shiki-token-variable, #24292e) !important; }
.cm-s-material .cm-variable-2 { color: var(--shiki-token-variable, #24292e) !important; }
.cm-s-material .cm-variable-3 { color: var(--shiki-token-variable, #24292e) !important; }
.cm-s-material .cm-property { color: var(--shiki-token-property, #005cc5) !important; }
.cm-s-material .cm-operator { color: var(--shiki-token-operator, #d73a49) !important; }
.cm-s-material .cm-comment { color: var(--shiki-token-comment, #6a737d) !important; }
.cm-s-material .cm-string { color: var(--shiki-token-string, #22863a) !important; }
.cm-s-material .cm-string-2 { color: var(--shiki-token-string, #22863a) !important; }
.cm-s-material .cm-tag { color: var(--shiki-token-tag, #22863a) !important; }
.cm-s-material .cm-attribute { color: var(--shiki-token-attribute, #005cc5) !important; }
.cm-s-material .cm-bracket { color: var(--shiki-token-punctuation, #24292e) !important; }
.cm-s-material .cm-punctuation { color: var(--shiki-token-punctuation, #24292e) !important; }
.cm-s-material .cm-link { color: var(--shiki-token-link, #0366d6) !important; }
.cm-s-material .cm-error { color: #f44336 !important; }
}
/* Handle theme toggle */
html[data-theme="light"] .CodeMirror {
background: #f5f7fa !important;
}
html[data-theme="light"] .CodeMirror-gutters {
background: #f5f7fa !important;
border-right: 1px solid #e5e7eb !important;
}
/* Also apply the same styles to CodeMirror with data-theme */
html[data-theme="light"] .cm-s-material .cm-keyword { color: var(--shiki-token-keyword, #d73a49) !important; }
html[data-theme="light"] .cm-s-material .cm-atom { color: var(--shiki-token-constant, #005cc5) !important; }
html[data-theme="light"] .cm-s-material .cm-number { color: var(--shiki-token-number, #005cc5) !important; }
html[data-theme="light"] .cm-s-material .cm-def { color: var(--shiki-token-function, #6f42c1) !important; }
html[data-theme="light"] .cm-s-material .cm-variable { color: var(--shiki-token-variable, #24292e) !important; }
html[data-theme="light"] .cm-s-material .cm-variable-2 { color: var(--shiki-token-variable, #24292e) !important; }
html[data-theme="light"] .cm-s-material .cm-variable-3 { color: var(--shiki-token-variable, #24292e) !important; }
html[data-theme="light"] .cm-s-material .cm-property { color: var(--shiki-token-property, #005cc5) !important; }
html[data-theme="light"] .cm-s-material .cm-operator { color: var(--shiki-token-operator, #d73a49) !important; }
html[data-theme="light"] .cm-s-material .cm-comment { color: var(--shiki-token-comment, #6a737d) !important; }
html[data-theme="light"] .cm-s-material .cm-string { color: var(--shiki-token-string, #22863a) !important; }
html[data-theme="light"] .cm-s-material .cm-string-2 { color: var(--shiki-token-string, #22863a) !important; }
html[data-theme="light"] .cm-s-material .cm-tag { color: var(--shiki-token-tag, #22863a) !important; }
html[data-theme="light"] .cm-s-material .cm-attribute { color: var(--shiki-token-attribute, #005cc5) !important; }
html[data-theme="light"] .cm-s-material .cm-bracket { color: var(--shiki-token-punctuation, #24292e) !important; }
html[data-theme="light"] .cm-s-material .cm-punctuation { color: var(--shiki-token-punctuation, #24292e) !important; }
html[data-theme="light"] .cm-s-material .cm-link { color: var(--shiki-token-link, #0366d6) !important; }
html[data-theme="light"] .cm-s-material .cm-error { color: #f44336 !important; }
/* Mobile adjustments */
@media (max-width: 767px) {
${host} {
height: 50vh;
max-width: 100%;
}
${host} main {
flex-grow: 1;
display: flex;
flex-direction: column;
}
${host} main > * {
width: 100%;
max-width: 100% !important;
}
}
`;

View File

@ -1,18 +1,67 @@
import { pages, styles } from "../ssr.js";
const host= "."+prevNext.name;
styles.css`
${host}{
display: grid;
grid-template-columns: 1fr 2fr 1fr;
margin-top: 1rem;
/* Previous/Next navigation */
${host} {
display: flex;
justify-content: space-between;
margin-top: 3rem;
padding-top: 1.5rem;
border-top: 1px solid var(--border);
gap: 1rem;
}
${host} [rel=prev]{
grid-column: 1;
${host} a {
display: flex;
align-items: center;
padding: 0.75rem 1.25rem;
border-radius: var(--border-radius);
background-color: var(--primary-dark); /* Darker background for better contrast */
color: white;
font-weight: 600; /* Bolder text for better readability */
text-decoration: none;
transition: background-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
max-width: 45%;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15); /* Subtle shadow for better visibility */
}
${host} [rel=next]{
grid-column: 3;
text-align: right;
${host} a:hover {
background-color: var(--primary);
transform: translateY(-2px);
text-decoration: none;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* Enhanced shadow on hover */
}
${host} [rel=prev] {
margin-right: auto;
}
${host} [rel=next] {
margin-left: auto;
}
${host} [rel=prev]::before {
content: "←";
margin-right: 0.75rem;
font-size: 1.2em;
}
${host} [rel=next]::after {
content: "→";
margin-left: 0.75rem;
font-size: 1.2em;
}
/* If there's no previous/next, ensure the spacing still works */
${host} a:only-child {
margin-left: auto;
}
@media (max-width: 640px) {
${host} a {
padding: 0.5rem 0.75rem;
font-size: 0.9rem;
}
}
`;
import { el } from "../../index.js";
@ -22,21 +71,28 @@ import { el } from "../../index.js";
* @param {string} [attrs.id]
* */
export function h3({ textContent, id }){
if(!id) id= "h-"+textContent.toLowerCase().replaceAll(/\s/g, "-").replaceAll(/[^a-z-]/g, "");
return el("h3", { id }).append(
el("a", { textContent: "#", href: "#"+id, tabIndex: -1 }),
" ", textContent
);
if(!id) id= "h-"+textContent.toLowerCase().replaceAll(/\s/g, "-").replaceAll(/[^a-z-]/g, "");
return el("h3", { id, className: "section-heading" }).append(
el("a", {
className: "heading-anchor",
href: "#"+id,
textContent: "#",
title: `Link to this section: ${textContent}`,
"aria-label": `Link to section ${textContent}`
}),
" ",
textContent,
);
}
/**
* @param {import("../types.d.ts").Info} page
* */
export function prevNext(page){
const page_index= pages.indexOf(page);
return el("div", { className: prevNext.name }).append(
el(pageLink, { rel: "prev", page: pages[page_index-1] }),
el(pageLink, { rel: "next", page: pages[page_index+1] })
);
const page_index= pages.indexOf(page);
return el("div", { className: prevNext.name }).append(
el(pageLink, { rel: "prev", page: pages[page_index-1] }),
el(pageLink, { rel: "next", page: pages[page_index+1] })
);
}
/**
* @param {Object} attrs
@ -44,11 +100,22 @@ export function prevNext(page){
* @param {import("../types.d.ts").Info} [attrs.page]
* */
function pageLink({ rel, page }){
if(!page) return el();
let { href, title, description }= page;
return el("a", { rel, href, title: description }).append(
rel==="next" ?"(next) " : "",
title,
rel==="prev" ? " (previous)" : "",
);
if(!page) return el();
let { href, title, description }= page;
// Find the page index to show numbering
const pageIndex = pages.findIndex(p => p === page);
const pageNumber = pageIndex + 1;
const linkTitle = rel === "prev"
? `Previous: ${pageNumber}. ${title}`
: `Next: ${pageNumber}. ${title}`;
return el("a", {
rel,
href,
title: description || linkTitle
}).append(
title
);
}

View File

@ -1,124 +1,438 @@
import { styles } from "./ssr.js";
styles.css`
@import url(https://cdn.simplecss.org/simple.min.css);
:root{
--body-max-width: 45rem;
--marked: #fb3779;
--code: #0d47a1;
--accent: #d81b60;
/* Modern custom styling with reddish color scheme and high contrast */
:root {
/* Color variables - reddish theme with increased contrast */
--primary: #b71c1c; /* Darker red for better contrast on white */
--primary-light: #f05545; /* Lighter but still contrasting red */
--primary-dark: #7f0000; /* Very dark red for maximum contrast */
--primary-rgb: 183, 28, 28; /* RGB values for rgba operations */
--secondary: #700037; /* Darker purple for better contrast */
--secondary-light: #ae1357; /* More saturated magenta for visibility */
--secondary-dark: #4a0027; /* Very dark purple */
--secondary-rgb: 112, 0, 55; /* RGB values for rgba operations */
/* Typography */
--font-main: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
--font-mono: 'Fira Code', 'JetBrains Mono', 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
/* Layout */
--body-max-width: 50rem;
--sidebar-width: 16rem;
--header-height: 4rem;
--border-radius: 0.375rem;
/* Colors light mode - enhanced contrast */
--bg: #ffffff;
--bg-sidebar: #fff5f5;
--text: #1a1313; /* Near-black text for high contrast */
--text-light: #555050; /* Darker gray text that meets contrast requirements */
--code-bg: #f9f2f2;
--code-text: #9a0000; /* Darker red for code text */
--border: #d8c0c0;
--selection: rgba(183, 28, 28, 0.15);
--marked: #b71c1c;
--accent: var(--secondary);
--shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
/* Contrast improvements for components */
--link-color: #9a0000;
--link-hover: #7f0000;
--button-text: #ffffff;
}
@media (prefers-color-scheme:dark) {
@media (prefers-color-scheme: dark) {
:root {
--accent: #f06292;
--code: #62c1f0;
--bg: #121212; /* Pure dark background */
--bg-sidebar: #1a1212; /* Slightly lighter sidebar */
--text: #ffffff; /* Pure white text for highest contrast */
--text-light: #cccccc; /* Light gray that still meets contrast requirements */
--code-bg: #2c2020;
--code-text: #ff9e80; /* Slightly more orange for better visibility */
--border: #4d3939;
--selection: rgba(255, 99, 71, 0.25);
--primary: #b74141; /* Brighter red for better visibility on dark */
--primary-light: #ff867f; /* Even brighter highlight red */
--primary-dark: #c62828; /* Darker red that still has good contrast */
--secondary: #f02b47; /* Brighter magenta for better visibility */
--secondary-light: #ff6090; /* Bright pink with good contrast */
--secondary-dark: #b0003a; /* Darker but still visible pink */
--accent: var(--secondary);
/* Contrast improvements for dark mode components */
--link-color: #ff5252;
--link-hover: #ff867f;
--button-text: #ffffff;
/* Navigation current page - darker for better contrast */
--nav-current-bg: #aa2222;
--nav-current-text: #ffffff;
/* RGB values for rgba operations in dark mode */
--primary-rgb: 255, 82, 82;
--secondary-rgb: 233, 30, 99;
}
}
body {
grid-template-columns: 100%;
grid-template-areas: "header" "sidebar" "content";
}
@media (min-width:768px) {
body{
grid-template-rows: auto auto;
grid-template-columns: calc(10 * var(--body-max-width) / 27) auto;
grid-template-areas:
"header header"
"sidebar content"
}
}
body > *{
grid-column: unset;
}
body > header{
grid-area: header;
padding: 0;
}
body > nav{
grid-area: sidebar;
background-color: var(--accent-bg);
display: flex;
flex-flow: column nowrap;
}
body > nav {
font-size: 1rem;
line-height: 2;
padding: 1rem 0 0 0;
}
body > nav ol,
body > nav ul {
align-content: space-around;
align-items: center;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
list-style-type: none;
/* Base styling */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body > nav ol li,
body > nav ul li {
display:inline-block
html {
scroll-behavior: smooth;
}
body > nav a,
body > nav a:visited {
margin: 0 .5rem 1rem .5rem;
border: 1px solid currentColor;
border-radius: var(--standard-border-radius);
color: var(--text-light);
display: inline-block;
padding: .1rem 1rem;
/* Accessibility improvements */
:focus {
outline: 3px solid rgba(63, 81, 181, 0.5);
outline-offset: 2px;
}
:focus:not(:focus-visible) {
outline: none;
}
:focus-visible {
outline: 3px solid rgba(63, 81, 181, 0.5);
outline-offset: 2px;
}
/* Ensure reduced motion preferences are respected */
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* Skip link for better keyboard navigation */
.skip-link {
position: absolute;
top: 0;
left: 0;
transform: translateX(-100%);
z-index: 9999;
background-color: var(--primary);
color: white;
padding: 0.5rem 1rem;
text-decoration: none;
cursor: pointer;
transition: all .15s;
transition: transform 0.3s ease-in-out;
}
body > nav a.current,
body > nav a[aria-current=page] {
.skip-link:focus {
transform: translateX(0);
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
body {
font-family: var(--font-main);
background-color: var(--bg);
color: var(--text);
}
body > nav a:hover{
background-color: var(--bg);
color: var(--accent);
}
@media only screen and (max-width:720px) {
body > nav{
flex-flow: row wrap;
padding-block: .5rem;
}
body > nav a {
border:none;
text-decoration:underline;
margin-block: .1rem;
padding-block:.1rem;
line-height: 1rem;
font-size: .9rem;
}
}
main{
grid-area: content;
line-height: 1.6;
font-size: 1rem;
display: grid;
grid-template-columns:
[full-main-start] 1fr
[main-start] min(var(--body-max-width), 90%) [main-end]
1fr [full-main-end];
grid-template-columns: 100%;
grid-template-areas:
"header"
"sidebar"
"content";
min-height: 100vh;
}
main > *, main slot > *{
grid-column: main;
::selection {
background-color: var(--selection);
}
/* Typography */
h1, h2, h3, h4, h5, h6 {
margin-bottom: 1rem;
margin-top: 2rem;
font-weight: 700; /* Bolder for better contrast */
line-height: 1.25;
color: var(--text);
}
h1 {
font-size: 2.25rem; /* Slightly larger for better hierarchy */
margin-top: 0;
color: var(--primary-dark); /* Distinctive color for main headings */
}
h2 {
font-size: 1.5rem;
border-bottom: 2px solid var(--border); /* Thicker border for better visibility */
padding-bottom: 0.5rem;
color: var(--primary); /* Color for better hierarchy */
}
h3 {
font-size: 1.25rem;
color: var(--secondary); /* Different color for tertiary headings */
}
p {
margin-bottom: 1.5rem;
}
a {
color: var(--link-color, var(--primary));
text-decoration: none;
transition: color 0.2s ease;
font-weight: 500; /* Slightly bolder for better contrast */
text-decoration: underline;
text-underline-offset: 3px;
transition: color 0.2s ease, text-underline-offset 0.2s ease;
}
/* Ensure visited links maintain high contrast */
a:visited {
color: var(--secondary, #700037);
}
a:hover {
color: var(--link-hover, var(--primary-light));
text-underline-offset: 5px;
}
code, pre {
font-family: var(--font-mono);
font-size: 0.9em;
border-radius: var(--border-radius);
}
code {
background-color: var(--code-bg);
color: var(--code-text);
padding: 0.2em 0.4em;
}
pre {
background-color: var(--code-bg);
padding: 1rem;
overflow-x: auto;
margin-bottom: 1.5rem;
border: 1px solid var(--border);
}
pre code {
background-color: transparent;
padding: 0;
}
/* Layout */
@media (min-width: 768px) {
body {
grid-template-rows: var(--header-height) 1fr;
grid-template-columns: var(--sidebar-width) 1fr;
grid-template-areas:
"header header"
"sidebar content";
}
}
/* Main content */
body > main {
grid-area: content;
padding: 2rem;
max-width: 100%;
overflow-x: hidden;
}
body > main > *, body > main slot > * {
max-width: calc(var(--body-max-width) - var(--sidebar-width));
margin-left: auto;
margin-right: auto;
}
/* Page title with ID anchor for skip link */
body > main .page-title {
margin-top: 0;
border-bottom: 1px solid var(--border);
padding-bottom: 0.75rem;
margin-bottom: 1.5rem;
color: var(--primary);
position: relative;
}
/* Section headings with better visual hierarchy */
body > main h2, body > main h3 {
scroll-margin-top: calc(var(--header-height) + 1rem);
position: relative;
}
body > main h3 {
border-left: 3px solid var(--primary);
padding-left: 0.75rem;
margin-left: -0.75rem;
}
/* Make clickable heading links for better navigation */
body > main h2 .heading-anchor,
body > main h3 .heading-anchor {
position: absolute;
color: var(--text-light);
left: -1rem;
text-decoration: none;
font-weight: normal;
opacity: 0;
transition: opacity 0.2s;
}
body > main h2:hover .heading-anchor,
body > main h3:hover .heading-anchor {
opacity: 0.8;
}
@media (max-width: 767px) {
body > main {
padding: 1.5rem 1rem;
}
body > main > *, body > main slot > * {
max-width: 100%;
}
}
/* Example boxes */
.example {
border: 1px solid var(--border);
border-radius: var(--border-radius);
margin: 2rem 0;
overflow: hidden;
box-shadow: var(--shadow-sm);
transition: box-shadow 0.2s;
}
.example:hover {
box-shadow: var(--shadow);
}
.example-header {
background-color: var(--bg-sidebar);
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--border);
font-weight: 600;
display: flex;
justify-content: space-between;
align-items: center;
}
.example-content {
padding: 1.25rem;
}
/* Icon styling */
.icon {
vertical-align: sub;
padding-right: .25rem;
display: inline-block;
width: 1em;
height: 1.3em;
margin-right: 0.2rem;
height: 1em;
margin-right: 0.5rem;
vertical-align: -0.125em;
stroke-width: 0;
stroke: currentColor;
fill: currentColor;
}
.note{
font-size: .9rem;
font-style: italic;
/* Information blocks */
.note, .tip, .warning {
padding: 1rem 1.25rem;
margin: 1.5rem 0;
border-radius: var(--border-radius);
position: relative;
font-size: 0.95rem;
line-height: 1.5;
}
.note {
background-color: rgba(63, 81, 181, 0.08);
border-left: 4px solid var(--primary);
border-radius: 0 var(--border-radius) var(--border-radius) 0;
}
.tip {
background-color: rgba(46, 204, 113, 0.08);
border-left: 4px solid #2ecc71;
border-radius: 0 var(--border-radius) var(--border-radius) 0;
}
.warning {
background-color: rgba(241, 196, 15, 0.08);
border-left: 4px solid #f1c40f;
border-radius: 0 var(--border-radius) var(--border-radius) 0;
}
.note::before, .tip::before, .warning::before {
font-weight: 600;
display: block;
margin-bottom: 0.5rem;
}
.note::before {
content: "Note";
color: var(--primary);
}
.tip::before {
content: "Tip";
color: #2ecc71;
}
.warning::before {
content: "Warning";
color: #f1c40f;
}
/* Prev/Next buttons */
.prev-next {
display: flex;
justify-content: space-between;
margin-top: 3rem;
padding-top: 1.5rem;
border-top: 1px solid var(--border);
}
.prev-next a {
display: flex;
align-items: center;
padding: 0.5rem 1rem;
border-radius: var(--border-radius);
background-color: var(--primary);
color: white;
transition: background-color 0.2s ease;
}
.prev-next a:hover {
background-color: var(--primary-dark);
text-decoration: none;
}
.prev-next a:empty {
display: none;
}
.prev-next a[rel="prev"]::before {
content: "←";
margin-right: 0.5rem;
}
.prev-next a[rel="next"]::after {
content: "→";
margin-left: 0.5rem;
}
`;

View File

@ -1,38 +1,245 @@
import { el, elNS } from "deka-dom-el";
import { pages } from "../ssr.js";
import { pages, styles } from "../ssr.js";
const host= "."+header.name;
const host_nav= "."+nav.name;
styles.css`
/* Header */
${host} {
grid-area: header;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
padding: 0.75rem 1.5rem;
background-color: var(--primary);
color: white;
box-shadow: var(--shadow);
min-height: var(--header-height);
}
${host} .header-title {
display: flex;
align-items: center;
gap: 0.75rem;
}
${host} h1 {
font-size: 1.25rem;
margin: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: white;
}
${host} .version-badge {
font-size: 0.75rem;
background-color: rgba(150, 150, 150, 0.2);
padding: 0.25rem 0.5rem;
border-radius: var(--border-radius);
}
${host} p {
display: none;
margin: 0;
}
${host} .github-link {
display: flex;
align-items: center;
gap: 0.5rem;
color: white;
font-size: 0.875rem;
padding: 0.375rem 0.75rem;
border-radius: var(--border-radius);
background-color: rgba(0, 0, 0, 0.2);
margin-left: 1rem;
text-decoration: none;
transition: background-color 0.2s;
}
${host} .github-link:hover {
background-color: rgba(0, 0, 0, 0.3);
text-decoration: none;
}
@media (min-width: 768px) {
${host} p {
display: block;
font-size: 0.875rem;
opacity: 0.9;
}
}
/* Navigation */
${host_nav} {
grid-area: sidebar;
background-color: var(--bg-sidebar);
border-right: 1px solid var(--border);
padding: 1.5rem 1rem;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
${host_nav} a {
display: flex;
align-items: center;
padding: 0.625rem 0.75rem;
border-radius: var(--border-radius);
color: var(--text);
text-decoration: none;
transition: background-color 0.2s ease, color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
line-height: 1.2;
}
${host_nav} a:hover {
background-color: rgba(var(--primary-rgb), 0.08); /* Using CSS variables for better theming */
text-decoration: none;
transform: translateY(-1px);
color: var(--primary);
}
${host_nav} a.current,
${host_nav} a[aria-current=page] {
background-color: var(--nav-current-bg, var(--primary-dark));
color: var(--nav-current-text, white);
font-weight: 600;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.25);
}
${host_nav} a.current:hover,
${host_nav} a[aria-current=page]:hover {
background-color: var(--primary);
color: white;
transform: translateY(-1px);
}
${host_nav} a .nav-number {
display: inline-block;
width: 1.5rem;
text-align: right;
margin-right: 0.5rem;
opacity: 0.7;
}
${host_nav} a:first-child {
display: flex;
align-items: center;
font-weight: 600;
margin-bottom: 0.5rem;
}
/* Mobile navigation */
@media (max-width: 767px) {
${host_nav} {
padding: 0.75rem;
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 0.5rem;
border-bottom: 1px solid var(--border);
border-right: none;
justify-content: center;
}
${host_nav} a {
font-size: 0.875rem;
padding: 0.375rem 0.75rem;
white-space: nowrap;
}
${host_nav} a .nav-number {
width: auto;
margin-right: 0.25rem;
}
${host_nav} a:first-child {
margin-bottom: 0;
margin-right: 0.5rem;
min-width: 100%;
justify-content: center;
}
}
`;
/**
* @param {object} def
* @param {import("../types.d.ts").Info} def.info
* @param {import("../types.d.ts").Pkg} def.pkg Package information.
* */
export function header({ info: { href, title, description }, pkg }){
title= `\`${pkg.name}\`${title}`;
const pageTitle = `${pkg.name}${title}`;
// Add meta elements to the head
document.head.append(
head({ title, description, pkg })
head({ title: pageTitle, description, pkg })
);
// Add theme color meta tag
document.head.append(
el("meta", { name: "theme-color", content: "#3f51b5" })
);
return el().append(
el("header").append(
el("h1", title),
el("p", description)
),
el("nav").append(
el("a", { href: pkg.homepage }).append(
// Header section with accessibility support
el("header", { role: "banner", className: header.name }).append(
el("div", { className: "header-title" }).append(
el("h1", pkg.name),
el("span", {
className: "version-badge",
"aria-label": "Version",
textContent: pkg.version || ""
})
),
el("p", description),
el("a", {
href: pkg.homepage,
className: "github-link",
"aria-label": "View on GitHub",
target: "_blank",
rel: "noopener noreferrer"
}).append(
el(iconGitHub),
"GitHub"
),
...pages.map((p, i)=> el("a", {
href: p.href==="index" ? "./" : p.href,
textContent: (i+1) + ". " + p.title,
title: p.description,
classList: { current: p.href===href }
}))
)
)
),
// Navigation between pages
nav({ href })
);
}
function nav({ href }){
return el("nav", {
role: "navigation",
"aria-label": "Main navigation",
className: nav.name
}).append(
...pages.map((p, i) => {
const isIndex = p.href === "index";
const isCurrent = p.href === href;
return el("a", {
href: isIndex ? "./" : p.href,
title: p.description || `Go to ${p.title}`,
"aria-current": isCurrent ? "page" : null,
classList: { current: isCurrent }
}).append(
el("span", {
className: "nav-number",
"aria-hidden": "true",
textContent: `${i+1}.`
}),
p.title
);
})
);
}
function head({ title, description, pkg }){
return el().append(
el("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }),
el("meta", { name: "description", content: description }),
el("meta", { name: "theme-color", content: "#b71c1c" }),
el("title", title),
el(metaAuthor),
el(metaTwitter, pkg),

View File

@ -7,9 +7,25 @@ import { prevNext } from "../components/pageUtils.html.js";
/** @param {Pick<import("../types.d.ts").PageAttrs, "pkg" | "info">} attrs */
export function simplePage({ pkg, info }){
return simulateSlots(el().append(
// Skip link for keyboard navigation
el("a", {
href: "#main-content",
className: "skip-link",
textContent: "Skip to main content"
}),
// Header with site information
el(header, { info, pkg }),
el("main").append(
// Main content area
el("main", { id: "main-content", role: "main" }).append(
// Page title as an h1
el("h1", { className: "page-title", textContent: info.title }),
// Main content from child elements
el("slot"),
// Navigation between pages
el(prevNext, info)
)
));

View File

@ -1,7 +1,7 @@
import { T, t } from "./utils/index.js";
export const info= {
title: t`Signals and reactivity`,
description: t`Handling reactivity in UI via signals.`,
description: t`Managing reactive UI state with signals.`,
};
import { el } from "deka-dom-el";
@ -100,10 +100,10 @@ export function page({ pkg, info }){
`),
el(h3, t`Reactive DOM attributes and elements`),
el("p", t`There are on basic level two distinc situation to mirror dynamic value into the DOM/UI`),
el("p", t`There are two fundamental ways to make your DOM reactive with signals:`),
el("ol").append(
el("li", t`to change some attribute(s) of existing element(s)`),
el("li", t`to generate elements itself dynamically this covers conditions and loops`)
el("li", t`Reactive attributes: Update properties, attributes, and styles of existing elements`),
el("li", t`Reactive elements: Dynamically create or update DOM elements based on data changes (for conditions and loops)`)
),
el(example, { src: fileURL("./components/examples/signals/dom-attrs.js"), page_id }),
el("p").append(...T`