ShyyTAUI/src/routes/documentation/+page.svelte
2025-10-18 18:31:15 +02:00

1099 lines
No EOL
37 KiB
Svelte

<script lang="ts">
import { onMount } from 'svelte';
import { fly, fade, slide } from 'svelte/transition';
import { marked } from 'marked';
import 'prismjs';
import 'prismjs/components/prism-typescript';
import 'prismjs/components/prism-javascript';
import 'prismjs/components/prism-css';
import 'prismjs/components/prism-json';
import 'prismjs/components/prism-bash';
import 'prismjs/components/prism-markdown';
// Documentation structure - for now this is left as is until I finish writing the rest of the docs, this is just placeholder for structure and what can be done
let docStructure = [
{
category: "Getting Started",
icon: "rocket_launch",
items: [
{ title: "Introduction", file: "1-intro" },
{ title: "Installation", file: "1-installation" },
{ title: "Familiarisation, States", file: "1-fam-states-intro" },
{ title: "How TA Works, Info", file: "1-taInfo" },
]
},
{
category: "State Manager",
icon: "account_tree",
items: [
{ title: "Introduction to the StateManager", file: "2-stateManager-intro" },
{ title: "All Functions", file: "2-stateManager-allFunctions" },
{ title: "Emit", file: "2-stateManager-emit" },
{ title: "GetKnownServers", file: "2-stateManager-getKnownServers" },
{ title: "GetMatch", file: "2-stateManager-getMatch" },
{ title: "GetMatches", file: "2-stateManager-getMatches" },
{ title: "GetQualifier", file: "2-stateManager-getQualifier" },
{ title: "GetQualifiers", file: "2-stateManager-getQualifiers" },
{ title: "GetSelfGuid", file: "2-stateManager-getSelfGuid" },
{ title: "GetTournament", file: "2-stateManager-getTournament" },
{ title: "GetTournaments", file: "2-stateManager-getTournaments" },
{ title: "GetUser", file: "2-stateManager-getUser" },
{ title: "GetUsers", file: "2-stateManager-getUsers" },
{ title: "HandlePacket", file: "2-stateManager-handlePacket" },
{ title: "On", file: "2-stateManager-on" },
{ title: "Once", file: "2-stateManager-once" },
{ title: "RemoveListener", file: "2-stateManager-removeListener" },
]
},
{
category: "Client",
icon: "api",
items: [
{ title: "Introduction to the Client", file: "3-client-intro" },
{ title: "All Functions", file: "3-client-allFunctions" },
{ title: "AddAuthorizedUser", file: "3-client-addAuthorizedUser" },
{ title: "AddQualifierMaps", file: "3-client-addQualifierMaps" },
{ title: "AddServer", file: "3-client-addServer" },
{ title: "AddTournamentPool", file: "3-client-addTournamentPool" },
{ title: "AddTournamentPoolMaps", file: "3-client-addTournamentPoolMaps" },
{ title: "AddTournamentRole", file: "3-client-addTournamentRole" },
{ title: "AddTournamentTeam", file: "3-client-addTournamentTeam" },
{ title: "AddUserToMatch", file: "3-client-addUserToMatch" },
{ title: "Connect", file: "3-client-connect" },
{ title: "CreateMatch", file: "3-client-createMatch" },
{ title: "CreateQualifierEvent", file: "3-client-createQualifierEvent" },
{ title: "CreateTournament", file: "3-client-createTournament" },
{ title: "DelayTestFinished", file: "3-client-delayTestFinished" },
{ title: "DeleteMatch", file: "3-client-deleteMatch" },
{ title: "DeleteQualifierEvent", file: "3-client-deleteQualifierEvent" },
{ title: "DeleteTournament", file: "3-client-deleteTournament" },
{ title: "Disconnect", file: "3-client-disconnect" },
{ title: "Emit", file: "3-client-emit" },
{ title: "FlipColors", file: "3-client-flipColors" },
{ title: "FlipHands", file: "3-client-flipHands" },
{ title: "GenerateBotToken", file: "3-client-generateBotToken" },
{ title: "GetAuthorizedUsers", file: "3-client-getAuthorizedUsers" },
{ title: "GetBotTokensForUser", file: "3-client-getBotTokensForUser" },
{ title: "GetDiscordInfo", file: "3-client-getDiscordInfo" },
{ title: "GetLeaderboard", file: "3-client-getLeaderboard" },
{ title: "IsConnected", file: "3-client-isConnected" },
{ title: "IsConnecting", file: "3-client-isConnecting" },
{ title: "JoinTournament", file: "3-client-joinTournament" },
{ title: "LoadImage", file: "3-client-loadImage" },
{ title: "LoadSong", file: "3-client-loadSong" },
{ title: "On", file: "3-client-on" },
{ title: "Once", file: "3-client-once" },
{ title: "PlaySong", file: "3-client-playSong" },
{ title: "RemoveAuthorizedUser", file: "3-client-removeAuthorizedUser" },
{ title: "RemoveListener", file: "3-client-removeListener" },
{ title: "RemoveQualifierMap", file: "3-client-removeQualifierMap" },
{ title: "RemoveTournamentPool", file: "3-client-removeTournamentPool" },
{ title: "RemoveTournamentPoolMap", file: "3-client-removeTournamentPoolMap" },
{ title: "RemoveTournamentRole", file: "3-client-removeTournamentRole" },
{ title: "RemoveTournamentTeam", file: "3-client-removeTournamentTeam" },
{ title: "RemoveUserFromMatch", file: "3-client-removeUserFromMatch" },
{ title: "ReturnToMenu", file: "3-client-returnToMenu" },
{ title: "RevokeBotToken", file: "3-client-revokeBotToken" },
{ title: "SendResponse", file: "3-client-sendResponse" },
{ title: "SetAuthToken", file: "3-client-setAuthToken" },
{ title: "SetMatchLeader", file: "3-client-setMatchLeader" },
{ title: "SetMatchMap", file: "3-client-setMatchMap" },
{ title: "SetQualifierFlags", file: "3-client-setQualifierFlags" },
{ title: "SetQualifierImage", file: "3-client-setQualifierImage" },
{ title: "SetQualifierInfoChannel", file: "3-client-setQualifierInfoChannel" },
{ title: "SetQualifierLeaderboardSort", file: "3-client-setQualifierLeaderboardSort" },
{ title: "SetQualifierName", file: "3-client-setQualifierName" },
{ title: "SetTournamentAllowUnauthorizedView", file: "3-client-setTournamentAllowUnauthorizedView" },
{ title: "SetTournamentBannedMods", file: "3-client-setTournamentBannedMods" },
{ title: "SetTournamentEnablePools", file: "3-client-setTournamentEnablePools" },
{ title: "SetTournamentEnableTeams", file: "3-client-setTournamentEnableTeams" },
{ title: "SetTournamentImage", file: "3-client-setTournamentImage" },
{ title: "SetTournamentName", file: "3-client-setTournamentName" },
{ title: "SetTournamentPoolName", file: "3-client-setTournamentPoolName" },
{ title: "SetTournamentRoleName", file: "3-client-setTournamentRoleName" },
{ title: "SetTournamentRolePermissions", file: "3-client-setTournamentRolePermissions" },
{ title: "SetTournamentScoreUpdateFrequency", file: "3-client-setTournamentScoreUpdateFrequency" },
{ title: "SetTournamentShowQualifierButton", file: "3-client-setTournamentShowQualifierButton" },
{ title: "SetTournamentShowTournamentButton", file: "3-client-setTournamentShowTournamentButton" },
{ title: "SetTournamentTeamImage", file: "3-client-setTournamentTeamImage" },
{ title: "SetTournamentTeamName", file: "3-client-setTournamentTeamName" },
{ title: "ShowLoadedImage", file: "3-client-showLoadedImage" },
{ title: "ShowPrompt", file: "3-client-showPrompt" },
{ title: "StateManager", file: "3-client-stateManager" },
{ title: "UpdateQualifierMap", file: "3-client-updateQualifierMap" },
{ title: "UpdateTournamentPoolMap", file: "3-client-updateTournamentPoolMap" },
{ title: "UpdateUser", file: "3-client-updateUser" }
]
},
{
category: "Models",
icon: "view_in_ar",
items: [
{ title: "4-", file: "" },
{ title: "Endpoints", file: "" },
{ title: "Response Types", file: "" }
]
},
{
category: "Enums",
icon: "view_in_ar",
items: [
{ title: "5-", file: "" },
{ title: "Endpoints", file: "" },
{ title: "Response Types", file: "" }
]
},
{
category: "Events",
icon: "view_in_ar",
items: [
{ title: "6-", file: "" },
{ title: "Endpoints", file: "" },
{ title: "Response Types", file: "" }
]
},
{
category: "Best Practices",
icon: "view_in_ar",
items: [
{ title: "7-", file: "" },
{ title: "Endpoints", file: "" },
{ title: "Response Types", file: "" }
]
}
];
// Flatten categories for URL handling
let allDocs = docStructure.flatMap(category =>
category.items.map(item => ({
...item,
category: category.category
}))
);
// State
let currentDoc: string = "";
let activeCategory: string = "";
let markdownContent: string = "";
let htmlContent: string = "";
let isLoading: boolean = true;
let isMobileMenuOpen: boolean = false;
let error: string | null = null;
// Handle hash changes
function handleHashChange() {
const hash = window.location.hash.slice(1); // Remove the # character
if (hash) {
const [category, doc] = hash.split("/");
if (category && doc) {
// Both category and doc are specified
activeCategory = decodeURIComponent(category);
currentDoc = decodeURIComponent(doc);
} else {
// Only category is specified, load first doc in that category
activeCategory = decodeURIComponent(hash);
const categoryData = docStructure.find(c => c.category === activeCategory);
if (categoryData && categoryData.items.length > 0) {
currentDoc = categoryData.items[0].file;
}
}
} else {
// No hash, load first doc of first category
if (docStructure.length > 0) {
activeCategory = docStructure[0].category;
currentDoc = docStructure[0].items[0].file;
}
}
loadMarkdownContent();
}
// Load markdown content
async function loadMarkdownContent() {
isLoading = true;
error = null;
try {
// Fetch using API from lib folder
const response = await fetch(`/api/docs/${currentDoc}.md`);
if (!response.ok) {
throw new Error(`Failed to load documentation: ${response.statusText}`);
}
markdownContent = await response.text();
htmlContent = await marked.parse(markdownContent);
} catch (err) {
console.error("Error loading markdown:", err);
error = "Could not load the requested documentation.";
htmlContent = "";
} finally {
isLoading = false;
}
}
// Toggle category expansion
function toggleCategory(category: string) {
if (activeCategory === category) {
activeCategory = "";
} else {
activeCategory = category;
// Find first doc in category and set it as current
const categoryData = docStructure.find(c => c.category === category);
if (categoryData && categoryData.items.length > 0) {
categoryData.items.sort((a, b) => categoryData.items.indexOf(a) - categoryData.items.indexOf(b));
currentDoc = categoryData.items[0].file;
window.location.hash = `${encodeURIComponent(category)}/${encodeURIComponent(currentDoc)}`;
}
}
}
// Select a specific doc
function selectDoc(category: string, doc: string) {
activeCategory = category;
currentDoc = doc;
window.location.hash = `${encodeURIComponent(category)}/${encodeURIComponent(doc)}`;
// On mobile, close the sidebar after selection
if (window.innerWidth < 768) {
isMobileMenuOpen = false;
}
}
// Toggle mobile menu
function toggleMobileMenu() {
isMobileMenuOpen = !isMobileMenuOpen;
}
function highlightCodeBlocks() {
// Make sure Prism is available
if (typeof window !== 'undefined' && (window as any).Prism) {
// Find all pre code elements and highlight them
const codeBlocks = document.querySelectorAll('pre code');
codeBlocks.forEach((block: Element) => {
// Get the language class if it exists (e.g., language-typescript)
const classes = block.classList;
let language = 'typescript'; // Default language
for (let i = 0; i < classes.length; i++) {
if (classes[i].startsWith('language-')) {
language = classes[i].replace('language-', '');
break;
}
}
// Add the language class if it doesn't exist
if (!block.classList.contains(`language-${language}`)) {
block.classList.add(`language-${language}`);
}
// Make sure the parent pre has the language class too
const pre = block.parentElement;
if (pre && !pre.classList.contains(`language-${language}`)) {
pre.classList.add(`language-${language}`);
}
// Highlight the code block
(window as any).Prism.highlightElement(block);
});
}
}
// Get current document index and navigation info
function getCurrentDocInfo(currentDocData: any) {
if (!currentDocData) return null;
const currentIndex = allDocs.findIndex(doc => doc.file === currentDoc);
return {
current: currentDocData,
index: currentIndex,
total: allDocs.length
};
}
// Navigate to previous document
function navigatePrevious() {
const docInfo = getCurrentDocInfo(allDocs.find(doc => doc.file === currentDoc));
if (!docInfo || docInfo.index <= 0) return;
const prevDoc = allDocs[docInfo.index - 1];
selectDoc(prevDoc.category, prevDoc.file);
}
// Navigate to next document
function navigateNext() {
const docInfo = getCurrentDocInfo(allDocs.find(doc => doc.file === currentDoc));
if (!docInfo || docInfo.index >= docInfo.total - 1) return;
const nextDoc = allDocs[docInfo.index + 1];
selectDoc(nextDoc.category, nextDoc.file);
}
// Get previous and next document info for button states
function getNavigationState(currentDocData: any) {
const docInfo = getCurrentDocInfo(currentDocData);
if (!docInfo) return { hasPrev: false, hasNext: false, prev: null, next: null };
const hasPrev = docInfo.index > 0;
const hasNext = docInfo.index < docInfo.total - 1;
return {
hasPrev,
hasNext,
prev: hasPrev ? allDocs[docInfo.index - 1] : null,
next: hasNext ? allDocs[docInfo.index + 1] : null
};
}
// Reactive statement to get navigation state
$: navigationState = getNavigationState(allDocs.find(doc => doc.file === currentDoc));
onMount(() => {
// Initialise content based on URL hash or load default
handleHashChange();
// Listen for hash changes
window.addEventListener('hashchange', handleHashChange);
return () => {
window.removeEventListener('hashchange', handleHashChange);
};
});
$: if (htmlContent && !isLoading) {
setTimeout(() => {
highlightCodeBlocks();
}, 0);
}
</script>
<svelte:head>
<title>Documentation | Tournament Assistant</title>
</svelte:head>
<div class="documentation-container">
<!-- Mobile menu toggle -->
<button class="mobile-menu-toggle" on:click={toggleMobileMenu}>
<span class="material-icons">{isMobileMenuOpen ? 'close' : 'menu'}</span>
<span>Documentation</span>
</button>
<!-- Sidebar -->
<aside class="sidebar" class:open={isMobileMenuOpen}>
<div class="sidebar-header">
<h2>Documentation</h2>
<button class="close-sidebar" on:click={toggleMobileMenu}>
<span class="material-icons">close</span>
</button>
</div>
<nav class="sidebar-nav">
{#each docStructure as category}
<div class="category">
<button
class="category-header"
class:active={activeCategory === category.category}
on:click={() => toggleCategory(category.category)}
>
<div class="category-header-content">
<span class="material-icons">{category.icon}</span>
<span>{category.category}</span>
</div>
<span class="material-icons">
{activeCategory === category.category ? 'expand_less' : 'expand_more'}
</span>
</button>
{#if activeCategory === category.category}
<!-- svelte-ignore missing-declaration -->
<div class="category-items" transition:slide={{duration: 200}}>
{#each category.items.sort((a, b) => category.items.indexOf(a) - category.items.indexOf(b)) as item}
<button
class="doc-link"
class:active={currentDoc === item.file}
on:click={() => selectDoc(category.category, item.file)}
>
<span>{item.title}</span>
</button>
{/each}
</div>
{/if}
</div>
{/each}
</nav>
</aside>
<!-- Main content -->
<main class="content">
{#if isLoading}
<div class="loading-container" in:fade={{duration: 200}}>
<div class="loading-spinner"></div>
<p>Loading documentation...</p>
</div>
{:else if error}
<div class="error-container" in:fly={{y: 20, duration: 300}}>
<span class="material-icons">error_outline</span>
<h3>Documentation Not Found</h3>
<p>{error}</p>
<button class="error-action-btn" on:click={handleHashChange}>
<span class="material-icons">refresh</span>
Try Again
</button>
</div>
{:else}
<div class="markdown-container" in:fly={{y: 20, duration: 300}}>
{@html htmlContent}
</div>
<!-- Navigation buttons -->
<div class="doc-navigation">
<button
class="nav-btn prev-btn"
class:disabled={!navigationState.hasPrev}
disabled={!navigationState.hasPrev}
on:click={navigatePrevious}
>
<span class="material-icons">arrow_back</span>
<div class="nav-btn-content">
<span class="nav-btn-label">Previous</span>
{#if navigationState.prev}
<span class="nav-btn-title">{navigationState.prev.title}</span>
{/if}
</div>
</button>
<button
class="nav-btn next-btn"
class:disabled={!navigationState.hasNext}
disabled={!navigationState.hasNext}
on:click={navigateNext}
>
<span class="material-icons">arrow_forward</span>
<div class="nav-btn-content">
<span class="nav-btn-label">Next</span>
{#if navigationState.next}
<span class="nav-btn-title">{navigationState.next.title}</span>
{/if}
</div>
</button>
</div>
{/if}
</main>
</div>
<style>
/* Documentation specific styles */
.documentation-container {
display: flex;
height: calc(100vh - var(--navbar-height));
background-color: var(--bg-primary);
position: relative;
}
/* Sidebar styles */
.sidebar {
width: 330px;
background-color: var(--bg-secondary);
border-right: 1px solid var(--border-color);
height: 100%;
overflow-y: auto;
transition: transform 0.3s ease;
z-index: 5;
border-radius: 1rem;
}
.sidebar-header {
padding: 1.5rem;
border-bottom: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
align-items: center;
}
.sidebar-header h2 {
margin: 0;
font-size: 1.25rem;
color: var(--accent-color);
}
.close-sidebar {
display: none;
background: none;
border: none;
color: var(--text-secondary);
cursor: pointer;
}
.sidebar-nav {
padding: 1rem 0;
border-radius: 1rem;
}
.category {
margin-bottom: 0.5rem;
}
.category-header {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem 1.5rem;
background: none;
border: none;
color: var(--text-primary);
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s ease;
text-align: left;
}
.category-header:hover {
background-color: var(--bg-tertiary);
}
.category-header.active {
background: linear-gradient(90deg, var(--accent-color) 0.5%, var(--bg-tertiary) 0.5%);
}
.category-header-content {
display: flex;
align-items: center;
gap: 0.75rem;
}
.category-items {
display: flex;
flex-direction: column;
padding-left: 2.5rem;
}
.doc-link {
padding: 0.5rem 1rem;
margin: 0.125rem 0;
background: none;
border: none;
color: var(--text-secondary);
font-size: 0.875rem;
text-align: left;
cursor: pointer;
border-radius: 0.25rem;
transition: all 0.2s ease;
}
.doc-link:hover {
background-color: var(--bg-tertiary);
color: var(--text-primary);
}
.doc-link.active {
background-color: rgba(79, 70, 229, 0.15);
color: var(--accent-color);
box-shadow: 0 0 8px rgba(79, 70, 229, 0.2);
}
/* Mobile menu toggle */
.mobile-menu-toggle {
display: none;
position: fixed;
top: calc(var(--navbar-height) + 1rem);
left: 1rem;
z-index: 10;
padding: 0.5rem 1rem;
background-color: var(--accent-color);
color: white;
border: none;
border-radius: 0.375rem;
font-weight: 500;
cursor: pointer;
box-shadow: 0 4px 12px rgba(79, 70, 229, 0.3);
align-items: center;
gap: 0.5rem;
}
/* Main content */
.content {
flex: 1;
padding: 2rem;
overflow-y: auto;
max-width: 100%;
}
/* Loading styles */
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
gap: 1rem;
color: var(--text-secondary);
}
.loading-spinner {
width: 2.5rem;
height: 2.5rem;
border: 0.25rem solid rgba(79, 70, 229, 0.3);
border-top: 0.25rem solid var(--accent-color);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Error styles */
.error-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 60%;
text-align: center;
padding: 2rem;
gap: 1rem;
color: var(--text-secondary);
}
.error-container .material-icons {
font-size: 3rem;
color: var(--danger-color);
}
.error-container h3 {
margin: 0;
font-size: 1.5rem;
color: var(--text-primary);
}
.error-action-btn {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background-color: var(--bg-tertiary);
color: var(--text-primary);
border: 1px solid var(--border-color);
border-radius: 0.375rem;
margin-top: 1rem;
cursor: pointer;
transition: all 0.2s ease;
}
.error-action-btn:hover {
background-color: var(--accent-color);
border-color: var(--accent-color);
box-shadow: 0 0 12px var(--accent-glow);
}
.doc-navigation {
display: flex;
justify-content: space-between;
gap: 1rem;
padding: 2rem;
border-top: 1px solid var(--border-color);
background-color: var(--bg-primary);
}
.nav-btn {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 1rem 1.5rem;
background-color: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: 0.5rem;
color: var(--text-primary);
cursor: pointer;
transition: all 0.2s ease;
font-family: inherit;
min-height: 4rem;
flex: 1;
max-width: auto;
}
.nav-btn:hover:not(.disabled) {
background-color: var(--accent-color);
border-color: var(--accent-color);
box-shadow: 0 0 12px var(--accent-glow);
color: white;
}
.nav-btn.disabled {
opacity: 0.5;
cursor: not-allowed;
background-color: var(--bg-tertiary);
border-color: var(--border-color);
}
.nav-btn-content {
display: flex;
flex-direction: column;
align-items: flex-start;
text-align: left;
}
.next-btn .nav-btn-content {
align-items: flex-end;
text-align: right;
}
.nav-btn-label {
font-size: 0.875rem;
font-weight: 500;
opacity: 0.8;
margin-bottom: 0.25rem;
}
.nav-btn-title {
font-size: 1rem;
font-weight: 600;
line-height: 1.2;
}
.prev-btn {
margin-right: auto;
}
.next-btn {
margin-left: auto;
flex-direction: row-reverse;
}
@media (max-width: 768px) {
.doc-navigation {
flex-direction: column;
gap: 0.75rem;
padding: 1rem;
}
.nav-btn {
max-width: none;
justify-content: center;
}
.nav-btn-content {
align-items: center;
text-align: center;
}
.next-btn .nav-btn-content {
align-items: center;
text-align: center;
}
}
/* Markdown content styling */
.markdown-container {
max-width: 800px;
margin: 0 auto;
}
.markdown-container :global(h1) {
font-size: 2rem;
font-weight: 700;
margin-bottom: 1.5rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--border-color);
color: var(--accent-color);
}
.markdown-container :global(h2) {
font-size: 1.5rem;
font-weight: 600;
margin-top: 2rem;
margin-bottom: 1rem;
color: var(--text-primary);
}
.markdown-container :global(h3) {
font-size: 1.25rem;
font-weight: 600;
margin-top: 1.5rem;
margin-bottom: 0.75rem;
color: var(--text-primary);
}
.markdown-container :global(p) {
margin-bottom: 1rem;
line-height: 1.6;
color: var(--text-secondary);
}
.markdown-container :global(ul), .markdown-container :global(ol) {
margin-bottom: 1rem;
padding-left: 1.5rem;
color: var(--text-secondary);
}
.markdown-container :global(li) {
margin-bottom: 0.5rem;
line-height: 1.6;
}
.markdown-container :global(code) {
background-color: var(--bg-tertiary);
padding: 0.2em 0.4em;
border-radius: 0.25rem;
font-family: monospace;
font-size: 0.875em;
}
.markdown-container :global(pre) {
background-color: var(--bg-tertiary);
padding: 1rem;
border-radius: 0.5rem;
overflow-x: auto;
margin-bottom: 1.5rem;
border: 1px solid var(--border-color);
}
.markdown-container :global(pre code) {
background-color: transparent;
padding: 0;
border-radius: 0;
display: block;
}
.markdown-container :global(blockquote) {
border-left: 4px solid var(--accent-color);
padding-left: 1rem;
margin-left: 0;
margin-right: 0;
margin-bottom: 1rem;
color: var(--text-secondary);
background-color: rgba(79, 70, 229, 0.07);
padding: 0.75rem 1rem;
border-radius: 0 0.375rem 0.375rem 0;
}
.markdown-container :global(table) {
width: 100%;
border-collapse: collapse;
margin-bottom: 1.5rem;
}
.markdown-container :global(th), .markdown-container :global(td) {
padding: 0.75rem;
border: 1px solid var(--border-color);
text-align: left;
}
.markdown-container :global(th) {
background-color: var(--bg-tertiary);
font-weight: 600;
}
.markdown-container :global(tr:nth-child(even)) {
background-color: rgba(45, 45, 45, 0.5);
}
.markdown-container :global(a) {
color: var(--accent-color);
text-decoration: none;
border-bottom: 1px dotted var(--accent-color);
transition: border-bottom 0.2s ease;
}
.markdown-container :global(a:hover) {
border-bottom: 1px solid var(--accent-color);
}
.markdown-container :global(hr) {
border: none;
border-top: 1px solid var(--border-color);
margin: 2rem 0;
}
.markdown-container :global(img) {
max-width: 100%;
border-radius: 0.5rem;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
/* Custom info box style */
.markdown-container :global(.info-box),
.markdown-container :global(.warning-box),
.markdown-container :global(.success-box) {
border-radius: 0.5rem;
padding: 1rem;
margin-bottom: 1.5rem;
display: flex;
gap: 1rem;
align-items: flex-start;
color: var(--text-primary);
}
.markdown-container :global(.info-box) {
background-color: rgba(59, 130, 246, 0.1);
border-left: 4px solid #3b82f6;
}
.markdown-container :global(.warning-box) {
background-color: rgba(245, 158, 11, 0.1);
border-left: 4px solid #f59e0b;
}
.markdown-container :global(.success-box) {
background-color: rgba(34, 197, 94, 0.1);
border-left: 4px solid #22c55e;
}
/* Responsive styles */
@media (max-width: 768px) {
.mobile-menu-toggle {
display: flex;
}
.sidebar {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
transform: translateX(-100%);
z-index: 999;
}
.sidebar.open {
transform: translateX(0);
}
.close-sidebar {
display: block;
}
.content {
padding: 4rem 1rem 2rem;
}
}
/* PrismJS VSCode-like Dark Theme */
.markdown-container :global(pre) {
background-color: #1e1e1e;
padding: 1rem;
border-radius: 0.5rem;
overflow-x: auto;
margin-bottom: 1.5rem;
border: 1px solid var(--border-color);
}
.markdown-container :global(code[class*="language-"]),
.markdown-container :global(pre[class*="language-"]) {
color: #d4d4d4;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
tab-size: 4;
hyphens: none;
}
/* Tokens */
.markdown-container :global(.token.comment),
.markdown-container :global(.token.prolog),
.markdown-container :global(.token.doctype),
.markdown-container :global(.token.cdata) {
color: #6a9955;
}
.markdown-container :global(.token.punctuation) {
color: #d4d4d4;
}
.markdown-container :global(.token.namespace) {
opacity: 0.7;
}
.markdown-container :global(.token.property),
.markdown-container :global(.token.tag),
.markdown-container :global(.token.boolean),
.markdown-container :global(.token.number),
.markdown-container :global(.token.constant),
.markdown-container :global(.token.symbol) {
color: #b5cea8;
}
.markdown-container :global(.token.selector),
.markdown-container :global(.token.attr-name),
.markdown-container :global(.token.string),
.markdown-container :global(.token.char),
.markdown-container :global(.token.builtin) {
color: #ce9178;
}
.markdown-container :global(.token.operator),
.markdown-container :global(.token.entity),
.markdown-container :global(.token.url),
.markdown-container :global(.language-css .token.string),
.markdown-container :global(.style .token.string) {
color: #d4d4d4;
}
.markdown-container :global(.token.atrule),
.markdown-container :global(.token.attr-value),
.markdown-container :global(.token.keyword) {
color: #569cd6;
}
.markdown-container :global(.token.function) {
color: #dcdcaa;
}
.markdown-container :global(.token.regex),
.markdown-container :global(.token.important),
.markdown-container :global(.token.variable) {
color: #d16969;
}
.markdown-container :global(.token.important),
.markdown-container :global(.token.bold) {
font-weight: bold;
}
.markdown-container :global(.token.italic) {
font-style: italic;
}
.markdown-container :global(.token.constant) {
color: #9cdcfe;
}
.markdown-container :global(.token.class-name) {
color: #4ec9b0;
}
.markdown-container :global(.token.parameter) {
color: #9cdcfe;
}
.markdown-container :global(.token.interpolation) {
color: #9cdcfe;
}
.markdown-container :global(.token.punctuation.interpolation-punctuation) {
color: #569cd6;
}
.markdown-container :global(.token.entity) {
cursor: help;
}
/* Line highlighting */
.markdown-container :global(pre[data-line]) {
position: relative;
}
.markdown-container :global(pre[class*="language-"] > code[class*="language-"]) {
position: relative;
z-index: 1;
}
</style>