This commit is contained in:
parent
5dfcdfd516
commit
c06f71a5ad
34 changed files with 3641 additions and 279 deletions
3
sites/poetry/package-lock.json
generated
3
sites/poetry/package-lock.json
generated
|
|
@ -50,7 +50,8 @@
|
|||
"tailwind-merge": "^3.0.2",
|
||||
"tailwindcss": "^4.0.12",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"typescript": "^5.9.2"
|
||||
"typescript": "^5.9.2",
|
||||
"yaml": "^2.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^3.5.3",
|
||||
|
|
|
|||
|
|
@ -62,7 +62,8 @@
|
|||
"tailwind-merge": "^3.0.2",
|
||||
"tailwindcss": "^4.0.12",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"typescript": "^5.9.2"
|
||||
"typescript": "^5.9.2",
|
||||
"yaml": "^2.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^3.5.3",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
import { existsSync, readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { parse as parseYaml } from "yaml";
|
||||
import { fromMarkdown } from "mdast-util-from-markdown";
|
||||
|
||||
const ENV_TYPES = new Set([
|
||||
|
|
@ -24,7 +27,8 @@ const ENV_LABELS = {
|
|||
|
||||
function tokenizeInfo(info) {
|
||||
return (
|
||||
info.match(/[^\s=]+=(?:"[^"]*"|'[^']*'|[^\s]+)|"[^"]*"|'[^']*'|[^\s]+/g) ?? []
|
||||
info.match(/[^\s=]+=(?:"[^"]*"|'[^']*'|[^\s]+)|"[^"]*"|'[^']*'|[^\s]+/g) ??
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -102,22 +106,308 @@ function encodedJson(value) {
|
|||
return encodeURIComponent(JSON.stringify(value));
|
||||
}
|
||||
|
||||
function csv(value) {
|
||||
return String(value || "")
|
||||
.split(",")
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function keyValueList(value) {
|
||||
const entries = {};
|
||||
for (const item of csv(value)) {
|
||||
const separator = item.indexOf(":");
|
||||
if (separator <= 0) continue;
|
||||
const key = item.slice(0, separator).trim();
|
||||
const entryValue = item.slice(separator + 1).trim();
|
||||
if (key && entryValue) entries[key] = entryValue;
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
function booleanAttr(value, defaultValue = true) {
|
||||
if (value === undefined || value === "") return defaultValue;
|
||||
return !["0", "false", "no", "off"].includes(
|
||||
String(value).trim().toLowerCase(),
|
||||
);
|
||||
}
|
||||
|
||||
function labelize(value) {
|
||||
return String(value || "")
|
||||
.replace(/[-_]+/g, " ")
|
||||
.replace(/\b\w/g, (letter) => letter.toUpperCase());
|
||||
}
|
||||
|
||||
function plainText(value) {
|
||||
return String(value ?? "")
|
||||
.replace(/<br\s*\/?>/gi, " ")
|
||||
.replace(/<[^>]+>/g, " ")
|
||||
.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
|
||||
function inlineMarkup(value) {
|
||||
return escapeHtml(value ?? "")
|
||||
.replace(/<br\s*\/?>/gi, "<br>")
|
||||
.replace(/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g, '<a href="$2">$1</a>');
|
||||
}
|
||||
|
||||
function semanticError(message) {
|
||||
return {
|
||||
type: "html",
|
||||
value: `<aside class="semantic-error">${message}</aside>`,
|
||||
};
|
||||
}
|
||||
|
||||
function firstClass(attrs, excluded) {
|
||||
return attrs.classes.find((className) => !excluded.has(className));
|
||||
}
|
||||
|
||||
function sourcePathForFile(file) {
|
||||
return file?.path || file?.history?.[0] || "";
|
||||
}
|
||||
|
||||
function resolveDataPath(src, file) {
|
||||
if (!src) return "";
|
||||
if (path.isAbsolute(src)) return src;
|
||||
|
||||
const sourcePath = sourcePathForFile(file);
|
||||
const baseDir = sourcePath ? path.dirname(sourcePath) : process.cwd();
|
||||
return path.resolve(baseDir, src);
|
||||
}
|
||||
|
||||
function readDataRows(src, file) {
|
||||
const resolvedPath = resolveDataPath(src, file);
|
||||
if (!resolvedPath || !existsSync(resolvedPath)) {
|
||||
throw new Error(`Data file not found: ${src}`);
|
||||
}
|
||||
|
||||
const raw = readFileSync(resolvedPath, "utf8");
|
||||
const extension = path.extname(resolvedPath).toLowerCase();
|
||||
const parsed = extension === ".json" ? JSON.parse(raw) : parseYaml(raw);
|
||||
const rows = Array.isArray(parsed) ? parsed : parsed?.items;
|
||||
|
||||
if (!Array.isArray(rows)) {
|
||||
throw new Error(
|
||||
`Data file must contain an array or an object with an items array: ${src}`,
|
||||
);
|
||||
}
|
||||
|
||||
return rows.filter((row) => row && typeof row === "object");
|
||||
}
|
||||
|
||||
const VALUE_LABELS = {
|
||||
MOC: "Map / index",
|
||||
book: "Book",
|
||||
course: "Course",
|
||||
essay: "Essay",
|
||||
other: "Other",
|
||||
software: "Software",
|
||||
youtube: "YouTube",
|
||||
};
|
||||
|
||||
function displayValue(value) {
|
||||
return VALUE_LABELS[value] || value;
|
||||
}
|
||||
|
||||
function renderCell(row, column, options) {
|
||||
const raw = row[column] ?? "";
|
||||
const display = displayValue(raw);
|
||||
const text = plainText(display);
|
||||
const sortValue = attribute(text.toLowerCase());
|
||||
const cellAttrs = `data-column="${attribute(column)}" data-sort-value="${sortValue}"`;
|
||||
|
||||
if (column === options.linkColumn && row[options.urlField]) {
|
||||
const numberValue = row[options.numberField];
|
||||
const numberLabel =
|
||||
numberValue === undefined || numberValue === null || numberValue === ""
|
||||
? ""
|
||||
: String(numberValue).padStart(2, "0");
|
||||
const prefix = options.numberField && numberLabel ? `${numberLabel}. ` : "";
|
||||
return `<td ${cellAttrs}><a href="${attribute(row[options.urlField])}">${escapeHtml(prefix)}${inlineMarkup(display)}</a></td>`;
|
||||
}
|
||||
|
||||
if (column === "type") {
|
||||
return `<td ${cellAttrs}><span class="data-table-badge">${escapeHtml(display)}</span></td>`;
|
||||
}
|
||||
|
||||
if (
|
||||
column === "summary" ||
|
||||
String(display).length > 140 ||
|
||||
String(display).includes("<br")
|
||||
) {
|
||||
return `<td ${cellAttrs}><div class="data-table-rich-cell">${inlineMarkup(display)}</div></td>`;
|
||||
}
|
||||
|
||||
return `<td ${cellAttrs}>${inlineMarkup(display)}</td>`;
|
||||
}
|
||||
|
||||
function dataTableScript(id) {
|
||||
return `<script>
|
||||
(() => {
|
||||
const root = document.getElementById(${JSON.stringify(id)});
|
||||
if (!root || root.dataset.dataTableReady) return;
|
||||
root.dataset.dataTableReady = "true";
|
||||
|
||||
const input = root.querySelector("[data-data-table-search]");
|
||||
const tbody = root.querySelector("tbody");
|
||||
const empty = root.querySelector("[data-data-table-empty]");
|
||||
const rows = Array.from(tbody?.querySelectorAll("tr") || []);
|
||||
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" });
|
||||
|
||||
function applyFilter() {
|
||||
const query = (input?.value || "").trim().toLowerCase();
|
||||
let visible = 0;
|
||||
|
||||
for (const row of rows) {
|
||||
const matches = !query || row.dataset.search.includes(query);
|
||||
row.hidden = !matches;
|
||||
if (matches) visible += 1;
|
||||
}
|
||||
|
||||
if (empty) empty.hidden = visible !== 0;
|
||||
}
|
||||
|
||||
input?.addEventListener("input", applyFilter);
|
||||
|
||||
root.querySelectorAll("[data-data-table-sort]").forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
const column = button.dataset.dataTableSort;
|
||||
const current = button.getAttribute("aria-sort");
|
||||
const direction = current === "ascending" ? "descending" : "ascending";
|
||||
|
||||
root.querySelectorAll("[data-data-table-sort]").forEach((other) => {
|
||||
other.removeAttribute("aria-sort");
|
||||
});
|
||||
button.setAttribute("aria-sort", direction);
|
||||
|
||||
rows
|
||||
.slice()
|
||||
.sort((left, right) => {
|
||||
const leftValue = left.querySelector(\`[data-column="\${column}"]\`)?.dataset.sortValue || "";
|
||||
const rightValue = right.querySelector(\`[data-column="\${column}"]\`)?.dataset.sortValue || "";
|
||||
const result = collator.compare(leftValue, rightValue);
|
||||
return direction === "ascending" ? result : -result;
|
||||
})
|
||||
.forEach((row) => tbody.appendChild(row));
|
||||
|
||||
applyFilter();
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>`;
|
||||
}
|
||||
|
||||
function dataTableFromCode(node, file) {
|
||||
const attrs = parseFenceInfo(node);
|
||||
if (!attrs.classes.includes("data-table")) return null;
|
||||
|
||||
const src = attrs.values.src || attrs.values.source || "";
|
||||
if (!src) {
|
||||
return semanticError('Data table block missing <code>src="..."</code>.');
|
||||
}
|
||||
|
||||
let rows;
|
||||
try {
|
||||
rows = readDataRows(src, file);
|
||||
} catch (error) {
|
||||
return semanticError(escapeHtml(error.message));
|
||||
}
|
||||
|
||||
const firstRow = rows[0] || {};
|
||||
const columns = csv(attrs.values.columns).length
|
||||
? csv(attrs.values.columns)
|
||||
: Object.keys(firstRow).filter((key) => key !== "link");
|
||||
const headers = keyValueList(attrs.values.headers || attrs.values.labels);
|
||||
const linkColumn =
|
||||
attrs.values.link || (columns.includes("title") ? "title" : "");
|
||||
const urlField = attrs.values.url || attrs.values.href || "link";
|
||||
const numberField = attrs.values.number || "";
|
||||
const caption = attrs.values.caption || attrs.values.title || "";
|
||||
const searchable = booleanAttr(attrs.values.search, true);
|
||||
const sortable = booleanAttr(attrs.values.sort, true);
|
||||
const placeholder =
|
||||
attrs.values["search-placeholder"] || `Search ${caption || "table"}`;
|
||||
const id =
|
||||
attrs.id ||
|
||||
`data-table-${src.replace(/[^a-z0-9]+/gi, "-").replace(/^-|-$/g, "")}`;
|
||||
const tableId = `${id}-table`;
|
||||
const searchId = `${id}-search`;
|
||||
|
||||
const headerHtml = columns
|
||||
.map((column) => {
|
||||
const label = headers[column] || labelize(column);
|
||||
const content = sortable
|
||||
? `<button type="button" data-data-table-sort="${attribute(column)}">${escapeHtml(label)}</button>`
|
||||
: escapeHtml(label);
|
||||
return `<th scope="col">${content}</th>`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
const bodyHtml = rows
|
||||
.map((row) => {
|
||||
const searchText = columns
|
||||
.map((column) => plainText(displayValue(row[column] ?? "")))
|
||||
.join(" ")
|
||||
.toLowerCase();
|
||||
const cells = columns
|
||||
.map((column) =>
|
||||
renderCell(row, column, { linkColumn, urlField, numberField }),
|
||||
)
|
||||
.join("");
|
||||
return `<tr data-search="${attribute(searchText)}">${cells}</tr>`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
const searchHtml = searchable
|
||||
? [
|
||||
'<div class="data-table-search">',
|
||||
`<label for="${attribute(searchId)}">Search</label>`,
|
||||
`<input id="${attribute(searchId)}" type="search" autocomplete="off" placeholder="${attribute(placeholder)}" data-data-table-search>`,
|
||||
"</div>",
|
||||
].join("")
|
||||
: "";
|
||||
|
||||
return {
|
||||
type: "html",
|
||||
value: [
|
||||
`<section class="semantic-data-table" id="${attribute(id)}" data-data-table-root>`,
|
||||
caption || searchable
|
||||
? `<div class="data-table-toolbar">${caption ? `<h2>${escapeHtml(caption)}</h2>` : ""}${searchHtml}</div>`
|
||||
: "",
|
||||
'<div class="data-table-scroll">',
|
||||
`<table id="${attribute(tableId)}">`,
|
||||
caption ? `<caption>${escapeHtml(caption)}</caption>` : "",
|
||||
`<thead><tr>${headerHtml}</tr></thead>`,
|
||||
`<tbody>${bodyHtml}</tbody>`,
|
||||
"</table>",
|
||||
"</div>",
|
||||
`<p class="data-table-empty" data-data-table-empty hidden>No matching rows.</p>`,
|
||||
"</section>",
|
||||
dataTableScript(id),
|
||||
].join(""),
|
||||
};
|
||||
}
|
||||
|
||||
function interactiveFromCode(node) {
|
||||
const attrs = parseFenceInfo(node);
|
||||
if (!attrs.classes.includes("interactive")) return null;
|
||||
|
||||
const declaredId = attrs.id || attrs.values.component || attrs.values.name || "";
|
||||
const declaredId =
|
||||
attrs.id || attrs.values.component || attrs.values.name || "";
|
||||
const componentId =
|
||||
attrs.values.component ||
|
||||
(declaredId.startsWith("int:") ? declaredId.slice("int:".length) : declaredId);
|
||||
const label = attrs.values.label || (declaredId.startsWith("int:") ? declaredId : `int:${declaredId}`);
|
||||
(declaredId.startsWith("int:")
|
||||
? declaredId.slice("int:".length)
|
||||
: declaredId);
|
||||
const label =
|
||||
attrs.values.label ||
|
||||
(declaredId.startsWith("int:") ? declaredId : `int:${declaredId}`);
|
||||
const caption = attrs.values.caption || attrs.values.title || "";
|
||||
const description = node.value.trim();
|
||||
const fallback = attrs.values.pdf || attrs.values.fallback || description || caption;
|
||||
const fallback =
|
||||
attrs.values.pdf || attrs.values.fallback || description || caption;
|
||||
|
||||
if (!componentId) {
|
||||
return {
|
||||
|
|
@ -141,7 +431,9 @@ function interactiveFromCode(node) {
|
|||
value: [
|
||||
`<figure class="semantic-interactive" id="${attribute(label)}" data-interactive-label="${attribute(label)}">`,
|
||||
`<div class="interactive-mount" data-interactive-id="${attribute(componentId)}" data-interactive-props="${attribute(encodedJson(props))}">`,
|
||||
fallback ? `<p class="interactive-fallback">${escapeHtml(fallback)}</p>` : "",
|
||||
fallback
|
||||
? `<p class="interactive-fallback">${escapeHtml(fallback)}</p>`
|
||||
: "",
|
||||
"</div>",
|
||||
caption ? `<figcaption>${escapeHtml(caption)}</figcaption>` : "",
|
||||
"</figure>",
|
||||
|
|
@ -154,7 +446,10 @@ function envFromCode(node) {
|
|||
const shorthandType = firstClass(attrs, new Set(["env", "latex-env"]));
|
||||
const explicitType = attrs.values.type || shorthandType;
|
||||
const type = ENV_TYPES.has(explicitType) ? explicitType : "";
|
||||
const isEnv = attrs.classes.includes("env") || attrs.classes.includes("latex-env") || Boolean(type);
|
||||
const isEnv =
|
||||
attrs.classes.includes("env") ||
|
||||
attrs.classes.includes("latex-env") ||
|
||||
Boolean(type);
|
||||
|
||||
if (!isEnv) return null;
|
||||
|
||||
|
|
@ -168,7 +463,8 @@ function envFromCode(node) {
|
|||
|
||||
const title = attrs.values.title || attrs.values.name || "";
|
||||
const label = attrs.id || attrs.values.label || "";
|
||||
const renderMode = attrs.values.html || (type === "proof" ? "plain" : "callout");
|
||||
const renderMode =
|
||||
attrs.values.html || (type === "proof" ? "plain" : "callout");
|
||||
const heading = title ? `${ENV_LABELS[type]} (${title})` : ENV_LABELS[type];
|
||||
const parsed = fromMarkdown(node.value || "");
|
||||
const className = [
|
||||
|
|
@ -210,23 +506,26 @@ function envFromCode(node) {
|
|||
};
|
||||
}
|
||||
|
||||
function transformTree(parent) {
|
||||
function transformTree(parent, file) {
|
||||
if (!parent?.children) return;
|
||||
|
||||
for (let index = 0; index < parent.children.length; index += 1) {
|
||||
const child = parent.children[index];
|
||||
if (child.type === "code") {
|
||||
const replacement = interactiveFromCode(child) || envFromCode(child);
|
||||
const replacement =
|
||||
dataTableFromCode(child, file) ||
|
||||
interactiveFromCode(child) ||
|
||||
envFromCode(child);
|
||||
if (replacement) {
|
||||
parent.children[index] = replacement;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
transformTree(child);
|
||||
transformTree(child, file);
|
||||
}
|
||||
}
|
||||
|
||||
export default function remarkSemanticBlocks() {
|
||||
return (tree) => transformTree(tree);
|
||||
return (tree, file) => transformTree(tree, file);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,8 +67,8 @@
|
|||
.article-content {
|
||||
color: hsl(var(--foreground));
|
||||
font-family:
|
||||
var(--font-heliotrope), "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji",
|
||||
"Noto Sans Symbols 2", "DejaVu Sans", sans-serif;
|
||||
var(--font-heliotrope), "Apple Color Emoji", "Segoe UI Emoji",
|
||||
"Noto Color Emoji", "Noto Sans Symbols 2", "DejaVu Sans", sans-serif;
|
||||
font-size: 1.0625rem;
|
||||
line-height: 1.78;
|
||||
}
|
||||
|
|
@ -138,7 +138,8 @@
|
|||
}
|
||||
|
||||
.article-content :where(.symbol-emoji) {
|
||||
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", emoji, sans-serif;
|
||||
font-family:
|
||||
"Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", emoji, sans-serif;
|
||||
}
|
||||
|
||||
.article-content :where(.symbol-chess, .symbol-card) {
|
||||
|
|
@ -146,7 +147,11 @@
|
|||
font-size: 1.04em;
|
||||
}
|
||||
|
||||
.article-content :where(.symbol-card[data-symbol="heart"], .symbol-card[data-symbol="diamond"]) {
|
||||
.article-content
|
||||
:where(
|
||||
.symbol-card[data-symbol="heart"],
|
||||
.symbol-card[data-symbol="diamond"]
|
||||
) {
|
||||
color: color-mix(in srgb, crimson 80%, hsl(var(--foreground)));
|
||||
}
|
||||
|
||||
|
|
@ -281,7 +286,8 @@
|
|||
|
||||
.article-content :where(.semantic-error) {
|
||||
margin-block: 1.5rem;
|
||||
border: 1px solid color-mix(in srgb, hsl(var(--destructive)) 50%, hsl(var(--border)));
|
||||
border: 1px solid
|
||||
color-mix(in srgb, hsl(var(--destructive)) 50%, hsl(var(--border)));
|
||||
border-radius: 0.55rem;
|
||||
background: color-mix(in srgb, hsl(var(--destructive)) 8%, transparent);
|
||||
padding: 1rem;
|
||||
|
|
@ -333,7 +339,8 @@
|
|||
padding: 1rem;
|
||||
}
|
||||
|
||||
.article-content :where(.interactive-mount[data-interactive-state="missing-component"]) {
|
||||
.article-content
|
||||
:where(.interactive-mount[data-interactive-state="missing-component"]) {
|
||||
display: grid;
|
||||
min-height: 8rem;
|
||||
place-items: center;
|
||||
|
|
@ -375,6 +382,168 @@
|
|||
padding: 0.45rem 0.6rem;
|
||||
}
|
||||
|
||||
.article-content :where(.semantic-data-table) {
|
||||
margin-top: 2rem;
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.75rem;
|
||||
background: color-mix(in srgb, hsl(var(--background)) 92%, hsl(var(--muted)));
|
||||
box-shadow: 0 16px 40px
|
||||
color-mix(in srgb, hsl(var(--foreground)) 7%, transparent);
|
||||
}
|
||||
|
||||
.article-content :where(.data-table-toolbar) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.85rem;
|
||||
border-bottom: 1px solid hsl(var(--border));
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.article-content :where(.data-table-toolbar h2) {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.article-content :where(.data-table-search) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.55rem;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.article-content :where(.data-table-search label) {
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.72rem;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.article-content :where(.data-table-search input) {
|
||||
width: min(18rem, 58vw);
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 999px;
|
||||
background: hsl(var(--background));
|
||||
padding: 0.45rem 0.8rem;
|
||||
color: hsl(var(--foreground));
|
||||
font: inherit;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.article-content :where(.data-table-scroll) {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.article-content :where(.semantic-data-table table) {
|
||||
display: table;
|
||||
width: 100%;
|
||||
min-width: 44rem;
|
||||
border-collapse: collapse;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.article-content :where(.semantic-data-table caption) {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.article-content :where(.semantic-data-table th) {
|
||||
background: color-mix(in srgb, hsl(var(--muted)) 48%, transparent);
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 650;
|
||||
letter-spacing: 0.08em;
|
||||
text-align: left;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.article-content :where(.semantic-data-table th button) {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
letter-spacing: inherit;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
text-transform: inherit;
|
||||
}
|
||||
|
||||
.article-content :where(.semantic-data-table th button)::after {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 0.42rem;
|
||||
height: 0.42rem;
|
||||
border-right: 1px solid currentColor;
|
||||
border-bottom: 1px solid currentColor;
|
||||
color: color-mix(in srgb, hsl(var(--muted-foreground)) 58%, transparent);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.article-content
|
||||
:where(.semantic-data-table th button[aria-sort="ascending"])::after {
|
||||
transform: translateY(0.12rem) rotate(225deg);
|
||||
}
|
||||
|
||||
.article-content
|
||||
:where(.semantic-data-table th button[aria-sort="descending"])::after {
|
||||
transform: translateY(-0.12rem) rotate(45deg);
|
||||
}
|
||||
|
||||
.article-content :where(.semantic-data-table td) {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.article-content :where(.semantic-data-table td:first-child) {
|
||||
width: 7.5rem;
|
||||
}
|
||||
|
||||
.article-content :where(.semantic-data-table td:nth-child(2)) {
|
||||
width: 18rem;
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.article-content :where(.data-table-badge) {
|
||||
display: inline-flex;
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 999px;
|
||||
padding: 0.15rem 0.5rem;
|
||||
background: hsl(var(--background));
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.72rem;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.article-content :where(.data-table-rich-cell) {
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-size: 0.98rem;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.article-content :where(.data-table-rich-cell br + br) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.article-content :where(.data-table-empty) {
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.article-content :where(.twitter-tweet) {
|
||||
max-width: 100% !important;
|
||||
margin: 1.5rem auto !important;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue