knowledgebase style
This commit is contained in:
parent
9a3122745d
commit
a816c0630f
@ -3,6 +3,8 @@ import Navigation from '../components/Navigation.astro';
|
||||
import Footer from '../components/Footer.astro';
|
||||
import '../styles/global.css';
|
||||
import '../styles/auditTrail.css';
|
||||
import '../styles/knowledgebase.css';
|
||||
import '../styles/palette.css';
|
||||
|
||||
export interface Props {
|
||||
title: string;
|
||||
|
@ -52,205 +52,430 @@ const currentUrl = Astro.url.href;
|
||||
---
|
||||
|
||||
<BaseLayout title={entry.data.title} description={entry.data.description}>
|
||||
<article style="max-width: 900px; margin: 0 auto;">
|
||||
<header style="margin-bottom: 2rem; padding: 2rem; background: linear-gradient(135deg, var(--color-bg-secondary) 0%, var(--color-bg-tertiary) 100%); border-radius: 1rem; border: 1px solid var(--color-border);">
|
||||
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 1rem;">
|
||||
<div style="flex: 1;">
|
||||
<h1 style="margin: 0 0 0.5rem 0; color: var(--color-primary);">
|
||||
{displayTool?.icon && <span style="margin-right: 0.75rem; font-size: 1.5rem;">{displayTool.icon}</span>}
|
||||
<div class="article-layout">
|
||||
<!-- Article Header -->
|
||||
<header class="article-header">
|
||||
<div class="article-header-content">
|
||||
<div class="article-meta">
|
||||
<nav class="breadcrumb">
|
||||
<a href="/knowledgebase" class="breadcrumb-link">Knowledgebase</a>
|
||||
<span class="breadcrumb-separator">→</span>
|
||||
<span class="breadcrumb-current">{entry.data.title}</span>
|
||||
</nav>
|
||||
|
||||
<div class="article-tags">
|
||||
{entry.data.categories?.map((cat: string) => (
|
||||
<span class="article-tag article-tag-category">{cat}</span>
|
||||
))}
|
||||
{entry.data.tags?.map((tag: string) => (
|
||||
<span class="article-tag">{tag}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="article-title-section">
|
||||
<h1 class="article-title">
|
||||
{displayTool?.icon && <span class="article-icon">{displayTool.icon}</span>}
|
||||
{entry.data.title}
|
||||
</h1>
|
||||
<p style="margin: 0; color: var(--color-text-secondary); font-size: 1.125rem;">
|
||||
{entry.data.description}
|
||||
</p>
|
||||
<p class="article-description">{entry.data.description}</p>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; gap: 0.5rem; align-items: end;">
|
||||
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
|
||||
{isStandalone ? (
|
||||
<span class="badge" style="background-color: var(--color-accent); color: white;">Artikel</span>
|
||||
) : (
|
||||
<>
|
||||
{isConcept && <span class="badge" style="background-color: var(--color-concept); color: white;">Konzept</span>}
|
||||
{isMethod && <span class="badge" style="background-color: var(--color-method); color: white;">Methode</span>}
|
||||
{!isMethod && !isConcept && !isStandalone && <span class="badge" style="background-color: var(--color-primary); color: white;">Software</span>}
|
||||
{!isMethod && !isConcept && hasValidProjectUrl && <span class="badge badge-primary">CC24-Server</span>}
|
||||
{!isMethod && !isConcept && !isStandalone && displayTool?.license !== 'Proprietary' && <span class="badge badge-success">Open Source</span>}
|
||||
</>
|
||||
)}
|
||||
<span class="badge badge-error">📖</span>
|
||||
|
||||
<div class="article-metadata-grid">
|
||||
<div class="metadata-item">
|
||||
<span class="metadata-label">Typ</span>
|
||||
<div class="metadata-badges">
|
||||
{isStandalone ? (
|
||||
<span class="badge badge-accent">Artikel</span>
|
||||
) : (
|
||||
<>
|
||||
{isConcept && <span class="badge badge-concept">Konzept</span>}
|
||||
{isMethod && <span class="badge badge-method">Methode</span>}
|
||||
{!isMethod && !isConcept && <span class="badge badge-primary">Software</span>}
|
||||
{hasValidProjectUrl && <span class="badge badge-primary">CC24-Server</span>}
|
||||
</>
|
||||
)}
|
||||
<span class="badge badge-error">📖</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{entry.data.difficulty && (
|
||||
<div class="metadata-item">
|
||||
<span class="metadata-label">Schwierigkeit</span>
|
||||
<span class="metadata-value">{entry.data.difficulty}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div class="metadata-item">
|
||||
<span class="metadata-label">Aktualisiert</span>
|
||||
<span class="metadata-value">{entry.data.last_updated.toLocaleDateString('de-DE')}</span>
|
||||
</div>
|
||||
|
||||
<div class="metadata-item">
|
||||
<span class="metadata-label">Autor</span>
|
||||
<span class="metadata-value">{entry.data.author}</span>
|
||||
</div>
|
||||
|
||||
<div class="metadata-item">
|
||||
<span class="metadata-label">Lesezeit</span>
|
||||
<span class="metadata-value" id="reading-time">~5 min</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="article-actions">
|
||||
<button
|
||||
id="share-article-btn"
|
||||
class="btn btn-secondary btn-sm"
|
||||
class="btn btn-secondary"
|
||||
onclick="window.shareCurrentArticle(this)"
|
||||
title="Artikel teilen"
|
||||
style="font-size: 0.8125rem; padding: 0.5rem 0.75rem;"
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.375rem;">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="18" cy="5" r="3"/>
|
||||
<circle cx="6" cy="12" r="3"/>
|
||||
<circle cx="18" cy="19" r="3"/>
|
||||
<line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/>
|
||||
<line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/>
|
||||
</svg>
|
||||
Artikel teilen
|
||||
Teilen
|
||||
</button>
|
||||
|
||||
<a href="/knowledgebase" class="btn btn-secondary">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="15,18 9,12 15,6"></polyline>
|
||||
</svg>
|
||||
Zurück
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--color-border);">
|
||||
{entry.data.difficulty && (
|
||||
<div>
|
||||
<strong style="font-size: 0.875rem; color: var(--color-text-secondary);">Schwierigkeit</strong>
|
||||
<p style="margin: 0; font-size: 0.9375rem;">{entry.data.difficulty}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<strong style="font-size: 0.875rem; color: var(--color-text-secondary);">Letztes Update</strong>
|
||||
<p style="margin: 0; font-size: 0.9375rem;">{entry.data.last_updated.toLocaleDateString('de-DE')}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong style="font-size: 0.875rem; color: var(--color-text-secondary);">Autor</strong>
|
||||
<p style="margin: 0; font-size: 0.9375rem;">{entry.data.author}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong style="font-size: 0.875rem; color: var(--color-text-secondary);">Typ</strong>
|
||||
<p style="margin: 0; font-size: 0.9375rem;">
|
||||
{isStandalone ? 'Allgemeiner Artikel' :
|
||||
isConcept ? 'Konzept-Artikel' :
|
||||
isMethod ? 'Methoden-Artikel' :
|
||||
'Software-Artikel'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{entry.data.categories && entry.data.categories.length > 0 && (
|
||||
<div style="grid-column: 1 / -1;">
|
||||
<strong style="font-size: 0.875rem; color: var(--color-text-secondary);">Kategorien</strong>
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 0.25rem; margin-top: 0.25rem;">
|
||||
{entry.data.categories.map((cat: string) => (
|
||||
<span class="tag" style="font-size: 0.75rem;">{cat}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<nav style="margin-bottom: 2rem; position: relative; z-index: 50;">
|
||||
<a href="/knowledgebase" class="btn btn-secondary">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
|
||||
<polyline points="15,18 9,12 15,6"></polyline>
|
||||
</svg>
|
||||
Zurück zur Knowledgebase
|
||||
</a>
|
||||
</nav>
|
||||
<!-- Main Content Area -->
|
||||
<div class="article-content-wrapper">
|
||||
<!-- Sidebar Navigation (will be populated by JS) -->
|
||||
<aside class="article-sidebar">
|
||||
<!-- TOC will be inserted here by JavaScript -->
|
||||
</aside>
|
||||
|
||||
<div class="card" style="padding: 2rem;">
|
||||
<div class="kb-content markdown-content" style="line-height: 1.7;">
|
||||
<Content />
|
||||
</div>
|
||||
</div>
|
||||
<!-- Article Content -->
|
||||
<main class="article-main">
|
||||
<article class="article-content">
|
||||
<div class="markdown-content">
|
||||
<Content />
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<div class="card" style="margin-top: 2rem; background-color: var(--color-bg-secondary);">
|
||||
<h3 style="margin: 0 0 1rem 0; color: var(--color-text);">
|
||||
{isStandalone ? 'Verwandte Aktionen' : 'Tool-Aktionen'}
|
||||
</h3>
|
||||
|
||||
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
|
||||
{isStandalone ? (
|
||||
<a href="/knowledgebase" class="btn btn-primary">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
|
||||
<polyline points="14 2 14 8 20 8"/>
|
||||
<line x1="16" y1="13" x2="8" y2="13"/>
|
||||
<line x1="16" y1="17" x2="8" y2="17"/>
|
||||
<polyline points="10 9 9 9 8 9"/>
|
||||
</svg>
|
||||
Weitere Artikel
|
||||
</a>
|
||||
) : (
|
||||
<>
|
||||
{isConcept ? (
|
||||
<a href={displayTool.url} target="_blank" rel="noopener noreferrer" class="btn btn-primary" style="background-color: var(--color-concept); border-color: var(--color-concept);">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
|
||||
<polyline points="15 3 21 3 21 9"/>
|
||||
<line x1="10" y1="14" x2="21" y2="3"/>
|
||||
</svg>
|
||||
Mehr erfahren
|
||||
</a>
|
||||
) : isMethod ? (
|
||||
<a href={displayTool.projectUrl || displayTool.url} target="_blank" rel="noopener noreferrer" class="btn btn-primary" style="background-color: var(--color-method); border-color: var(--color-method);">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
|
||||
<polyline points="15 3 21 3 21 9"/>
|
||||
<line x1="10" y1="14" x2="21" y2="3"/>
|
||||
</svg>
|
||||
Zur Methode
|
||||
</a>
|
||||
) : (
|
||||
<>
|
||||
<a href={displayTool.url} target="_blank" rel="noopener noreferrer" class="btn btn-secondary">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
|
||||
<polyline points="15 3 21 3 21 9"/>
|
||||
<line x1="10" y1="14" x2="21" y2="3"/>
|
||||
<!-- Article Footer -->
|
||||
<footer class="article-footer">
|
||||
<div class="article-footer-actions">
|
||||
<h3>Tool-Aktionen</h3>
|
||||
<div class="footer-actions-grid">
|
||||
{isStandalone ? (
|
||||
<a href="/knowledgebase" class="btn btn-primary">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
|
||||
<polyline points="14 2 14 8 20 8"/>
|
||||
</svg>
|
||||
Software-Homepage
|
||||
Weitere Artikel
|
||||
</a>
|
||||
{hasValidProjectUrl && (
|
||||
<a href={displayTool.projectUrl} target="_blank" rel="noopener noreferrer" class="btn btn-primary">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
<path d="M12 16l4-4-4-4"/>
|
||||
<path d="M8 12h8"/>
|
||||
</svg>
|
||||
Zugreifen
|
||||
</a>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
) : (
|
||||
<>
|
||||
{isConcept ? (
|
||||
<a href={displayTool.url} target="_blank" rel="noopener noreferrer" class="btn btn-concept">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
|
||||
<polyline points="15 3 21 3 21 9"/>
|
||||
<line x1="10" y1="14" x2="21" y2="3"/>
|
||||
</svg>
|
||||
Mehr erfahren
|
||||
</a>
|
||||
) : isMethod ? (
|
||||
<a href={displayTool.projectUrl || displayTool.url} target="_blank" rel="noopener noreferrer" class="btn btn-method">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
|
||||
<polyline points="15 3 21 3 21 9"/>
|
||||
<line x1="10" y1="14" x2="21" y2="3"/>
|
||||
</svg>
|
||||
Zur Methode
|
||||
</a>
|
||||
) : (
|
||||
<>
|
||||
<a href={displayTool.url} target="_blank" rel="noopener noreferrer" class="btn btn-secondary">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
|
||||
<polyline points="15 3 21 3 21 9"/>
|
||||
<line x1="10" y1="14" x2="21" y2="3"/>
|
||||
</svg>
|
||||
Homepage
|
||||
</a>
|
||||
{hasValidProjectUrl && (
|
||||
<a href={displayTool.projectUrl} target="_blank" rel="noopener noreferrer" class="btn btn-primary">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
<path d="M12 16l4-4-4-4"/>
|
||||
<path d="M8 12h8"/>
|
||||
</svg>
|
||||
Zugreifen
|
||||
</a>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{relatedTools.length > 0 && relatedTools.length > (primaryTool ? 1 : 0) && (
|
||||
<div style="margin-left: auto;">
|
||||
<details style="position: relative;">
|
||||
<summary class="btn btn-secondary" style="cursor: pointer; list-style: none;">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
|
||||
<path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
|
||||
<circle cx="8.5" cy="7" r="4"/>
|
||||
<line x1="20" y1="8" x2="20" y2="14"/>
|
||||
<line x1="23" y1="11" x2="17" y2="11"/>
|
||||
<a href="/" class="btn btn-secondary">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
|
||||
<polyline points="9,22 9,12 15,12 15,22"/>
|
||||
</svg>
|
||||
Verwandte Tools ({relatedTools.length})
|
||||
</summary>
|
||||
<div style="position: absolute; top: 100%; left: 0; background: var(--color-bg); border: 1px solid var(--color-border); border-radius: 0.5rem; padding: 1rem; min-width: 200px; z-index: 100; box-shadow: var(--shadow-lg);">
|
||||
Zur Hauptseite
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{relatedTools.length > 0 && (
|
||||
<div class="related-tools">
|
||||
<h3>Verwandte Tools</h3>
|
||||
<div class="related-tools-grid">
|
||||
{relatedTools.map((tool: any) => (
|
||||
<a href={tool.projectUrl || tool.url} target="_blank" rel="noopener noreferrer"
|
||||
style="display: block; padding: 0.5rem; border-radius: 0.25rem; text-decoration: none; color: var(--color-text); margin-bottom: 0.25rem;"
|
||||
onmouseover="this.style.backgroundColor='var(--color-bg-secondary)'"
|
||||
onmouseout="this.style.backgroundColor='transparent'">
|
||||
{tool.icon && <span style="margin-right: 0.5rem;">{tool.icon}</span>}
|
||||
{tool.name}
|
||||
<a href={tool.projectUrl || tool.url} target="_blank" rel="noopener noreferrer" class="related-tool-card">
|
||||
{tool.icon && <span class="tool-icon">{tool.icon}</span>}
|
||||
<span class="tool-name">{tool.name}</span>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<a href="/" class="btn btn-secondary">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
|
||||
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
|
||||
<polyline points="9,22 9,12 15,12 15,22"/>
|
||||
</svg>
|
||||
Zur Hauptseite
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/** @template {Element} T
|
||||
* @param {string} sel
|
||||
* @param {Document|Element} [root=document]
|
||||
* @returns {T|null} */
|
||||
const qs = (sel, root = document) => root.querySelector(sel);
|
||||
|
||||
/** @template {Element} T
|
||||
* @param {string} sel
|
||||
* @param {Document|Element} [root=document]
|
||||
* @returns {T[]} */
|
||||
const qsa = (sel, root = document) => Array.from(root.querySelectorAll(sel));
|
||||
|
||||
function calculateReadingTime() {
|
||||
/** @type {HTMLElement|null} */
|
||||
const content = qs('.markdown-content');
|
||||
if (!content) return;
|
||||
|
||||
const text = (content.textContent || content.innerText || '').trim();
|
||||
if (!text) return;
|
||||
|
||||
const wordsPerMinute = 200;
|
||||
const words = text.split(/\s+/).length;
|
||||
const readingTime = Math.ceil(words / wordsPerMinute);
|
||||
|
||||
const readingTimeElement = document.getElementById('reading-time');
|
||||
if (readingTimeElement) {
|
||||
readingTimeElement.textContent = `~${readingTime} min`;
|
||||
}
|
||||
}
|
||||
|
||||
function generateSidebarTOC() {
|
||||
/** @type {HTMLElement|null} */
|
||||
const article = qs('.markdown-content');
|
||||
/** @type {HTMLElement|null} */
|
||||
const sidebar = qs('.article-sidebar');
|
||||
/** @type {HTMLElement|null} */
|
||||
const main = qs('.article-main');
|
||||
|
||||
if (!article || !sidebar || !main) return;
|
||||
|
||||
/** @type {HTMLHeadingElement[]} */
|
||||
const headings = qsa('h1, h2, h3, h4, h5, h6', article);
|
||||
|
||||
if (headings.length < 2) {
|
||||
sidebar.style.display = 'none';
|
||||
main.style.maxWidth = '100%';
|
||||
return;
|
||||
}
|
||||
|
||||
headings.forEach((h, i) => {
|
||||
if (!h.id) h.id = `heading-${i}`;
|
||||
});
|
||||
|
||||
const tocHTML = `
|
||||
<div class="sidebar-toc">
|
||||
<h3 class="toc-title">Inhaltsverzeichnis</h3>
|
||||
<nav class="toc-navigation">
|
||||
${headings.map((h) => {
|
||||
const level = parseInt(h.tagName.slice(1), 10);
|
||||
const text = (h.textContent || '').trim();
|
||||
const id = h.id;
|
||||
return `
|
||||
<a href="#${id}" class="toc-item toc-level-${level}" data-heading="${id}">
|
||||
${text}
|
||||
</a>
|
||||
`;
|
||||
}).join('')}
|
||||
</nav>
|
||||
</div>
|
||||
`;
|
||||
|
||||
sidebar.innerHTML = tocHTML;
|
||||
|
||||
/** @type {HTMLAnchorElement[]} */
|
||||
const tocItems = qsa('.toc-item', sidebar);
|
||||
|
||||
tocItems.forEach((item) => {
|
||||
item.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const href = item.getAttribute('href');
|
||||
if (!href || !href.startsWith('#')) return;
|
||||
|
||||
const target = document.getElementById(href.slice(1));
|
||||
if (target) {
|
||||
target.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'nearest' });
|
||||
tocItems.forEach((i) => i.classList.remove('active'));
|
||||
item.classList.add('active');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const updateActiveSection = () => {
|
||||
const scrollY = window.scrollY + 100;
|
||||
let currentId = null;
|
||||
|
||||
for (let i = 0; i < headings.length; i++) {
|
||||
const h = headings[i];
|
||||
const rect = h.getBoundingClientRect();
|
||||
const absTop = rect.top + window.pageYOffset;
|
||||
if (absTop <= scrollY) currentId = h.id;
|
||||
}
|
||||
|
||||
if (currentId) {
|
||||
tocItems.forEach((i) => i.classList.remove('active'));
|
||||
/** @type {HTMLAnchorElement|null} */
|
||||
const active = qs(`.toc-item[data-heading="${currentId}"]`, sidebar);
|
||||
if (active) active.classList.add('active');
|
||||
}
|
||||
};
|
||||
|
||||
let ticking = false;
|
||||
window.addEventListener('scroll', () => {
|
||||
if (ticking) return;
|
||||
ticking = true;
|
||||
requestAnimationFrame(() => {
|
||||
updateActiveSection();
|
||||
ticking = false;
|
||||
});
|
||||
});
|
||||
|
||||
updateActiveSection();
|
||||
}
|
||||
|
||||
function enhanceCodeCopy() {
|
||||
/** @type {HTMLPreElement[]} */
|
||||
const pres = qsa('.markdown-content pre');
|
||||
|
||||
pres.forEach((pre) => {
|
||||
if (pre.dataset.copyEnhanced === 'true') return;
|
||||
pre.dataset.copyEnhanced = 'true';
|
||||
pre.style.position ||= 'relative';
|
||||
|
||||
// Try to find an existing copy button we can reuse
|
||||
let btn =
|
||||
pre.querySelector('.copy-btn') || // our class
|
||||
pre.querySelector('.btn-copy, .copy-button, .code-copy, .copy-code, button[aria-label*="copy" i]');
|
||||
|
||||
// If there is an "old" button that is NOT ours, prefer to reuse it by giving it our class.
|
||||
if (btn && !btn.classList.contains('copy-btn')) {
|
||||
btn.classList.add('copy-btn');
|
||||
}
|
||||
|
||||
// If no button at all, create one
|
||||
if (!btn) {
|
||||
btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'copy-btn';
|
||||
btn.setAttribute('aria-label', 'Code kopieren');
|
||||
btn.innerHTML = `
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2"/>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
||||
<span>Copy</span>
|
||||
`;
|
||||
pre.appendChild(btn);
|
||||
}
|
||||
|
||||
// If there is a SECOND old button lingering (top-left in your case), hide it
|
||||
const possibleOldButtons = pre.querySelectorAll(
|
||||
'.btn-copy, .copy-button, .code-copy, .copy-code, button[aria-label*="copy" i]'
|
||||
);
|
||||
possibleOldButtons.forEach((b) => {
|
||||
if (b !== btn) b.style.display = 'none';
|
||||
});
|
||||
|
||||
// Success pill
|
||||
if (!pre.querySelector('.copied-pill')) {
|
||||
const pill = document.createElement('div');
|
||||
pill.className = 'copied-pill';
|
||||
pill.textContent = '✓ Kopiert';
|
||||
pre.appendChild(pill);
|
||||
}
|
||||
|
||||
// Screen reader live region
|
||||
if (!pre.querySelector('.sr-live')) {
|
||||
const live = document.createElement('div');
|
||||
live.className = 'sr-live';
|
||||
live.setAttribute('aria-live', 'polite');
|
||||
Object.assign(live.style, {
|
||||
position: 'absolute', width: '1px', height: '1px', padding: '0', margin: '-1px',
|
||||
overflow: 'hidden', clip: 'rect(0,0,0,0)', border: '0'
|
||||
});
|
||||
pre.appendChild(live);
|
||||
}
|
||||
|
||||
btn.addEventListener('click', async () => {
|
||||
const code = pre.querySelector('code');
|
||||
const text = code ? code.innerText : pre.innerText;
|
||||
const live = pre.querySelector('.sr-live');
|
||||
|
||||
const copyText = async (t) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(t);
|
||||
return true;
|
||||
} catch {
|
||||
const ta = document.createElement('textarea');
|
||||
ta.value = t;
|
||||
ta.style.position = 'fixed';
|
||||
ta.style.top = '-1000px';
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
const ok = document.execCommand('copy');
|
||||
document.body.removeChild(ta);
|
||||
return ok;
|
||||
}
|
||||
};
|
||||
|
||||
const ok = await copyText(text);
|
||||
pre.dataset.copied = ok ? 'true' : 'false';
|
||||
if (live) live.textContent = ok ? 'Code in die Zwischenablage kopiert' : 'Kopieren fehlgeschlagen';
|
||||
|
||||
window.setTimeout(() => { pre.dataset.copied = 'false'; }, 1200);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// keep your existing DOMContentLoaded; just ensure this is called
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// existing:
|
||||
calculateReadingTime();
|
||||
generateSidebarTOC();
|
||||
// new/updated:
|
||||
enhanceCodeCopy();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
</BaseLayout>
|
@ -14,72 +14,7 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* ===================================================================
|
||||
2. CSS VARIABLES AND THEMING
|
||||
================================================================= */
|
||||
|
||||
:root {
|
||||
/* Light Theme Colors */
|
||||
--color-bg: #fff;
|
||||
--color-bg-secondary: #f8fafc;
|
||||
--color-bg-tertiary: #e2e8f0;
|
||||
--color-text: #1e293b;
|
||||
--color-text-secondary: #64748b;
|
||||
--color-border: #cbd5e1;
|
||||
--color-primary: #2563eb;
|
||||
--color-primary-hover: #1d4ed8;
|
||||
--color-accent: #059669;
|
||||
--color-accent-hover: #047857;
|
||||
--color-warning: #d97706;
|
||||
--color-error: #dc2626;
|
||||
|
||||
/* Enhanced card type colors */
|
||||
--color-hosted: #7c3aed;
|
||||
--color-hosted-bg: #f3f0ff;
|
||||
--color-oss: #059669;
|
||||
--color-oss-bg: #ecfdf5;
|
||||
--color-method: #0891b2;
|
||||
--color-method-bg: #f0f9ff;
|
||||
--color-concept: #ea580c;
|
||||
--color-concept-bg: #fff7ed;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 5%);
|
||||
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 10%);
|
||||
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 10%);
|
||||
|
||||
/* Transitions */
|
||||
--transition-fast: all 0.2s ease;
|
||||
--transition-medium: all 0.3s ease;
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
--color-bg: #0f172a;
|
||||
--color-bg-secondary: #1e293b;
|
||||
--color-bg-tertiary: #334155;
|
||||
--color-text: #f1f5f9;
|
||||
--color-text-secondary: #94a3b8;
|
||||
--color-border: #475569;
|
||||
--color-primary: #3b82f6;
|
||||
--color-primary-hover: #60a5fa;
|
||||
--color-accent: #10b981;
|
||||
--color-accent-hover: #34d399;
|
||||
--color-warning: #f59e0b;
|
||||
--color-error: #f87171;
|
||||
|
||||
--color-hosted: #a855f7;
|
||||
--color-hosted-bg: #2e1065;
|
||||
--color-oss: #10b981;
|
||||
--color-oss-bg: #064e3b;
|
||||
--color-method: #0891b2;
|
||||
--color-method-bg: #164e63;
|
||||
--color-concept: #f97316;
|
||||
--color-concept-bg: #7c2d12;
|
||||
|
||||
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 30%);
|
||||
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 40%);
|
||||
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 50%);
|
||||
}
|
||||
|
||||
/* ===================================================================
|
||||
3. BASE HTML ELEMENTS
|
||||
@ -3746,133 +3681,30 @@ footer {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ===================================================================
|
||||
MIGRATION UTILITIES - Additional classes for inline style migration
|
||||
================================================================= */
|
||||
|
||||
/* Alignment utilities */
|
||||
.align-middle { vertical-align: middle; }
|
||||
|
||||
/* Font style utilities */
|
||||
.italic { font-style: italic; }
|
||||
|
||||
/* Border width utilities */
|
||||
.border-2 { border-width: 2px; }
|
||||
|
||||
/* Border color utilities */
|
||||
.border-accent { border-color: var(--color-accent); }
|
||||
|
||||
/* Card variants for complex backgrounds */
|
||||
.card-warning {
|
||||
background: linear-gradient(135deg, var(--color-warning) 0%, var(--color-accent) 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Header variants for complex gradient combinations */
|
||||
.header-primary {
|
||||
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-accent) 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Ensure we have all text size variants */
|
||||
.text-2xl { font-size: 1.5rem; }
|
||||
|
||||
/* Additional spacing utilities that might be missing */
|
||||
.py-8 { padding-top: 2rem; padding-bottom: 2rem; }
|
||||
.px-4 { padding-left: 1rem; padding-right: 1rem; }
|
||||
.my-6 { margin-top: 1.5rem; margin-bottom: 1.5rem; }
|
||||
|
||||
/* Flex utilities that might be missing */
|
||||
.flex-1 { flex: 1; }
|
||||
|
||||
/* Additional rounded variants */
|
||||
.rounded-xl { border-radius: 0.75rem; }
|
||||
|
||||
/* ===================================================================
|
||||
23. MARKDOWN CONTENT
|
||||
================================================================= */
|
||||
|
||||
.markdown-content h1,
|
||||
.markdown-content h2,
|
||||
.markdown-content h3,
|
||||
.markdown-content h4,
|
||||
.markdown-content h5,
|
||||
.markdown-content h6 {
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.markdown-content h1:first-child,
|
||||
.markdown-content h2:first-child,
|
||||
.markdown-content h3:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.markdown-content p {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.markdown-content ul,
|
||||
.markdown-content ol {
|
||||
margin-bottom: 1rem;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.markdown-content li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.markdown-content pre {
|
||||
background-color: var(--color-bg-tertiary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
margin: 1.5rem 0;
|
||||
overflow-x: auto;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.markdown-content code:not(pre code) {
|
||||
background-color: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.125rem 0.375rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.markdown-content table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 1.5rem 0;
|
||||
background-color: var(--color-bg);
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.markdown-content th,
|
||||
.markdown-content td {
|
||||
padding: 0.75rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.markdown-content th {
|
||||
background-color: var(--color-bg-secondary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown-content blockquote {
|
||||
border-left: 4px solid var(--color-primary);
|
||||
background-color: var(--color-bg-secondary);
|
||||
padding: 1rem 1.5rem;
|
||||
margin: 1.5rem 0;
|
||||
border-radius: 0 0.5rem 0.5rem 0;
|
||||
}
|
||||
|
||||
.markdown-content hr {
|
||||
border: none;
|
||||
border-top: 1px solid var(--color-border);
|
||||
margin: 2rem 0;
|
||||
}
|
661
src/styles/knowledgebase.css
Normal file
661
src/styles/knowledgebase.css
Normal file
@ -0,0 +1,661 @@
|
||||
/* ==========================================================================
|
||||
0) FOUNDATIONS
|
||||
- Typography, rhythm, utility tokens, base elements
|
||||
========================================================================== */
|
||||
|
||||
:root {
|
||||
/* Optional: gentle defaults if not already defined upstream */
|
||||
--radius-xs: 0.25rem;
|
||||
--radius-sm: 0.375rem;
|
||||
--radius-md: 0.5rem;
|
||||
--radius-lg: 0.75rem;
|
||||
--radius-xl: 1rem;
|
||||
|
||||
--shadow-xs: 0 1px 2px rgba(0,0,0,0.06);
|
||||
--shadow-sm: 0 2px 8px rgba(0,0,0,0.06);
|
||||
--shadow-md: 0 6px 18px rgba(0,0,0,0.10);
|
||||
--shadow-lg: 0 12px 30px rgba(0,0,0,0.16);
|
||||
|
||||
--ease-quick: 0.2s ease;
|
||||
}
|
||||
|
||||
/* Base text container for articles */
|
||||
:where(.markdown-content) {
|
||||
max-width: 70ch;
|
||||
margin: 0 auto;
|
||||
font-size: 1.125rem;
|
||||
line-height: 1.8;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
:where(.markdown-content) * {
|
||||
scroll-margin-top: 100px;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
1) HEADINGS & TEXT
|
||||
========================================================================== */
|
||||
|
||||
:where(.markdown-content) h1,
|
||||
:where(.markdown-content) h2,
|
||||
:where(.markdown-content) h3,
|
||||
:where(.markdown-content) h4,
|
||||
:where(.markdown-content) h5,
|
||||
:where(.markdown-content) h6 {
|
||||
font-weight: 700;
|
||||
line-height: 1.25;
|
||||
color: var(--color-text);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:where(.markdown-content) h1 {
|
||||
font-size: 2.5rem;
|
||||
margin: 3rem 0 1.5rem 0;
|
||||
border-bottom: 3px solid var(--color-primary);
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
:where(.markdown-content) h2 {
|
||||
font-size: 2rem;
|
||||
margin: 2.5rem 0 1rem 0;
|
||||
border-bottom: 2px solid var(--color-border);
|
||||
padding-bottom: 0.375rem;
|
||||
}
|
||||
|
||||
:where(.markdown-content) h3 {
|
||||
font-size: 1.5rem;
|
||||
margin: 2rem 0 0.75rem 0;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
:where(.markdown-content) h4 {
|
||||
font-size: 1.25rem;
|
||||
margin: 1.5rem 0 0.5rem 0;
|
||||
}
|
||||
|
||||
:where(.markdown-content) h5,
|
||||
:where(.markdown-content) h6 {
|
||||
font-size: 1.125rem;
|
||||
margin: 1rem 0 0.5rem 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
:where(.markdown-content) p {
|
||||
margin: 0 0 1.5rem 0;
|
||||
text-align: justify;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
:where(.markdown-content) strong { font-weight: 700; color: var(--color-text); }
|
||||
:where(.markdown-content) em { font-style: italic; color: var(--color-text-secondary); }
|
||||
|
||||
/* Links with refined hover state */
|
||||
:where(.markdown-content) a {
|
||||
color: var(--color-primary);
|
||||
text-decoration: underline;
|
||||
text-decoration-color: transparent;
|
||||
transition: var(--ease-quick);
|
||||
}
|
||||
:where(.markdown-content) a:hover {
|
||||
text-decoration-color: var(--color-primary);
|
||||
background-color: var(--color-bg-secondary);
|
||||
padding: 0.125rem 0.25rem;
|
||||
border-radius: var(--radius-xs);
|
||||
}
|
||||
/* External link indicator */
|
||||
:where(.markdown-content) a[href^="http"]:not([href*="localhost"]):not([href*="127.0.0.1"])::after {
|
||||
content: " ↗";
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
2) LISTS, DEFINITIONS, TABLES, QUOTES
|
||||
========================================================================== */
|
||||
|
||||
:where(.markdown-content) ul,
|
||||
:where(.markdown-content) ol {
|
||||
margin: 1.5rem 0;
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
:where(.markdown-content) li {
|
||||
margin-bottom: 0.75rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
:where(.markdown-content) li > ul,
|
||||
:where(.markdown-content) li > ol {
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
:where(.markdown-content) dl { margin: 1.5rem 0; }
|
||||
:where(.markdown-content) dt {
|
||||
font-weight: 700;
|
||||
margin: 1rem 0 0.5rem 0;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
:where(.markdown-content) dd {
|
||||
margin-left: 1.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
padding-left: 1rem;
|
||||
border-left: 2px solid var(--color-border);
|
||||
}
|
||||
|
||||
:where(.markdown-content) table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 1.5rem 0;
|
||||
background-color: var(--color-bg);
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
:where(.markdown-content) th,
|
||||
:where(.markdown-content) td {
|
||||
padding: 0.75rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
:where(.markdown-content) th {
|
||||
background-color: var(--color-bg-secondary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
:where(.markdown-content) blockquote {
|
||||
border-left: 4px solid var(--color-primary);
|
||||
background-color: var(--color-bg-secondary);
|
||||
padding: 1rem 1.5rem;
|
||||
margin: 1.5rem 0;
|
||||
border-radius: 0 var(--radius-md) var(--radius-md) 0;
|
||||
}
|
||||
|
||||
:where(.markdown-content) hr {
|
||||
border: none;
|
||||
border-top: 1px solid var(--color-border);
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
3) CODE & INLINE CODE
|
||||
========================================================================== */
|
||||
|
||||
:where(.markdown-content) pre {
|
||||
background-color: var(--color-bg-tertiary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.75rem;
|
||||
padding: 1.5rem;
|
||||
margin: 2rem 0;
|
||||
overflow-x: auto;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.6;
|
||||
position: relative;
|
||||
box-shadow: var(--shadow-sm);
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
:where(.markdown-content) pre::before {
|
||||
content: attr(data-language);
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
left: 0.75rem;
|
||||
right: auto;
|
||||
z-index: 1;
|
||||
display: inline-block;
|
||||
padding: 0.15rem 0.5rem;
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-bg);
|
||||
border: 1px solid var(--color-border);
|
||||
color: var(--color-text-secondary);
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.05em;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
:where(.markdown-content) pre[data-language]::before {
|
||||
top: 0.5rem;
|
||||
left: 0.75rem;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
display: inline-block;
|
||||
padding: 0.15rem 0.5rem;
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-bg);
|
||||
border: 1px solid var(--color-border);
|
||||
color: var(--color-text-secondary);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
:where(.markdown-content) code:not(pre code) {
|
||||
background-color: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
font-family: 'SF Mono','Monaco','Menlo','Consolas',monospace;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
/* Language badges (keep if your renderer sets data-language) */
|
||||
:where(.markdown-content) pre[data-language="bash"]::before { content: "BASH"; color: #4EAA25; }
|
||||
:where(.markdown-content) pre[data-language="javascript"]::before { content: "JS"; color: #F7DF1E; }
|
||||
:where(.markdown-content) pre[data-language="python"]::before { content: "PYTHON"; color: #3776AB; }
|
||||
:where(.markdown-content) pre[data-language="sql"]::before { content: "SQL"; color: #336791; }
|
||||
:where(.markdown-content) pre[data-language="yaml"]::before { content: "YAML"; color: #CB171E; }
|
||||
:where(.markdown-content) pre[data-language="json"]::before { content: "JSON"; color: #000000; }
|
||||
|
||||
/* Keyboard UI hints */
|
||||
:where(.markdown-content) kbd {
|
||||
background-color: var(--color-bg-tertiary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-xs);
|
||||
padding: 0.125rem 0.375rem;
|
||||
font-size: 0.75rem;
|
||||
font-family: monospace;
|
||||
box-shadow: var(--shadow-xs);
|
||||
margin: 0 0.125rem;
|
||||
}
|
||||
|
||||
:where(.markdown-content) .copy-btn {
|
||||
position: absolute;
|
||||
top: 0.5rem; right: 0.5rem;
|
||||
display: inline-flex; align-items: center; gap: 0.4rem;
|
||||
padding: 0.35rem 0.6rem;
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-bg);
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 0.75rem;
|
||||
cursor: pointer;
|
||||
box-shadow: var(--shadow-sm);
|
||||
line-height: 1;
|
||||
z-index: 2;
|
||||
transition: var(--transition-fast);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
:where(.markdown-content) .copy-btn:hover {
|
||||
background: var(--color-bg-secondary);
|
||||
}
|
||||
:where(.markdown-content) .copy-btn:focus-visible {
|
||||
outline: 2px solid color-mix(in srgb, var(--color-primary) 60%, transparent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
:where(.markdown-content) .copy-btn svg {
|
||||
width: 14px; height: 14px;
|
||||
}
|
||||
|
||||
[data-theme="dark"] :where(.markdown-content) .copy-btn {
|
||||
background: var(--color-bg-secondary);
|
||||
border-color: color-mix(in srgb, var(--color-border) 80%, transparent);
|
||||
box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-bg-tertiary) 40%, transparent), var(--shadow-sm);
|
||||
}
|
||||
|
||||
:where(.markdown-content) pre .copied-pill {
|
||||
position: absolute;
|
||||
top: 0.5rem; right: 0.5rem;
|
||||
padding: 0.35rem 0.6rem;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
opacity: 0; transform: translateY(-6px) scale(0.98);
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
|
||||
/* Success state driven by [data-copied="true"] on <pre> */
|
||||
:where(.markdown-content) pre[data-copied="true"] {
|
||||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-primary) 30%, transparent) inset, var(--shadow-sm);
|
||||
}
|
||||
:where(.markdown-content) pre[data-copied="true"] .copy-btn { opacity: 0; }
|
||||
:where(.markdown-content) pre[data-copied="true"] .copied-pill {
|
||||
opacity: 1; transform: translateY(0) scale(1);
|
||||
}
|
||||
|
||||
/* Smooth but gentle motion */
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
:where(.markdown-content) pre,
|
||||
:where(.markdown-content) .copy-btn,
|
||||
:where(.markdown-content) pre .copied-pill {
|
||||
transition: 180ms ease;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
4) CALLOUTS, STEPS, HIGHLIGHT BOX
|
||||
========================================================================== */
|
||||
|
||||
:where(.markdown-content) .callout {
|
||||
border-left: 4px solid;
|
||||
border-radius: 0.5rem;
|
||||
margin: 2rem 0;
|
||||
padding: 1.25rem 1.5rem;
|
||||
background-color: var(--color-bg-secondary);
|
||||
border-color: var(--color-border);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
:where(.markdown-content) .callout-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
font-weight: 600;
|
||||
font-size: 0.9375rem;
|
||||
}
|
||||
|
||||
:where(.markdown-content) .callout-icon {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Callout variants */
|
||||
:where(.markdown-content) .callout-note {
|
||||
border-left-color: var(--color-primary);
|
||||
background-color: color-mix(in srgb, var(--color-primary) 8%, var(--color-bg-secondary));
|
||||
}
|
||||
:where(.markdown-content) .callout-note .callout-header { color: var(--color-primary); }
|
||||
|
||||
:where(.markdown-content) .callout-tip {
|
||||
border-left-color: var(--color-accent);
|
||||
background-color: color-mix(in srgb, var(--color-accent) 8%, var(--color-bg-secondary));
|
||||
}
|
||||
:where(.markdown-content) .callout-tip .callout-header { color: var(--color-accent); }
|
||||
|
||||
:where(.markdown-content) .callout-warning {
|
||||
border-left-color: var(--color-warning);
|
||||
background-color: color-mix(in srgb, var(--color-warning) 8%, var(--color-bg-secondary));
|
||||
}
|
||||
:where(.markdown-content) .callout-warning .callout-header { color: var(--color-warning); }
|
||||
|
||||
:where(.markdown-content) .callout-danger {
|
||||
border-left-color: var(--color-error);
|
||||
background-color: color-mix(in srgb, var(--color-error) 8%, var(--color-bg-secondary));
|
||||
}
|
||||
:where(.markdown-content) .callout-danger .callout-header { color: var(--color-error); }
|
||||
|
||||
/* Highlight feature box */
|
||||
:where(.markdown-content) .highlight-box {
|
||||
background: linear-gradient(135deg, var(--color-accent) 0%, var(--color-primary) 100%);
|
||||
color: white;
|
||||
padding: 2rem;
|
||||
border-radius: 1rem;
|
||||
margin: 2rem 0;
|
||||
text-align: center;
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
:where(.markdown-content) .highlight-box h3 { margin: 0; color: white; }
|
||||
|
||||
/* Steps */
|
||||
:where(.markdown-content) .steps { counter-reset: step-counter; margin: 2rem 0; }
|
||||
:where(.markdown-content) .step {
|
||||
counter-increment: step-counter;
|
||||
margin: 1.25rem 0;
|
||||
padding: 1.25rem 1.5rem;
|
||||
background-color: var(--color-bg-secondary);
|
||||
border-radius: 0.75rem;
|
||||
border-left: 4px solid var(--color-primary);
|
||||
position: relative;
|
||||
}
|
||||
:where(.markdown-content) .step::before {
|
||||
content: counter(step-counter);
|
||||
position: absolute;
|
||||
left: -0.75rem;
|
||||
top: 1rem;
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
border-radius: 50%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-weight: 700;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
:where(.markdown-content) .step h4 { margin-top: 0; color: var(--color-primary); }
|
||||
|
||||
/* ==========================================================================
|
||||
5) ARTICLE LAYOUT (Header, Meta, Sidebar, Footer)
|
||||
========================================================================== */
|
||||
|
||||
.article-layout {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.article-header {
|
||||
background: linear-gradient(135deg, var(--color-bg-secondary) 0%, var(--color-bg-tertiary) 100%);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
.article-header-content { padding: 2rem; }
|
||||
|
||||
.breadcrumb {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.breadcrumb-link {
|
||||
color: var(--color-primary);
|
||||
text-decoration: none;
|
||||
transition: var(--ease-quick);
|
||||
}
|
||||
.breadcrumb-link:hover { text-decoration: underline; }
|
||||
.breadcrumb-separator { color: var(--color-text-secondary); }
|
||||
.breadcrumb-current { color: var(--color-text-secondary); font-weight: 500; }
|
||||
|
||||
.article-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.article-tag {
|
||||
padding: 0.25rem 0.75rem;
|
||||
background-color: var(--color-bg);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 1rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
.article-tag-category {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.article-title-section { margin-bottom: 2rem; }
|
||||
.article-title {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 1rem 0;
|
||||
color: var(--color-primary);
|
||||
line-height: 1.2;
|
||||
}
|
||||
.article-icon { margin-right: 1rem; font-size: 2rem; }
|
||||
.article-description { font-size: 1.25rem; color: var(--color-text-secondary); margin: 0; line-height: 1.5; }
|
||||
|
||||
.article-metadata-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
padding: 1.5rem;
|
||||
background-color: var(--color-bg);
|
||||
border-radius: 0.75rem;
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
.metadata-item { display: flex; flex-direction: column; gap: 0.5rem; }
|
||||
.metadata-label {
|
||||
font-size: 0.75rem; font-weight: 600; text-transform: uppercase;
|
||||
letter-spacing: 0.05em; color: var(--color-text-secondary);
|
||||
}
|
||||
.metadata-value { font-size: 0.9375rem; font-weight: 500; color: var(--color-text); }
|
||||
.metadata-badges { display: flex; flex-wrap: wrap; gap: 0.5rem; }
|
||||
|
||||
.article-actions { display: flex; gap: 1rem; justify-content: center; }
|
||||
|
||||
/* Content wrapper with sidebar */
|
||||
.article-content-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: 280px 1fr;
|
||||
gap: 3rem;
|
||||
align-items: start;
|
||||
}
|
||||
.article-sidebar {
|
||||
position: sticky;
|
||||
top: 6rem;
|
||||
max-height: calc(100vh - 8rem);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Sidebar TOC */
|
||||
.sidebar-toc {
|
||||
background-color: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.75rem;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
.toc-title {
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
.toc-navigation { display: flex; flex-direction: column; gap: 0.25rem; }
|
||||
.toc-item {
|
||||
display: block;
|
||||
padding: 0.5rem 0.75rem;
|
||||
color: var(--color-text);
|
||||
text-decoration: none;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.4;
|
||||
transition: var(--ease-quick);
|
||||
border-left: 3px solid transparent;
|
||||
}
|
||||
.toc-item:hover {
|
||||
background-color: var(--color-bg-tertiary);
|
||||
color: var(--color-primary);
|
||||
border-left-color: var(--color-primary);
|
||||
}
|
||||
.toc-item.active {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
border-left-color: var(--color-accent);
|
||||
}
|
||||
|
||||
/* Indentation by level (JS sets .toc-level-X) */
|
||||
.toc-level-1 { padding-left: 0.75rem; font-weight: 600; }
|
||||
.toc-level-2 { padding-left: 1rem; font-weight: 500; }
|
||||
.toc-level-3 { padding-left: 1.5rem; }
|
||||
.toc-level-4 { padding-left: 2rem; font-size: 0.8125rem; }
|
||||
.toc-level-5 { padding-left: 2.5rem; font-size: 0.8125rem; opacity: 0.85; }
|
||||
.toc-level-6 { padding-left: 3rem; font-size: 0.8125rem; opacity: 0.8; }
|
||||
|
||||
/* Main article column */
|
||||
.article-main { min-width: 0; max-width: 95ch; }
|
||||
.article-content { margin-bottom: 3rem; }
|
||||
|
||||
/* Footer */
|
||||
.article-footer {
|
||||
border-top: 2px solid var(--color-border);
|
||||
padding-top: 2rem;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
.article-footer h3 { margin: 0 0 1.5rem 0; color: var(--color-primary); }
|
||||
.footer-actions-grid { display: flex; flex-wrap: wrap; gap: 1rem; margin-bottom: 2rem; }
|
||||
.footer-actions-grid .btn { flex: 1; min-width: 200px; }
|
||||
|
||||
/* Tool button variants (keep since template uses them) */
|
||||
.btn-concept { background-color: var(--color-concept); color: white; border-color: var(--color-concept); }
|
||||
.btn-concept:hover { opacity: 0.9; }
|
||||
.btn-method { background-color: var(--color-method); color: white; border-color: var(--color-method); }
|
||||
.btn-method:hover { opacity: 0.9; }
|
||||
|
||||
.related-tools { margin-top: 2rem; padding-top: 2rem; border-top: 1px solid var(--color-border); }
|
||||
.related-tools h3 { margin: 0 0 1rem 0; color: var(--color-text); }
|
||||
.related-tools-grid {
|
||||
display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 1rem;
|
||||
}
|
||||
.related-tool-card {
|
||||
display: flex; align-items: center; gap: 0.75rem; padding: 1rem;
|
||||
background-color: var(--color-bg-secondary); border: 1px solid var(--color-border);
|
||||
border-radius: 0.5rem; color: var(--color-text); text-decoration: none;
|
||||
transition: var(--ease-quick);
|
||||
}
|
||||
.related-tool-card:hover {
|
||||
background-color: var(--color-bg-tertiary); border-color: var(--color-primary);
|
||||
transform: translateY(-1px); box-shadow: var(--shadow-sm);
|
||||
}
|
||||
.tool-icon { font-size: 1.25rem; flex-shrink: 0; }
|
||||
.tool-name { font-weight: 500; font-size: 0.9375rem; }
|
||||
|
||||
/* ==========================================================================
|
||||
6) RESPONSIVE
|
||||
========================================================================== */
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.article-content-wrapper { grid-template-columns: 240px 1fr; gap: 2rem; }
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.article-content-wrapper { grid-template-columns: 1fr; gap: 0; }
|
||||
.article-sidebar { position: static; max-height: none; margin-bottom: 2rem; }
|
||||
.sidebar-toc { max-width: 500px; margin: 0 auto; }
|
||||
.article-main { max-width: 100%; }
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.article-layout { padding: 0 0.5rem; }
|
||||
.article-header-content { padding: 1.5rem; }
|
||||
.article-title { font-size: 2rem; }
|
||||
.article-description { font-size: 1.125rem; }
|
||||
.article-metadata-grid { grid-template-columns: 1fr; gap: 1rem; padding: 1rem; }
|
||||
.article-actions { flex-direction: column; }
|
||||
.footer-actions-grid { flex-direction: column; }
|
||||
.footer-actions-grid .btn { min-width: auto; }
|
||||
|
||||
:where(.markdown-content) {
|
||||
font-size: 1rem;
|
||||
line-height: 1.65;
|
||||
max-width: 100%;
|
||||
}
|
||||
:where(.markdown-content) pre { padding: 1rem; margin: 1.5rem 0; font-size: 0.8125rem; }
|
||||
:where(.markdown-content) .callout { padding: 1rem; margin: 1.5rem 0; }
|
||||
:where(.markdown-content) .step { padding: 1rem; margin: 1rem 0; }
|
||||
:where(.markdown-content) .step::before { left: -0.5rem; top: 0.75rem; }
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.article-title { font-size: 1.75rem; }
|
||||
.article-icon { font-size: 1.5rem; margin-right: 0.75rem; }
|
||||
.breadcrumb { font-size: 0.8125rem; }
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
7) PRINT
|
||||
========================================================================== */
|
||||
|
||||
@media print {
|
||||
/* Hide interactive chrome not used in print */
|
||||
.article-sidebar { display: none !important; }
|
||||
.article-actions,
|
||||
.article-footer .footer-actions-grid { display: none !important; }
|
||||
|
||||
/* Expand content */
|
||||
.article-main { max-width: 100% !important; }
|
||||
}
|
62
src/styles/palette.css
Normal file
62
src/styles/palette.css
Normal file
@ -0,0 +1,62 @@
|
||||
:root {
|
||||
/* Light Theme Colors */
|
||||
--color-bg: #fff;
|
||||
--color-bg-secondary: #f8fafc;
|
||||
--color-bg-tertiary: #e2e8f0;
|
||||
--color-text: #1e293b;
|
||||
--color-text-secondary: #64748b;
|
||||
--color-border: #cbd5e1;
|
||||
--color-primary: #2563eb;
|
||||
--color-primary-hover: #1d4ed8;
|
||||
--color-accent: #059669;
|
||||
--color-accent-hover: #047857;
|
||||
--color-warning: #d97706;
|
||||
--color-error: #dc2626;
|
||||
|
||||
/* Enhanced card type colors */
|
||||
--color-hosted: #7c3aed;
|
||||
--color-hosted-bg: #f3f0ff;
|
||||
--color-oss: #059669;
|
||||
--color-oss-bg: #ecfdf5;
|
||||
--color-method: #0891b2;
|
||||
--color-method-bg: #f0f9ff;
|
||||
--color-concept: #ea580c;
|
||||
--color-concept-bg: #fff7ed;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 5%);
|
||||
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 10%);
|
||||
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 10%);
|
||||
|
||||
/* Transitions */
|
||||
--transition-fast: all 0.2s ease;
|
||||
--transition-medium: all 0.3s ease;
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
--color-bg: #0f172a;
|
||||
--color-bg-secondary: #1e293b;
|
||||
--color-bg-tertiary: #334155;
|
||||
--color-text: #f1f5f9;
|
||||
--color-text-secondary: #94a3b8;
|
||||
--color-border: #475569;
|
||||
--color-primary: #3b82f6;
|
||||
--color-primary-hover: #60a5fa;
|
||||
--color-accent: #10b981;
|
||||
--color-accent-hover: #34d399;
|
||||
--color-warning: #f59e0b;
|
||||
--color-error: #f87171;
|
||||
|
||||
--color-hosted: #a855f7;
|
||||
--color-hosted-bg: #2e1065;
|
||||
--color-oss: #10b981;
|
||||
--color-oss-bg: #064e3b;
|
||||
--color-method: #0891b2;
|
||||
--color-method-bg: #164e63;
|
||||
--color-concept: #f97316;
|
||||
--color-concept-bg: #7c2d12;
|
||||
|
||||
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 30%);
|
||||
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 40%);
|
||||
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 50%);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user