@panyam/tsappkit 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs/DocsPage.css +471 -0
- package/dist/docs/DocsPage.css.map +1 -0
- package/dist/docs/DocsPage.min.css +1 -0
- package/dist/docs/DocsPage.min.css.map +1 -0
- package/dist/docs/index.d.mts +91 -0
- package/dist/docs/index.d.ts +91 -0
- package/dist/docs/index.js +232 -0
- package/dist/docs/index.js.map +1 -0
- package/dist/docs/index.mjs +226 -0
- package/dist/docs/index.mjs.map +1 -0
- package/package.json +34 -13
- package/src/docs/ActionCompiler.ts +186 -0
- package/src/docs/CodeBlock.ts +103 -0
- package/src/docs/index.ts +13 -0
- package/src/docs/pageSetup.ts +32 -0
- package/styles/docs/DocsPage.scss +22 -0
- package/styles/docs/_base.scss +39 -0
- package/styles/docs/_content.scss +122 -0
- package/styles/docs/_footer.scss +56 -0
- package/styles/docs/_header.scss +157 -0
- package/styles/docs/_layout.scss +77 -0
- package/styles/docs/_responsive.scss +37 -0
- package/styles/docs/_variables.scss +71 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ActionCompiler - Shared action code compilation and execution
|
|
3
|
+
* Used by docs components like ExampleRunner and PlaygroundPage
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import ace from "ace-builds";
|
|
7
|
+
|
|
8
|
+
export interface ActionCompileResult {
|
|
9
|
+
success: boolean;
|
|
10
|
+
fn: ((node: any) => any) | null;
|
|
11
|
+
error: string | null;
|
|
12
|
+
line: number | null; // 0-indexed line number for error
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ActionRunResult {
|
|
16
|
+
success: boolean;
|
|
17
|
+
result: any;
|
|
18
|
+
error: string | null;
|
|
19
|
+
line: number | null; // 0-indexed line number for runtime error
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class ActionCompiler {
|
|
23
|
+
private editor: ace.Ace.Editor | null = null;
|
|
24
|
+
private compiledFn: ((node: any) => any) | null = null;
|
|
25
|
+
private compileError: string | null = null;
|
|
26
|
+
private statusEl: HTMLElement | null = null;
|
|
27
|
+
private onCompile: ((result: ActionCompileResult) => void) | null = null;
|
|
28
|
+
|
|
29
|
+
constructor(
|
|
30
|
+
editor: ace.Ace.Editor,
|
|
31
|
+
statusEl: HTMLElement | null = null,
|
|
32
|
+
onCompile: ((result: ActionCompileResult) => void) | null = null,
|
|
33
|
+
) {
|
|
34
|
+
this.editor = editor;
|
|
35
|
+
this.statusEl = statusEl;
|
|
36
|
+
this.onCompile = onCompile;
|
|
37
|
+
|
|
38
|
+
// Setup change listener with debounce
|
|
39
|
+
let compileTimeout: number | null = null;
|
|
40
|
+
this.editor.session.on("change", () => {
|
|
41
|
+
if (compileTimeout) clearTimeout(compileTimeout);
|
|
42
|
+
compileTimeout = window.setTimeout(() => this.compile(), 300);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Compile the current editor content
|
|
48
|
+
*/
|
|
49
|
+
compile(): ActionCompileResult {
|
|
50
|
+
if (!this.editor) {
|
|
51
|
+
return { success: false, fn: null, error: "No editor", line: null };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const code = this.editor.getValue().trim();
|
|
55
|
+
const session = this.editor.session;
|
|
56
|
+
|
|
57
|
+
// Clear previous annotations
|
|
58
|
+
session.clearAnnotations();
|
|
59
|
+
this.compiledFn = null;
|
|
60
|
+
this.compileError = null;
|
|
61
|
+
|
|
62
|
+
if (!code) {
|
|
63
|
+
this.updateStatus("", "");
|
|
64
|
+
const result = { success: true, fn: null, error: null, line: null };
|
|
65
|
+
this.onCompile?.(result);
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
// Compile using new Function
|
|
71
|
+
this.compiledFn = new Function("node", code) as (node: any) => any;
|
|
72
|
+
this.updateStatus("✓", "success");
|
|
73
|
+
const result = { success: true, fn: this.compiledFn, error: null, line: null };
|
|
74
|
+
this.onCompile?.(result);
|
|
75
|
+
return result;
|
|
76
|
+
} catch (e: any) {
|
|
77
|
+
this.compileError = e.message;
|
|
78
|
+
this.updateStatus("✗ Compile Error", "error");
|
|
79
|
+
|
|
80
|
+
// Try to extract line number from error message
|
|
81
|
+
// Common patterns: "at line 5", "line 5", ":5:", "(5:"
|
|
82
|
+
const lineMatch = e.message.match(/(?:at line |line |:|\()(\d+)(?::|,|\))/i);
|
|
83
|
+
const line = lineMatch ? parseInt(lineMatch[1], 10) - 1 : 0; // 0-indexed
|
|
84
|
+
|
|
85
|
+
session.setAnnotations([
|
|
86
|
+
{
|
|
87
|
+
row: line,
|
|
88
|
+
column: 0,
|
|
89
|
+
text: e.message,
|
|
90
|
+
type: "error",
|
|
91
|
+
},
|
|
92
|
+
]);
|
|
93
|
+
|
|
94
|
+
const result = { success: false, fn: null, error: e.message, line };
|
|
95
|
+
this.onCompile?.(result);
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Run the compiled action on a parse tree node
|
|
102
|
+
*/
|
|
103
|
+
run(node: any): ActionRunResult {
|
|
104
|
+
if (!node) {
|
|
105
|
+
return { success: false, result: null, error: "No parse result available", line: null };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (this.compileError) {
|
|
109
|
+
return { success: false, result: null, error: `Compile Error: ${this.compileError}`, line: null };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!this.compiledFn) {
|
|
113
|
+
return { success: true, result: null, error: null, line: null }; // No action code
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const result = this.compiledFn(node);
|
|
118
|
+
// Clear runtime error annotations
|
|
119
|
+
if (this.editor) {
|
|
120
|
+
this.editor.session.clearAnnotations();
|
|
121
|
+
}
|
|
122
|
+
this.updateStatus("✓", "success");
|
|
123
|
+
return { success: true, result, error: null, line: null };
|
|
124
|
+
} catch (e: any) {
|
|
125
|
+
// Try to extract line number from runtime error stack
|
|
126
|
+
const stack = e.stack || "";
|
|
127
|
+
const lineMatch = stack.match(/<anonymous>:(\d+):/);
|
|
128
|
+
const line = lineMatch ? parseInt(lineMatch[1], 10) - 1 : null; // 0-indexed
|
|
129
|
+
|
|
130
|
+
// Set runtime error annotation
|
|
131
|
+
if (this.editor && line !== null) {
|
|
132
|
+
this.editor.session.setAnnotations([
|
|
133
|
+
{
|
|
134
|
+
row: line,
|
|
135
|
+
column: 0,
|
|
136
|
+
text: e.message,
|
|
137
|
+
type: "error",
|
|
138
|
+
},
|
|
139
|
+
]);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
this.updateStatus("✗ Runtime Error", "error");
|
|
143
|
+
return { success: false, result: null, error: e.message, line };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get the current compile error (if any)
|
|
149
|
+
*/
|
|
150
|
+
getCompileError(): string | null {
|
|
151
|
+
return this.compileError;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Check if there's a compiled action function
|
|
156
|
+
*/
|
|
157
|
+
hasCompiledAction(): boolean {
|
|
158
|
+
return this.compiledFn !== null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Update status element
|
|
163
|
+
*/
|
|
164
|
+
private updateStatus(text: string, statusClass: string): void {
|
|
165
|
+
if (this.statusEl) {
|
|
166
|
+
this.statusEl.textContent = text;
|
|
167
|
+
// Clear and set status classes without removing base class
|
|
168
|
+
this.statusEl.classList.remove("success", "error");
|
|
169
|
+
if (statusClass) {
|
|
170
|
+
this.statusEl.classList.add(statusClass);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Clear annotations and status
|
|
177
|
+
*/
|
|
178
|
+
clear(): void {
|
|
179
|
+
if (this.editor) {
|
|
180
|
+
this.editor.session.clearAnnotations();
|
|
181
|
+
}
|
|
182
|
+
this.compiledFn = null;
|
|
183
|
+
this.compileError = null;
|
|
184
|
+
this.updateStatus("", "");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CodeBlock.ts - Enhances pre/code blocks with copy functionality
|
|
3
|
+
* Automatically finds all pre>code elements and adds a copy button
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class CodeBlock {
|
|
7
|
+
private container: HTMLElement;
|
|
8
|
+
private copyButton: HTMLButtonElement;
|
|
9
|
+
private codeElement: HTMLElement;
|
|
10
|
+
|
|
11
|
+
constructor(preElement: HTMLPreElement) {
|
|
12
|
+
this.codeElement = preElement.querySelector('code') || preElement;
|
|
13
|
+
|
|
14
|
+
// Wrap pre in a container for positioning
|
|
15
|
+
this.container = document.createElement('div');
|
|
16
|
+
this.container.className = 'code-block';
|
|
17
|
+
preElement.parentNode?.insertBefore(this.container, preElement);
|
|
18
|
+
this.container.appendChild(preElement);
|
|
19
|
+
|
|
20
|
+
// Create copy button
|
|
21
|
+
this.copyButton = document.createElement('button');
|
|
22
|
+
this.copyButton.className = 'code-block-copy';
|
|
23
|
+
this.copyButton.setAttribute('aria-label', 'Copy code');
|
|
24
|
+
this.copyButton.innerHTML = this.getCopyIcon();
|
|
25
|
+
this.copyButton.addEventListener('click', () => this.copyCode());
|
|
26
|
+
|
|
27
|
+
this.container.appendChild(this.copyButton);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private getCopyIcon(): string {
|
|
31
|
+
return `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
32
|
+
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
|
33
|
+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
|
34
|
+
</svg>`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private getCheckIcon(): string {
|
|
38
|
+
return `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
39
|
+
<polyline points="20 6 9 17 4 12"></polyline>
|
|
40
|
+
</svg>`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private async copyCode(): Promise<void> {
|
|
44
|
+
const text = this.codeElement.textContent || '';
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
await navigator.clipboard.writeText(text);
|
|
48
|
+
this.showSuccess();
|
|
49
|
+
} catch (err) {
|
|
50
|
+
// Fallback for older browsers
|
|
51
|
+
this.fallbackCopy(text);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private fallbackCopy(text: string): void {
|
|
56
|
+
const textarea = document.createElement('textarea');
|
|
57
|
+
textarea.value = text;
|
|
58
|
+
textarea.style.position = 'fixed';
|
|
59
|
+
textarea.style.opacity = '0';
|
|
60
|
+
document.body.appendChild(textarea);
|
|
61
|
+
textarea.select();
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
document.execCommand('copy');
|
|
65
|
+
this.showSuccess();
|
|
66
|
+
} catch (err) {
|
|
67
|
+
console.error('Failed to copy:', err);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
document.body.removeChild(textarea);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private showSuccess(): void {
|
|
74
|
+
this.copyButton.innerHTML = this.getCheckIcon();
|
|
75
|
+
this.copyButton.classList.add('copied');
|
|
76
|
+
|
|
77
|
+
setTimeout(() => {
|
|
78
|
+
this.copyButton.innerHTML = this.getCopyIcon();
|
|
79
|
+
this.copyButton.classList.remove('copied');
|
|
80
|
+
}, 2000);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Initialize all code blocks on the page
|
|
86
|
+
* @param selector - CSS selector for finding pre elements (default: '.content-body pre')
|
|
87
|
+
*/
|
|
88
|
+
export function initCodeBlocks(selector: string = '.content-body pre'): void {
|
|
89
|
+
const preElements = document.querySelectorAll(selector);
|
|
90
|
+
|
|
91
|
+
preElements.forEach((pre) => {
|
|
92
|
+
// Skip if already initialized
|
|
93
|
+
if (pre.parentElement?.classList.contains('code-block')) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
// Skip if this pre contains another pre (outer wrapper of double-wrapped code blocks)
|
|
97
|
+
// We only want to process the innermost pre that has the actual code
|
|
98
|
+
if (pre.querySelector('pre')) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
new CodeBlock(pre as HTMLPreElement);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Documentation site components and utilities
|
|
3
|
+
*
|
|
4
|
+
* This module provides reusable components for building documentation sites:
|
|
5
|
+
* - CodeBlock: Copy-to-clipboard for code blocks
|
|
6
|
+
* - pageSetup: Common page initialization (sidebar highlighting, etc.)
|
|
7
|
+
* - ActionCompiler: Compile and run user-provided action code
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export { CodeBlock, initCodeBlocks } from './CodeBlock';
|
|
11
|
+
export { highlightActiveSidebarLink, initPageSetup } from './pageSetup';
|
|
12
|
+
export { ActionCompiler } from './ActionCompiler';
|
|
13
|
+
export type { ActionCompileResult, ActionRunResult } from './ActionCompiler';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pageSetup.ts - Common page initialization shared across all doc pages
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Highlights the currently active link in the sidebar navigation
|
|
7
|
+
* @param sidebarSelector - CSS selector for sidebar links (default: '.sidebar .section-nav a')
|
|
8
|
+
*/
|
|
9
|
+
export function highlightActiveSidebarLink(sidebarSelector: string = '.sidebar .section-nav a'): void {
|
|
10
|
+
const currentPath = window.location.pathname;
|
|
11
|
+
const sidebarLinks = document.querySelectorAll(sidebarSelector);
|
|
12
|
+
|
|
13
|
+
sidebarLinks.forEach((link) => {
|
|
14
|
+
const href = link.getAttribute('href');
|
|
15
|
+
if (href && currentPath.endsWith(href.replace(/\/$/, '') + '/')) {
|
|
16
|
+
link.classList.add('active');
|
|
17
|
+
} else if (href && currentPath === href) {
|
|
18
|
+
link.classList.add('active');
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Initialize common page features
|
|
25
|
+
* Call this on DOMContentLoaded for all doc pages
|
|
26
|
+
* @param options - Configuration options for page setup
|
|
27
|
+
*/
|
|
28
|
+
export function initPageSetup(options: {
|
|
29
|
+
sidebarSelector?: string;
|
|
30
|
+
} = {}): void {
|
|
31
|
+
highlightActiveSidebarLink(options.sidebarSelector);
|
|
32
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// DocsPage - Main Documentation Styles
|
|
3
|
+
//
|
|
4
|
+
// This is the main entry point for documentation site styling.
|
|
5
|
+
// Import this file in your project's main stylesheet:
|
|
6
|
+
//
|
|
7
|
+
// @use '@panyam/tsappkit/styles/docs/DocsPage.scss';
|
|
8
|
+
//
|
|
9
|
+
// Or import individual partials for customization:
|
|
10
|
+
//
|
|
11
|
+
// @use '@panyam/tsappkit/styles/docs/variables';
|
|
12
|
+
// @use '@panyam/tsappkit/styles/docs/base';
|
|
13
|
+
// // ... add your customizations
|
|
14
|
+
// ============================================
|
|
15
|
+
|
|
16
|
+
@use 'variables';
|
|
17
|
+
@use 'base';
|
|
18
|
+
@use 'header';
|
|
19
|
+
@use 'layout';
|
|
20
|
+
@use 'content';
|
|
21
|
+
@use 'footer';
|
|
22
|
+
@use 'responsive';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// Base Styles - Reset & Typography
|
|
3
|
+
// ============================================
|
|
4
|
+
|
|
5
|
+
// Reset
|
|
6
|
+
*,
|
|
7
|
+
*::before,
|
|
8
|
+
*::after {
|
|
9
|
+
box-sizing: border-box;
|
|
10
|
+
margin: 0;
|
|
11
|
+
padding: 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
html {
|
|
15
|
+
font-size: 16px;
|
|
16
|
+
scroll-behavior: smooth;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
body {
|
|
20
|
+
font-family: var(--font-sans);
|
|
21
|
+
color: var(--color-text);
|
|
22
|
+
background: var(--color-bg);
|
|
23
|
+
line-height: 1.6;
|
|
24
|
+
min-height: 100vh;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
a {
|
|
28
|
+
color: var(--color-primary);
|
|
29
|
+
text-decoration: none;
|
|
30
|
+
|
|
31
|
+
&:hover {
|
|
32
|
+
text-decoration: underline;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
code,
|
|
37
|
+
pre {
|
|
38
|
+
font-family: var(--font-mono);
|
|
39
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// Content Styles
|
|
3
|
+
// ============================================
|
|
4
|
+
|
|
5
|
+
.content-header {
|
|
6
|
+
margin-bottom: var(--spacing-xl);
|
|
7
|
+
|
|
8
|
+
h1 {
|
|
9
|
+
font-size: 2.5rem;
|
|
10
|
+
font-weight: 700;
|
|
11
|
+
line-height: 1.2;
|
|
12
|
+
margin-bottom: var(--spacing-sm);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.lead {
|
|
16
|
+
font-size: 1.25rem;
|
|
17
|
+
color: var(--color-text-muted);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.content-body {
|
|
22
|
+
font-size: 1rem;
|
|
23
|
+
line-height: 1.75;
|
|
24
|
+
|
|
25
|
+
h2 {
|
|
26
|
+
font-size: 1.75rem;
|
|
27
|
+
font-weight: 600;
|
|
28
|
+
margin-top: var(--spacing-xl);
|
|
29
|
+
margin-bottom: var(--spacing-md);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
h3 {
|
|
33
|
+
font-size: 1.25rem;
|
|
34
|
+
font-weight: 600;
|
|
35
|
+
margin-top: var(--spacing-lg);
|
|
36
|
+
margin-bottom: var(--spacing-sm);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
p {
|
|
40
|
+
margin-bottom: var(--spacing-md);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
ul,
|
|
44
|
+
ol {
|
|
45
|
+
margin-bottom: var(--spacing-md);
|
|
46
|
+
padding-left: var(--spacing-lg);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
li {
|
|
50
|
+
margin-bottom: var(--spacing-xs);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Code blocks
|
|
54
|
+
pre {
|
|
55
|
+
background: var(--color-bg-code);
|
|
56
|
+
color: #e2e8f0;
|
|
57
|
+
padding: var(--spacing-md);
|
|
58
|
+
border-radius: var(--radius-md);
|
|
59
|
+
overflow-x: auto;
|
|
60
|
+
margin-bottom: var(--spacing-md);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Inline code
|
|
64
|
+
code {
|
|
65
|
+
background: var(--color-bg-alt);
|
|
66
|
+
padding: 0.2em 0.4em;
|
|
67
|
+
border-radius: var(--radius-sm);
|
|
68
|
+
font-size: 0.9em;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
pre code {
|
|
72
|
+
background: none;
|
|
73
|
+
padding: 0;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Code block with copy button (used with CodeBlock.ts component)
|
|
78
|
+
.code-block {
|
|
79
|
+
position: relative;
|
|
80
|
+
margin-bottom: var(--spacing-md);
|
|
81
|
+
|
|
82
|
+
pre {
|
|
83
|
+
margin-bottom: 0;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.code-block-copy {
|
|
88
|
+
position: absolute;
|
|
89
|
+
top: var(--spacing-sm);
|
|
90
|
+
right: var(--spacing-sm);
|
|
91
|
+
padding: 6px 8px;
|
|
92
|
+
background: rgba(255, 255, 255, 0.1);
|
|
93
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
94
|
+
border-radius: var(--radius-sm);
|
|
95
|
+
color: #94a3b8;
|
|
96
|
+
cursor: pointer;
|
|
97
|
+
opacity: 0;
|
|
98
|
+
transition: opacity 0.2s, background 0.2s, color 0.2s;
|
|
99
|
+
display: flex;
|
|
100
|
+
align-items: center;
|
|
101
|
+
justify-content: center;
|
|
102
|
+
|
|
103
|
+
svg {
|
|
104
|
+
width: 16px;
|
|
105
|
+
height: 16px;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.code-block:hover .code-block-copy {
|
|
110
|
+
opacity: 1;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.code-block-copy:hover {
|
|
114
|
+
background: rgba(255, 255, 255, 0.2);
|
|
115
|
+
color: #e2e8f0;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.code-block-copy.copied {
|
|
119
|
+
color: #4ade80;
|
|
120
|
+
background: rgba(74, 222, 128, 0.2);
|
|
121
|
+
border-color: rgba(74, 222, 128, 0.3);
|
|
122
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// Footer Styles
|
|
3
|
+
// ============================================
|
|
4
|
+
|
|
5
|
+
.site-footer {
|
|
6
|
+
background: var(--color-bg-alt);
|
|
7
|
+
border-top: 1px solid var(--color-border);
|
|
8
|
+
padding: var(--spacing-xl) 0;
|
|
9
|
+
margin-top: var(--spacing-xl);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.footer-container {
|
|
13
|
+
max-width: var(--page-max-width);
|
|
14
|
+
margin: 0 auto;
|
|
15
|
+
padding: 0 var(--spacing-lg);
|
|
16
|
+
display: grid;
|
|
17
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
18
|
+
gap: var(--spacing-xl);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.footer-section {
|
|
22
|
+
h4 {
|
|
23
|
+
font-weight: 600;
|
|
24
|
+
margin-bottom: var(--spacing-md);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
ul {
|
|
28
|
+
list-style: none;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
li {
|
|
32
|
+
margin-bottom: var(--spacing-xs);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
a {
|
|
36
|
+
color: var(--color-text-muted);
|
|
37
|
+
|
|
38
|
+
&:hover {
|
|
39
|
+
color: var(--color-primary);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.version {
|
|
44
|
+
color: var(--color-text-light);
|
|
45
|
+
font-size: 0.875rem;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.footer-bottom {
|
|
50
|
+
text-align: center;
|
|
51
|
+
padding-top: var(--spacing-lg);
|
|
52
|
+
margin-top: var(--spacing-lg);
|
|
53
|
+
border-top: 1px solid var(--color-border);
|
|
54
|
+
color: var(--color-text-light);
|
|
55
|
+
font-size: 0.875rem;
|
|
56
|
+
}
|