@keshavsoft/api-tree 2.0.0 → 6.1.0
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/README.md +52 -134
- package/docs/api.html +123 -0
- package/docs/examples.html +100 -0
- package/docs/how-it-works.html +69 -0
- package/docs/index.html +63 -266
- package/docs/style.css +272 -0
- package/docs/validation.html +88 -0
- package/package.json +7 -3
- package/src/index.d.ts +1 -5
- package/src/index.js +2 -2
- package/src/v3/index.js +34 -0
- package/src/v3/internal-working/buildTree.js +35 -0
- package/src/v4/blueprint/api.json +13 -0
- package/src/v4/blueprint/source.json +228 -0
- package/src/v4/index.js +29 -0
- package/src/v4/internal-working/buildTree.js +35 -0
- package/src/v4/internal-working/guards/index.js +15 -0
- package/src/v4/internal-working/guards/isFunction.js +9 -0
- package/src/v4/internal-working/guards/isObject.js +9 -0
- package/src/v4/internal-working/guards/isStringArray.js +15 -0
- package/src/v5/blueprint/api.json +13 -0
- package/src/v5/blueprint/source.json +228 -0
- package/src/v5/engine/guards/index.js +15 -0
- package/src/v5/engine/guards/isFunction.js +9 -0
- package/src/v5/engine/guards/isObject.js +9 -0
- package/src/v5/engine/guards/isStringArray.js +15 -0
- package/src/v5/engine/run.js +35 -0
- package/src/v5/index.js +29 -0
- package/src/v6/blueprint/api.json +13 -0
- package/src/v6/blueprint/source.json +228 -0
- package/src/v6/engine/guards/index.js +15 -0
- package/src/v6/engine/guards/isFunction.js +9 -0
- package/src/v6/engine/guards/isObject.js +9 -0
- package/src/v6/engine/guards/isStringArray.js +15 -0
- package/src/v6/engine/run.js +35 -0
- package/src/v6/index.js +29 -0
package/docs/index.html
CHANGED
|
@@ -3,296 +3,93 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<title
|
|
7
|
-
<link rel="
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
<
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
color: var(--text);
|
|
29
|
-
line-height: 1.6;
|
|
30
|
-
padding: 0 1.5rem 4rem;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
.container {
|
|
34
|
-
max-width: 900px;
|
|
35
|
-
margin: 0 auto;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
header {
|
|
39
|
-
padding: 4rem 0 2.5rem;
|
|
40
|
-
border-bottom: 1px solid var(--border);
|
|
41
|
-
margin-bottom: 3rem;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.badge-bar {
|
|
45
|
-
display: flex;
|
|
46
|
-
gap: 0.5rem;
|
|
47
|
-
margin-bottom: 1rem;
|
|
48
|
-
flex-wrap: wrap;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
.badge {
|
|
52
|
-
display: inline-flex;
|
|
53
|
-
align-items: center;
|
|
54
|
-
padding: 0.25rem 0.65rem;
|
|
55
|
-
border-radius: 9999px;
|
|
56
|
-
font-size: 0.75rem;
|
|
57
|
-
font-weight: 600;
|
|
58
|
-
background: #1e293b;
|
|
59
|
-
color: #94a3b8;
|
|
60
|
-
border: 1px solid #334155;
|
|
61
|
-
}
|
|
62
|
-
.badge.green { background: #064e3b; color: #34d399; border-color: #059669; }
|
|
63
|
-
.badge.blue { background: #1e3a8a; color: #93c5fd; border-color: #2563eb; }
|
|
64
|
-
|
|
65
|
-
h1 {
|
|
66
|
-
font-size: 2.75rem;
|
|
67
|
-
font-weight: 800;
|
|
68
|
-
letter-spacing: -0.03em;
|
|
69
|
-
margin-bottom: 0.75rem;
|
|
70
|
-
background: linear-gradient(135deg, #ffffff 0%, #93c5fd 100%);
|
|
71
|
-
-webkit-background-clip: text;
|
|
72
|
-
-webkit-text-fill-color: transparent;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
.tagline {
|
|
76
|
-
font-size: 1.25rem;
|
|
77
|
-
color: var(--text-muted);
|
|
78
|
-
max-width: 700px;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
h2 {
|
|
82
|
-
font-size: 1.75rem;
|
|
83
|
-
font-weight: 700;
|
|
84
|
-
margin: 2.5rem 0 1rem;
|
|
85
|
-
color: #ffffff;
|
|
86
|
-
border-bottom: 1px solid var(--border);
|
|
87
|
-
padding-bottom: 0.5rem;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
h3 {
|
|
91
|
-
font-size: 1.25rem;
|
|
92
|
-
font-weight: 600;
|
|
93
|
-
margin: 1.5rem 0 0.75rem;
|
|
94
|
-
color: var(--primary-light);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
p, ul {
|
|
98
|
-
color: #d1d5db;
|
|
99
|
-
margin-bottom: 1.25rem;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
ul { padding-left: 1.5rem; }
|
|
103
|
-
li { margin-bottom: 0.5rem; }
|
|
104
|
-
|
|
105
|
-
.formula-box {
|
|
106
|
-
background: linear-gradient(180deg, rgba(30, 58, 138, 0.2) 0%, rgba(17, 24, 39, 0.6) 100%);
|
|
107
|
-
border: 1px solid #2563eb;
|
|
108
|
-
border-radius: 12px;
|
|
109
|
-
padding: 1.5rem;
|
|
110
|
-
margin: 2rem 0;
|
|
111
|
-
text-align: center;
|
|
112
|
-
font-size: 1.15rem;
|
|
113
|
-
font-weight: 600;
|
|
114
|
-
color: #bfdbfe;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
pre {
|
|
118
|
-
background: var(--code-bg);
|
|
119
|
-
border: 1px solid var(--card-border);
|
|
120
|
-
border-radius: 8px;
|
|
121
|
-
padding: 1.25rem;
|
|
122
|
-
overflow-x: auto;
|
|
123
|
-
font-family: 'Fira Code', monospace;
|
|
124
|
-
font-size: 0.9rem;
|
|
125
|
-
margin-bottom: 1.5rem;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
code {
|
|
129
|
-
font-family: 'Fira Code', monospace;
|
|
130
|
-
color: #93c5fd;
|
|
131
|
-
}
|
|
6
|
+
<title>api-tree — The Situation & Solution</title>
|
|
7
|
+
<link rel="stylesheet" href="style.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div class="layout">
|
|
11
|
+
<aside class="sidebar">
|
|
12
|
+
<div class="sidebar-brand">
|
|
13
|
+
<a href="index.html" class="brand-title">api-tree</a>
|
|
14
|
+
<div class="brand-subtitle">v2.0.0 • KeshavSoft</div>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="nav-label">Documentation</div>
|
|
17
|
+
<ul class="nav-list">
|
|
18
|
+
<li><a href="index.html" class="nav-link active">The Story</a></li>
|
|
19
|
+
<li><a href="how-it-works.html" class="nav-link">How It Works</a></li>
|
|
20
|
+
<li><a href="api.html" class="nav-link">API Reference</a></li>
|
|
21
|
+
<li><a href="examples.html" class="nav-link">Examples</a></li>
|
|
22
|
+
<li><a href="validation.html" class="nav-link">Guarantees</a></li>
|
|
23
|
+
</ul>
|
|
24
|
+
<div class="sidebar-footer">
|
|
25
|
+
<a href="https://github.com/keshavsoft/api-tree" target="_blank" rel="noopener">GitHub Repository →</a>
|
|
26
|
+
</div>
|
|
27
|
+
</aside>
|
|
132
28
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
29
|
+
<main class="content">
|
|
30
|
+
<h1>api-tree</h1>
|
|
31
|
+
<div class="lead">A small, zero-dependency runtime API-tree builder.</div>
|
|
136
32
|
|
|
137
|
-
|
|
138
|
-
display: grid;
|
|
139
|
-
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
|
140
|
-
gap: 1.25rem;
|
|
141
|
-
margin: 1.5rem 0;
|
|
142
|
-
}
|
|
33
|
+
<p>We had a situation in our codebase: we were writing function after function just to expose different endpoints.</p>
|
|
143
34
|
|
|
144
|
-
|
|
145
|
-
background: var(--card-bg);
|
|
146
|
-
border: 1px solid var(--card-border);
|
|
147
|
-
border-radius: 10px;
|
|
148
|
-
padding: 1.25rem;
|
|
149
|
-
}
|
|
35
|
+
<p>Yet under the hood, every single one of those functions was doing the exact same thing: calling one core execution function. The only difference from endpoint to endpoint was a few variables—like a resource name, an action, or a query string.</p>
|
|
150
36
|
|
|
151
|
-
|
|
152
|
-
color: #60a5fa;
|
|
153
|
-
margin-bottom: 0.5rem;
|
|
154
|
-
font-size: 1.05rem;
|
|
155
|
-
}
|
|
37
|
+
<p>Writing dozens of nearly identical wrapper functions created boilerplate, duplicated code, and made maintenance tedious.</p>
|
|
156
38
|
|
|
157
|
-
|
|
158
|
-
color: var(--text-muted);
|
|
159
|
-
font-size: 0.9rem;
|
|
160
|
-
margin-bottom: 0;
|
|
161
|
-
}
|
|
39
|
+
<h2>What We Did</h2>
|
|
162
40
|
|
|
163
|
-
|
|
164
|
-
width: 100%;
|
|
165
|
-
border-collapse: collapse;
|
|
166
|
-
margin: 1.5rem 0;
|
|
167
|
-
}
|
|
41
|
+
<p>We separated the parts that never change from the parts that do:</p>
|
|
168
42
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
43
|
+
<ul>
|
|
44
|
+
<li><strong>The Single Function (Executor)</strong>: The core execution engine is written exactly once. It handles the network requests, headers, and response parsing.</li>
|
|
45
|
+
<li><strong>The Variables (source.json)</strong>: The parameters that vary per endpoint are moved into a clean JSON contract.</li>
|
|
46
|
+
<li><strong>The Routes (api.json)</strong>: The allowed endpoints are defined in a flat array of dot-notation paths.</li>
|
|
47
|
+
</ul>
|
|
175
48
|
|
|
176
|
-
|
|
177
|
-
background: var(--card-bg);
|
|
178
|
-
color: #ffffff;
|
|
179
|
-
}
|
|
49
|
+
<p><strong>api-tree</strong> marries the single executor function with the variable JSON, dynamically building the callable dot-notation tree at runtime.</p>
|
|
180
50
|
|
|
181
|
-
|
|
182
|
-
background: rgba(17, 24, 39, 0.4);
|
|
183
|
-
}
|
|
51
|
+
<h2>The Code</h2>
|
|
184
52
|
|
|
185
|
-
|
|
186
|
-
margin-top: 4rem;
|
|
187
|
-
padding-top: 2rem;
|
|
188
|
-
border-top: 1px solid var(--border);
|
|
189
|
-
color: var(--text-muted);
|
|
190
|
-
font-size: 0.85rem;
|
|
191
|
-
text-align: center;
|
|
192
|
-
}
|
|
193
|
-
</style>
|
|
194
|
-
</head>
|
|
195
|
-
<body>
|
|
196
|
-
<div class="container">
|
|
197
|
-
<header>
|
|
198
|
-
<div class="badge-bar">
|
|
199
|
-
<span class="badge blue">@keshavsoft/api-tree</span>
|
|
200
|
-
<span class="badge green">v2.0.0</span>
|
|
201
|
-
<span class="badge">Zero Dependencies</span>
|
|
202
|
-
<span class="badge">Node >= 20.10</span>
|
|
203
|
-
</div>
|
|
204
|
-
<h1>@keshavsoft/api-tree</h1>
|
|
205
|
-
<p class="tagline">A declarative, ultra-lean routing engine that turns source schemas, API paths, and an external executor into a callable runtime API tree.</p>
|
|
206
|
-
</header>
|
|
53
|
+
<p>Instead of writing endless wrapper functions like this:</p>
|
|
207
54
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
55
|
+
<pre><code>// The repetitive pattern
|
|
56
|
+
export const getUnits = () => execute({ resource: "Unit" });
|
|
57
|
+
export const getLedgers = () => execute({ resource: "Ledger" });
|
|
58
|
+
export const getCompany = () => execute({ resource: "Company" });</code></pre>
|
|
211
59
|
|
|
212
|
-
|
|
213
|
-
<p><code>@keshavsoft/api-tree</code> decouples specification contracts from runtime execution:</p>
|
|
214
|
-
<div class="grid">
|
|
215
|
-
<div class="card">
|
|
216
|
-
<h4>1. Source Schema</h4>
|
|
217
|
-
<p>The single source of truth containing leaf specifications, metadata, and domain contracts.</p>
|
|
218
|
-
</div>
|
|
219
|
-
<div class="card">
|
|
220
|
-
<h4>2. API Paths</h4>
|
|
221
|
-
<p>A flat, declarative array of dotted routes (e.g. <code>app.users.profile.fetch</code>).</p>
|
|
222
|
-
</div>
|
|
223
|
-
<div class="card">
|
|
224
|
-
<h4>3. Custom Executor</h4>
|
|
225
|
-
<p>A pure execution handler that receives standardized context and executes the operation.</p>
|
|
226
|
-
</div>
|
|
227
|
-
</div>
|
|
60
|
+
<p>You write your executor once, put your variables in JSON, and let <code>api-tree</code> generate the surface:</p>
|
|
228
61
|
|
|
229
|
-
|
|
230
|
-
<pre><code>import apiTree from "@keshavsoft/api-tree";
|
|
62
|
+
<pre><code>import apiTree from "@keshavsoft/api-tree";
|
|
231
63
|
|
|
64
|
+
// 1. Only the variables
|
|
232
65
|
const source = {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
66
|
+
app: {
|
|
67
|
+
masters: {
|
|
68
|
+
unit: { all: { resource: "Unit", action: "fetch" } }
|
|
69
|
+
}
|
|
238
70
|
}
|
|
239
|
-
}
|
|
240
71
|
};
|
|
241
72
|
|
|
242
|
-
const apiPaths = ["app.
|
|
73
|
+
const apiPaths = ["app.masters.unit.all"];
|
|
243
74
|
|
|
244
|
-
|
|
245
|
-
|
|
75
|
+
// 2. The single executor function
|
|
76
|
+
const execute = async ({ inLeafSpec, inParam }) => {
|
|
77
|
+
return await dispatch(inLeafSpec.resource, inParam);
|
|
246
78
|
};
|
|
247
79
|
|
|
248
|
-
|
|
80
|
+
// 3. Build the callable tree
|
|
81
|
+
const app = apiTree(source, apiPaths, execute);
|
|
249
82
|
|
|
250
|
-
// Call
|
|
251
|
-
const
|
|
252
|
-
console.log(result); // { id: "123", action: "fetch" }</code></pre>
|
|
83
|
+
// 4. Call naturally
|
|
84
|
+
const units = await app.masters.unit.all();</code></pre>
|
|
253
85
|
|
|
254
|
-
|
|
255
|
-
<p>When an attached leaf function is invoked, your executor receives a rich context object:</p>
|
|
256
|
-
<table>
|
|
257
|
-
<thead>
|
|
258
|
-
<tr>
|
|
259
|
-
<th>Property</th>
|
|
260
|
-
<th>Type</th>
|
|
261
|
-
<th>Description</th>
|
|
262
|
-
</tr>
|
|
263
|
-
</thead>
|
|
264
|
-
<tbody>
|
|
265
|
-
<tr>
|
|
266
|
-
<td><code>inRoutePath</code></td>
|
|
267
|
-
<td>string</td>
|
|
268
|
-
<td>The full dot-notation route path.</td>
|
|
269
|
-
</tr>
|
|
270
|
-
<tr>
|
|
271
|
-
<td><code>inParam</code></td>
|
|
272
|
-
<td>any</td>
|
|
273
|
-
<td>First argument passed to the leaf.</td>
|
|
274
|
-
</tr>
|
|
275
|
-
<tr>
|
|
276
|
-
<td><code>inArgs</code></td>
|
|
277
|
-
<td>any[]</td>
|
|
278
|
-
<td>Array of all additional arguments.</td>
|
|
279
|
-
</tr>
|
|
280
|
-
<tr>
|
|
281
|
-
<td><code>inLeafSpec</code></td>
|
|
282
|
-
<td>object</td>
|
|
283
|
-
<td>Pre-resolved leaf definition from source.json.</td>
|
|
284
|
-
</tr>
|
|
285
|
-
<tr>
|
|
286
|
-
<td><code>inSource</code></td>
|
|
287
|
-
<td>object</td>
|
|
288
|
-
<td>The raw source schema object.</td>
|
|
289
|
-
</tr>
|
|
290
|
-
</tbody>
|
|
291
|
-
</table>
|
|
86
|
+
<p>When you need a new endpoint tomorrow, you do not write a new JavaScript function. You simply add a new entry to your JSON file, and it is immediately available on the tree.</p>
|
|
292
87
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
88
|
+
<div class="page-nav">
|
|
89
|
+
<span></span>
|
|
90
|
+
<a href="how-it-works.html">Next: How It Works →</a>
|
|
91
|
+
</div>
|
|
92
|
+
</main>
|
|
296
93
|
</div>
|
|
297
94
|
</body>
|
|
298
95
|
</html>
|
package/docs/style.css
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--bg: #0d1117;
|
|
3
|
+
--sidebar-bg: #090d13;
|
|
4
|
+
--card-bg: #161b22;
|
|
5
|
+
--border: #30363d;
|
|
6
|
+
--text: #e6edf3;
|
|
7
|
+
--text-muted: #8b949e;
|
|
8
|
+
--accent: #58a6ff;
|
|
9
|
+
--accent-hover: #79c0ff;
|
|
10
|
+
--active-bg: rgba(56, 139, 253, 0.15);
|
|
11
|
+
--code-bg: #161b22;
|
|
12
|
+
--callout-border: #388bfd;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
* {
|
|
16
|
+
box-sizing: border-box;
|
|
17
|
+
margin: 0;
|
|
18
|
+
padding: 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
body {
|
|
22
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
|
|
23
|
+
background: var(--bg);
|
|
24
|
+
color: var(--text);
|
|
25
|
+
line-height: 1.7;
|
|
26
|
+
min-height: 100vh;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.layout {
|
|
30
|
+
display: flex;
|
|
31
|
+
min-height: 100vh;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Sidebar Navigation (Left Side) */
|
|
35
|
+
.sidebar {
|
|
36
|
+
width: 250px;
|
|
37
|
+
background: var(--sidebar-bg);
|
|
38
|
+
border-right: 1px solid var(--border);
|
|
39
|
+
padding: 36px 20px 40px;
|
|
40
|
+
flex-shrink: 0;
|
|
41
|
+
position: sticky;
|
|
42
|
+
top: 0;
|
|
43
|
+
height: 100vh;
|
|
44
|
+
overflow-y: auto;
|
|
45
|
+
display: flex;
|
|
46
|
+
flex-direction: column;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.sidebar-brand {
|
|
50
|
+
margin-bottom: 32px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.brand-title {
|
|
54
|
+
font-size: 1.25rem;
|
|
55
|
+
font-weight: 700;
|
|
56
|
+
color: #ffffff;
|
|
57
|
+
text-decoration: none;
|
|
58
|
+
display: block;
|
|
59
|
+
letter-spacing: -0.01em;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.brand-subtitle {
|
|
63
|
+
font-size: 0.8rem;
|
|
64
|
+
color: var(--text-muted);
|
|
65
|
+
margin-top: 4px;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.nav-label {
|
|
69
|
+
font-size: 0.72rem;
|
|
70
|
+
text-transform: uppercase;
|
|
71
|
+
letter-spacing: 0.08em;
|
|
72
|
+
color: var(--text-muted);
|
|
73
|
+
font-weight: 600;
|
|
74
|
+
margin-bottom: 12px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.nav-list {
|
|
78
|
+
list-style: none;
|
|
79
|
+
display: flex;
|
|
80
|
+
flex-direction: column;
|
|
81
|
+
gap: 4px;
|
|
82
|
+
margin-bottom: auto;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.nav-link {
|
|
86
|
+
display: flex;
|
|
87
|
+
align-items: center;
|
|
88
|
+
padding: 8px 12px;
|
|
89
|
+
border-radius: 6px;
|
|
90
|
+
font-size: 0.92rem;
|
|
91
|
+
color: var(--text-muted);
|
|
92
|
+
text-decoration: none;
|
|
93
|
+
transition: all 0.15s ease;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.nav-link:hover {
|
|
97
|
+
color: var(--text);
|
|
98
|
+
background: rgba(255, 255, 255, 0.04);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.nav-link.active {
|
|
102
|
+
color: var(--accent);
|
|
103
|
+
background: var(--active-bg);
|
|
104
|
+
font-weight: 600;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.sidebar-footer {
|
|
108
|
+
padding-top: 24px;
|
|
109
|
+
border-top: 1px solid var(--border);
|
|
110
|
+
font-size: 0.8rem;
|
|
111
|
+
color: var(--text-muted);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.sidebar-footer a {
|
|
115
|
+
color: var(--text-muted);
|
|
116
|
+
text-decoration: none;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.sidebar-footer a:hover {
|
|
120
|
+
color: var(--accent);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* Main Content Area (Right Side) */
|
|
124
|
+
.content {
|
|
125
|
+
flex: 1;
|
|
126
|
+
max-width: 800px;
|
|
127
|
+
padding: 56px 64px 96px;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
h1 {
|
|
131
|
+
font-size: 2.2rem;
|
|
132
|
+
font-weight: 700;
|
|
133
|
+
color: #ffffff;
|
|
134
|
+
margin-bottom: 8px;
|
|
135
|
+
letter-spacing: -0.025em;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.lead {
|
|
139
|
+
font-size: 1.15rem;
|
|
140
|
+
color: var(--text-muted);
|
|
141
|
+
margin-bottom: 40px;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
h2 {
|
|
145
|
+
font-size: 1.45rem;
|
|
146
|
+
font-weight: 600;
|
|
147
|
+
color: #ffffff;
|
|
148
|
+
margin: 44px 0 16px;
|
|
149
|
+
letter-spacing: -0.015em;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
h3 {
|
|
153
|
+
font-size: 1.15rem;
|
|
154
|
+
font-weight: 600;
|
|
155
|
+
color: #ffffff;
|
|
156
|
+
margin: 28px 0 12px;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
p {
|
|
160
|
+
margin-bottom: 18px;
|
|
161
|
+
color: #c9d1d9;
|
|
162
|
+
font-size: 1.02rem;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
ul, ol {
|
|
166
|
+
margin: 0 0 24px 24px;
|
|
167
|
+
color: #c9d1d9;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
li {
|
|
171
|
+
margin-bottom: 10px;
|
|
172
|
+
font-size: 1.02rem;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
strong {
|
|
176
|
+
color: #ffffff;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
pre {
|
|
180
|
+
background: var(--card-bg);
|
|
181
|
+
border: 1px solid var(--border);
|
|
182
|
+
border-radius: 6px;
|
|
183
|
+
padding: 18px 22px;
|
|
184
|
+
overflow-x: auto;
|
|
185
|
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
186
|
+
font-size: 0.9rem;
|
|
187
|
+
line-height: 1.6;
|
|
188
|
+
margin: 20px 0 28px;
|
|
189
|
+
color: #e6edf3;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
code {
|
|
193
|
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
194
|
+
font-size: 0.88em;
|
|
195
|
+
background: rgba(110, 118, 129, 0.2);
|
|
196
|
+
padding: 2px 6px;
|
|
197
|
+
border-radius: 4px;
|
|
198
|
+
color: #79c0ff;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
pre code {
|
|
202
|
+
background: none;
|
|
203
|
+
padding: 0;
|
|
204
|
+
color: inherit;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
table {
|
|
209
|
+
width: 100%;
|
|
210
|
+
border-collapse: collapse;
|
|
211
|
+
margin: 24px 0;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
th, td {
|
|
215
|
+
border: 1px solid var(--border);
|
|
216
|
+
padding: 12px 16px;
|
|
217
|
+
text-align: left;
|
|
218
|
+
font-size: 0.92rem;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
th {
|
|
222
|
+
background: var(--card-bg);
|
|
223
|
+
color: #ffffff;
|
|
224
|
+
font-weight: 600;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
td {
|
|
228
|
+
background: rgba(22, 27, 34, 0.4);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.page-nav {
|
|
232
|
+
margin-top: 60px;
|
|
233
|
+
padding-top: 24px;
|
|
234
|
+
border-top: 1px solid var(--border);
|
|
235
|
+
display: flex;
|
|
236
|
+
justify-content: space-between;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.page-nav a {
|
|
240
|
+
color: var(--accent);
|
|
241
|
+
text-decoration: none;
|
|
242
|
+
font-size: 0.95rem;
|
|
243
|
+
font-weight: 500;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.page-nav a:hover {
|
|
247
|
+
color: var(--accent-hover);
|
|
248
|
+
text-decoration: underline;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/* Mobile Responsive */
|
|
252
|
+
@media (max-width: 820px) {
|
|
253
|
+
.layout {
|
|
254
|
+
flex-direction: column;
|
|
255
|
+
}
|
|
256
|
+
.sidebar {
|
|
257
|
+
width: 100%;
|
|
258
|
+
height: auto;
|
|
259
|
+
position: static;
|
|
260
|
+
border-right: none;
|
|
261
|
+
border-bottom: 1px solid var(--border);
|
|
262
|
+
padding: 24px 20px;
|
|
263
|
+
}
|
|
264
|
+
.nav-list {
|
|
265
|
+
flex-direction: row;
|
|
266
|
+
flex-wrap: wrap;
|
|
267
|
+
gap: 8px;
|
|
268
|
+
}
|
|
269
|
+
.content {
|
|
270
|
+
padding: 36px 20px 64px;
|
|
271
|
+
}
|
|
272
|
+
}
|