πŸ“¦ release: crates.io publish prep + webtui site updates

swadhinbiswas committed 2 months ago
6975fa8
1 parent 7867f8d
commit 6975fa8
6 files changed
+1319 -425
β–ΌCargo.lock
+5-5
2729
2729
2730
2730
[[package]]
2731
2731
name = "justapi-bench"
2732
-version = "2.0.8"
2732
+version = "2.0.9"
2733
2733
dependencies = [
2734
2734
"anyhow",
2735
2735
"clap",
2744
2744
2745
2745
[[package]]
2746
2746
name = "justapi-cli"
2747
-version = "2.0.8"
2747
+version = "2.0.9"
2748
2748
dependencies = [
2749
2749
"anyhow",
2750
2750
"clap",
2764
2764
2765
2765
[[package]]
2766
2766
name = "justapi-core"
2767
-version = "2.0.8"
2767
+version = "2.0.9"
2768
2768
dependencies = [
2769
2769
"anyhow",
2770
2770
"arc-swap",
2833
2833
2834
2834
[[package]]
2835
2835
name = "justapi-inference"
2836
-version = "2.0.8"
2836
+version = "2.0.9"
2837
2837
dependencies = [
2838
2838
"anyhow",
2839
2839
"async-stream",
2853
2853
2854
2854
[[package]]
2855
2855
name = "justapi-py"
2856
-version = "2.0.8"
2856
+version = "2.0.9"
2857
2857
dependencies = [
2858
2858
"anyhow",
2859
2859
"base64 0.22.1",
2860
2860
β–Όwebtui/build.py
+77-4
115
115
parts = []
116
116
for group, items in SIDEBAR:
117
117
parts.append(f'<div class="ja-nav-group">{html_mod.escape(group)}</div>')
118
+ parts.append('<ul marker-="tree">')
118
119
for label, path in items:
119
120
cls = ' class="active"' if path == active_path else ""
120
- parts.append(f'<a href="{path}"{cls}>{html_mod.escape(label)}</a>')
121
+ parts.append(f' <li><a href="{path}"{cls}>{html_mod.escape(label)}</a></li>')
122
+ parts.append('</ul>')
121
123
return "\n".join(parts)
122
124
123
125
141
143
142
144
143
145
def md_to_html(md_text: str) -> str:
144
- return markdown.markdown(
146
+ html = markdown.markdown(
145
147
md_text,
146
148
extensions=["fenced_code", "tables", "codehilite", "toc", "attr_list"],
147
149
)
150
+ # Enrich standard elements with WebTUI attributes
151
+ # <pre> left unstyled β€” site CSS handles code block styling
152
+ html = html.replace("<table>", '<table class="shadow" box-="square">')
153
+ html = html.replace("<blockquote>", '<blockquote box-="square">')
154
+ return html
155
+
156
+
157
+def get_prev_next(active_path: str):
158
+ flat = []
159
+ for group, items in SIDEBAR:
160
+ for label, path in items:
161
+ flat.append((label, path))
162
+ for i, (label, path) in enumerate(flat):
163
+ if path == active_path:
164
+ prev_item = flat[i-1] if i > 0 else None
165
+ next_item = flat[i+1] if i < len(flat) - 1 else None
166
+ return prev_item, next_item
167
+ return None, None
148
168
149
169
150
170
def render_page(meta: dict, body: str, path: str) -> str:
171
+ template = (ROOT / "templates/base.html").read_text()
151
172
content_html = md_to_html(body)
152
173
content_html = slugify_headers(content_html)
174
+
175
+ prev_item, next_item = get_prev_next(path)
176
+ if prev_item or next_item:
177
+ nav_html = '\n<div class="ja-doc-nav">\n'
178
+ if prev_item:
179
+ nav_html += f' <a href="{prev_item[1]}" class="ja-doc-nav-prev"><span class="nf">&#xf060;</span> Prev: {html_mod.escape(prev_item[0])}</a>\n'
180
+ else:
181
+ nav_html += ' <span></span>\n'
182
+ if next_item:
183
+ nav_html += f' <a href="{next_item[1]}" class="ja-doc-nav-next">Next: {html_mod.escape(next_item[0])} <span class="nf">&#xf061;</span></a>\n'
184
+ nav_html += '</div>\n'
185
+ content_html += nav_html
186
+
153
187
title = meta.get("title", "JustAPI")
154
188
description = meta.get("description", "JustAPI β€” Python web framework with a Rust core.")
155
- page = TEMPLATE
189
+ page = template
156
190
page = page.replace("{{ title }}", html_mod.escape(title))
157
191
page = page.replace("{{ description }}", html_mod.escape(description))
158
192
page = page.replace("{{ content }}", content_html)
159
193
page = page.replace("{{ sidebar }}", build_sidebar(path))
160
194
page = page.replace("{{ toc }}", build_toc(content_html))
195
+ page = page.replace("{{ url }}", path)
161
196
return page
162
197
163
198
174
209
shutil.copy(ROOT / "src/main.js", DIST / "js/main.js")
175
210
176
211
count = 0
212
+ urls = ["/"]
213
+ all_llm_content = ["# JustAPI Full Documentation\n\n"]
177
214
for md_file in sorted(CONTENT.rglob("*.md*")):
178
215
rel = md_file.relative_to(CONTENT)
179
216
if rel.name == "index.mdx":
183
220
out = DIST / str(rel).replace(".mdx", "").replace(".md", "") / "index.html"
184
221
out.parent.mkdir(parents=True, exist_ok=True)
185
222
out.write_text(render_page(meta, body, url_path))
223
+ urls.append(url_path)
224
+ all_llm_content.append(f"## {meta.get('title', url_path)}\n\n{body}\n\n")
186
225
count += 1
187
226
188
227
# Landing page (hand-built, no frontmatter)
191
230
landing = landing.replace("{{ toc }}", "")
192
231
(DIST / "index.html").write_text(landing)
193
232
194
- print(f"built {count} docs pages + landing β†’ {DIST}")
233
+ # Generate Section Index Entrypoint Aliases (so /getting-started/, /tutorials/, etc. work directly)
234
+ SECTION_ALIASES = [
235
+ ("getting-started", "getting-started/overview"),
236
+ ("tutorials", "tutorials/hello-world"),
237
+ ("api-reference", "api-reference/justapiapp"),
238
+ ("deployment", "deployment/docker"),
239
+ ("advanced", "advanced/zero-gil-architecture"),
240
+ ("security", "security/policy"),
241
+ ("observability", "observability/metrics-monitoring"),
242
+ ("reference", "reference/cli"),
243
+ ]
244
+ for sec, target in SECTION_ALIASES:
245
+ sec_dir = DIST / sec
246
+ target_file = DIST / target / "index.html"
247
+ if target_file.exists():
248
+ sec_dir.mkdir(parents=True, exist_ok=True)
249
+ shutil.copy(target_file, sec_dir / "index.html")
250
+
251
+ # Generate sitemap.xml
252
+ sitemap = ['<?xml version="1.0" encoding="UTF-8"?>']
253
+ sitemap.append('<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">')
254
+ for u in urls:
255
+ priority = "1.0" if u == "/" else "0.8"
256
+ sitemap.append(f' <url>\n <loc>https://justapi.pages.dev{u}</loc>\n <changefreq>weekly</changefreq>\n <priority>{priority}</priority>\n </url>')
257
+ sitemap.append('</urlset>')
258
+ (DIST / "sitemap.xml").write_text("\n".join(sitemap))
259
+
260
+ # Generate robots.txt
261
+ robots = "User-agent: *\nAllow: /\n\nSitemap: https://justapi.pages.dev/sitemap.xml\n"
262
+ (DIST / "robots.txt").write_text(robots)
263
+
264
+ # Generate all-llm.txt
265
+ (DIST / "all-llm.txt").write_text("".join(all_llm_content))
266
+
267
+ print(f"built {count} docs pages + landing + section entrypoints + seo artifacts β†’ {DIST}")
195
268
196
269
197
270
if __name__ == "__main__":
198
271
β–Όwebtui/src/main.css
+802-319
1
-/* ═══════════════════════════════════════════════════════════════════
2
- JUSTAPI DOCS β€” TRUE TERMINAL UI (WebTUI)
3
- Pure WebTUI: base + utils + components + nf plugin + themes
4
- ═══════════════════════════════════════════════════════════════════ */
5
-
6
-@layer base, utils, components;
7
-
8
-/* WebTUI core */
9
-@import './webtui/base.css';
10
-@import './webtui/box.css';
11
-@import './webtui/components/typography.css';
12
-@import './webtui/components/button.css';
13
-@import './webtui/components/badge.css';
14
-@import './webtui/components/mark.css';
15
-@import './webtui/components/pre.css';
16
-@import './webtui/components/progress.css';
17
-@import './webtui/components/spinner.css';
18
-@import './webtui/components/separator.css';
19
-@import './webtui/components/table.css';
20
-@import './webtui/components/accordion.css';
21
-@import './webtui/components/tooltip.css';
22
-@import './webtui/components/view.css';
23
-@import './webtui/nf.css';
24
-
25
-/* Themes */
26
-@import './webtui/catppuccin.css';
27
-@import './webtui/everforest.css';
28
-@import './webtui/nord.css';
29
-
30
-/* ═══════════════════════════════════════════════════════════════════
31
- BASE β€” 8-bit terminal palette
32
- ═══════════════════════════════════════════════════════════════════ */
1
+@layer base, utils, components, site;
2
+
3
+@import '/css/webtui/base.css';
4
+@import '/css/webtui/box.css';
5
+@import '/css/webtui/components/typography.css';
6
+@import '/css/webtui/components/button.css';
7
+@import '/css/webtui/components/badge.css';
8
+@import '/css/webtui/components/checkbox.css';
9
+@import '/css/webtui/components/dialog.css';
10
+@import '/css/webtui/components/input.css';
11
+@import '/css/webtui/components/mark.css';
12
+@import '/css/webtui/components/popover.css';
13
+@import '/css/webtui/components/pre.css';
14
+@import '/css/webtui/components/progress.css';
15
+@import '/css/webtui/components/radio.css';
16
+@import '/css/webtui/components/range.css';
17
+@import '/css/webtui/components/separator.css';
18
+@import '/css/webtui/components/spinner.css';
19
+@import '/css/webtui/components/switch.css';
20
+@import '/css/webtui/components/table.css';
21
+@import '/css/webtui/components/textarea.css';
22
+@import '/css/webtui/components/accordion.css';
23
+@import '/css/webtui/components/tooltip.css';
24
+@import '/css/webtui/components/view.css';
25
+@import '/css/webtui/nf.css';
26
+@import '/css/webtui/catppuccin.css';
27
+
33
28
@layer base {
34
29
:root {
35
- --font-size: 16px;
30
+ --font-size: 15px;
36
31
--line-height: 1.55;
37
32
--font-weight-normal: 400;
38
33
--font-weight-bold: 700;
39
- --font-family:
40
- 'JetBrains Mono', 'Fira Code', 'Cascadia Code', Menlo, Monaco,
41
- ui-monospace, monospace, 'Symbols Nerd Font';
42
- }
43
-
44
- [data-webtui-theme='dark'],
45
- [data-webtui-theme='light'] {
46
- --bit0: #000000; --bit1: #800000; --bit2: #008000; --bit3: #808000;
47
- --bit4: #000080; --bit5: #800080; --bit6: #008080; --bit7: #c0c0c0;
48
- --bit8: #808080; --bit9: #ff0000; --bit10: #00ff00; --bit11: #ffff00;
49
- --bit12: #0000ff; --bit13: #ff00ff; --bit14: #00ffff; --bit15: #ffffff;
50
- }
51
-
52
- [data-webtui-theme='dark'] {
53
- --background0: #0a0a0a;
54
- --background1: #161616;
55
- --background2: #242424;
56
- --background3: #333333;
57
- --foreground0: var(--bit7);
58
- --foreground1: var(--bit8);
59
- --foreground2: var(--bit7);
60
- --box-border-color: var(--bit7);
61
- --table-border-color: var(--bit7);
62
- --separator-color: var(--bit8);
63
- --green: var(--bit10);
64
- --red: var(--bit9);
65
- --yellow: var(--bit11);
66
- --blue: var(--bit14);
67
- --cyan: var(--bit14);
68
- --magenta: var(--bit13);
69
- --white: var(--bit15);
70
- }
71
-
72
- [data-webtui-theme='light'] {
34
+ --font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', Menlo, Monaco, ui-monospace, monospace, 'Symbols Nerd Font';
35
+ }
36
+
37
+ /* Nord semantic color bridge - WebTUI's nord.css uses --nord0..15 but our site uses --green, --red, etc. */
38
+ [data-webtui-theme=nord] {
39
+ --green: var(--nord14); /* #a3be8c */
40
+ --red: var(--nord11); /* #bf616a */
41
+ --yellow: var(--nord13); /* #ebcb8b */
42
+ --blue: var(--nord9); /* #81a1c1 */
43
+ --sky: var(--nord8); /* #88c0d0 */
44
+ --mauve: var(--nord15); /* #b48ead */
45
+ --peach: var(--nord12); /* #d08770 */
46
+ --teal: var(--nord7); /* #8fbcbb */
47
+ --lavender: var(--nord10); /* #5e81ac */
48
+ --crust: #242933;
49
+ --mantle: #292e39;
50
+ }
51
+
52
+ /* Dark theme palette (custom - no WebTUI file for this) */
53
+ [data-webtui-theme=dark] {
54
+ --background0: #121212;
55
+ --background1: #1e1e1e;
56
+ --background2: #2d2d2d;
57
+ --background3: #3d3d3d;
58
+ --foreground0: #e0e0e0;
59
+ --foreground1: #cccccc;
60
+ --foreground2: #888888;
61
+ --green: #40c057;
62
+ --red: #fa5252;
63
+ --yellow: #fab005;
64
+ --blue: #4dabf7;
65
+ --sky: #3bc9db;
66
+ --teal: #12b886;
67
+ --mauve: #be4bdb;
68
+ --peach: #ff922b;
69
+ --rosewater: #ff6b6b;
70
+ --lavender: #7950f2;
71
+ --box-border-color: var(--foreground0);
72
+ --crust: #000000;
73
+ --mantle: #0a0a0a;
74
+ }
75
+
76
+ /* Light theme palette (custom) */
77
+ [data-webtui-theme=light] {
73
78
--background0: #ffffff;
74
- --background1: #e8e8e8;
75
- --background2: #d0d0d0;
76
- --background3: #b8b8b8;
77
- --foreground0: #000000;
78
- --foreground1: #404040;
79
- --foreground2: #808080;
80
- --box-border-color: #000000;
81
- --table-border-color: #000000;
82
- --separator-color: #808080;
83
- --green: #008000;
84
- --red: #800000;
85
- --yellow: #808000;
86
- --blue: #000080;
87
- --cyan: #008080;
88
- --magenta: #800080;
89
- --white: #000000;
79
+ --background1: #f1f3f5;
80
+ --background2: #e9ecef;
81
+ --background3: #dee2e6;
82
+ --foreground0: #212529;
83
+ --foreground1: #343a40;
84
+ --foreground2: #6c757d;
85
+ --green: #2b8a3e;
86
+ --red: #c92a2a;
87
+ --yellow: #e67700;
88
+ --blue: #1864ab;
89
+ --sky: #0b7285;
90
+ --teal: #087f5b;
91
+ --mauve: #862e9c;
92
+ --peach: #d9480f;
93
+ --rosewater: #c2255c;
94
+ --lavender: #5c7cfa;
95
+ --box-border-color: var(--foreground0);
96
+ --crust: #dce0e8;
97
+ --mantle: #e6e9ef;
90
98
}
91
99
}
92
100
93
-/* ═══════════════════════════════════════════════════════════════════
94
- LAYOUT β€” terminal chrome
95
- ═══════════════════════════════════════════════════════════════════ */
96
101
@layer components {
97
- * { box-sizing: border-box; }
98
- html, body { margin: 0; padding: 0; background-color: var(--background0); color: var(--foreground0); }
99
- body { font-family: var(--font-family); }
102
+ /* Heading colors for dark/light/nord */
103
+ [data-webtui-theme=dark] h1, [data-webtui-theme=light] h1, [data-webtui-theme=nord] h1 { color: var(--red); }
104
+ [data-webtui-theme=dark] h2, [data-webtui-theme=light] h2, [data-webtui-theme=nord] h2 { color: var(--peach); }
105
+ [data-webtui-theme=dark] h3, [data-webtui-theme=light] h3, [data-webtui-theme=nord] h3 { color: var(--yellow); }
106
+ [data-webtui-theme=dark] h4, [data-webtui-theme=light] h4, [data-webtui-theme=nord] h4 { color: var(--green); }
107
+ [data-webtui-theme=dark] h5, [data-webtui-theme=light] h5, [data-webtui-theme=nord] h5 { color: var(--blue); }
108
+ [data-webtui-theme=dark] h6, [data-webtui-theme=light] h6, [data-webtui-theme=nord] h6 { color: var(--lavender); }
109
+
110
+ /* Link colors */
111
+ [data-webtui-theme=dark] p a:not([is-~="button"]), [data-webtui-theme=dark] li a:not([is-~="button"]), [data-webtui-theme=dark] blockquote a:not([is-~="button"]),
112
+ [data-webtui-theme=light] p a:not([is-~="button"]), [data-webtui-theme=light] li a:not([is-~="button"]), [data-webtui-theme=light] blockquote a:not([is-~="button"]),
113
+ [data-webtui-theme=nord] p a:not([is-~="button"]), [data-webtui-theme=nord] li a:not([is-~="button"]), [data-webtui-theme=nord] blockquote a:not([is-~="button"]) {
114
+ color: var(--sky); text-decoration: underline;
115
+ }
116
+ [data-webtui-theme=dark] p a:hover:not([is-~="button"]), [data-webtui-theme=dark] li a:hover:not([is-~="button"]),
117
+ [data-webtui-theme=light] p a:hover:not([is-~="button"]), [data-webtui-theme=light] li a:hover:not([is-~="button"]),
118
+ [data-webtui-theme=nord] p a:hover:not([is-~="button"]), [data-webtui-theme=nord] li a:hover:not([is-~="button"]) {
119
+ color: var(--blue);
120
+ }
121
+
122
+ /* Inline code color */
123
+ [data-webtui-theme=dark] p code, [data-webtui-theme=dark] li code,
124
+ [data-webtui-theme=light] p code, [data-webtui-theme=light] li code,
125
+ [data-webtui-theme=nord] p code, [data-webtui-theme=nord] li code {
126
+ color: var(--red);
127
+ }
128
+
129
+ /* Badge variant mappings for dark/light/nord */
130
+ [data-webtui-theme=dark] [is-~=badge][variant-=green], [data-webtui-theme=light] [is-~=badge][variant-=green], [data-webtui-theme=nord] [is-~=badge][variant-=green] { --badge-color: var(--green); --badge-text: var(--background0); }
131
+ [data-webtui-theme=dark] [is-~=badge][variant-=blue], [data-webtui-theme=light] [is-~=badge][variant-=blue], [data-webtui-theme=nord] [is-~=badge][variant-=blue] { --badge-color: var(--blue); --badge-text: var(--background0); }
132
+ [data-webtui-theme=dark] [is-~=badge][variant-=mauve], [data-webtui-theme=light] [is-~=badge][variant-=mauve], [data-webtui-theme=nord] [is-~=badge][variant-=mauve] { --badge-color: var(--mauve); --badge-text: var(--background0); }
133
+ [data-webtui-theme=dark] [is-~=badge][variant-=red], [data-webtui-theme=light] [is-~=badge][variant-=red], [data-webtui-theme=nord] [is-~=badge][variant-=red] { --badge-color: var(--red); --badge-text: var(--background0); }
134
+ [data-webtui-theme=dark] [is-~=badge][variant-=yellow], [data-webtui-theme=light] [is-~=badge][variant-=yellow], [data-webtui-theme=nord] [is-~=badge][variant-=yellow] { --badge-color: var(--yellow); --badge-text: var(--background0); }
135
+ [data-webtui-theme=dark] [is-~=badge][variant-=peach], [data-webtui-theme=light] [is-~=badge][variant-=peach], [data-webtui-theme=nord] [is-~=badge][variant-=peach] { --badge-color: var(--peach); --badge-text: var(--background0); }
136
+ [data-webtui-theme=dark] [is-~=badge][variant-=sky], [data-webtui-theme=light] [is-~=badge][variant-=sky], [data-webtui-theme=nord] [is-~=badge][variant-=sky] { --badge-color: var(--sky); --badge-text: var(--background0); }
137
+ [data-webtui-theme=dark] [is-~=badge][variant-=teal], [data-webtui-theme=light] [is-~=badge][variant-=teal], [data-webtui-theme=nord] [is-~=badge][variant-=teal] { --badge-color: var(--teal); --badge-text: var(--background0); }
138
+ [data-webtui-theme=dark] [is-~=badge][variant-=lavender], [data-webtui-theme=light] [is-~=badge][variant-=lavender], [data-webtui-theme=nord] [is-~=badge][variant-=lavender] { --badge-color: var(--lavender); --badge-text: var(--background0); }
139
+
140
+ /* Button variant mappings for dark/light/nord/everforest */
141
+ [data-webtui-theme=dark] button[variant-=green], [data-webtui-theme=dark] [is-~=button][variant-=green],
142
+ [data-webtui-theme=light] button[variant-=green], [data-webtui-theme=light] [is-~=button][variant-=green],
143
+ [data-webtui-theme=nord] button[variant-=green], [data-webtui-theme=nord] [is-~=button][variant-=green],
144
+ [data-webtui-theme|=everforest] [is-~=button][variant-=green] {
145
+ --button-primary: var(--green); --button-secondary: var(--background0);
146
+ }
147
+ [data-webtui-theme=dark] button[variant-=blue], [data-webtui-theme=dark] [is-~=button][variant-=blue],
148
+ [data-webtui-theme=light] button[variant-=blue], [data-webtui-theme=light] [is-~=button][variant-=blue],
149
+ [data-webtui-theme=nord] button[variant-=blue], [data-webtui-theme=nord] [is-~=button][variant-=blue],
150
+ [data-webtui-theme|=everforest] [is-~=button][variant-=blue] {
151
+ --button-primary: var(--blue); --button-secondary: var(--background0);
152
+ }
153
+ [data-webtui-theme=dark] button[variant-=mauve], [data-webtui-theme=dark] [is-~=button][variant-=mauve],
154
+ [data-webtui-theme=light] button[variant-=mauve], [data-webtui-theme=light] [is-~=button][variant-=mauve],
155
+ [data-webtui-theme=nord] button[variant-=mauve], [data-webtui-theme=nord] [is-~=button][variant-=mauve],
156
+ [data-webtui-theme|=everforest] [is-~=button][variant-=mauve] {
157
+ --button-primary: var(--mauve); --button-secondary: var(--background0);
158
+ }
159
+ [data-webtui-theme=dark] button[variant-=red], [data-webtui-theme=dark] [is-~=button][variant-=red],
160
+ [data-webtui-theme=light] button[variant-=red], [data-webtui-theme=light] [is-~=button][variant-=red],
161
+ [data-webtui-theme=nord] button[variant-=red], [data-webtui-theme=nord] [is-~=button][variant-=red],
162
+ [data-webtui-theme|=everforest] [is-~=button][variant-=red] {
163
+ --button-primary: var(--red); --button-secondary: var(--background0);
164
+ }
165
+}
166
+
167
+@layer site {
168
+ column { display: flex; flex-direction: column; }
169
+ row { display: flex; flex-direction: row; }
170
+ [align-="center"] { align-items: center; justify-content: center; }
171
+ [align-="between"] { justify-content: space-between; align-items: center; }
172
+ [gap-="1"] { gap: 0.5rem; }
173
+ [gap-="2"] { gap: 1rem; }
174
+ [gap-="3"] { gap: 1.5rem; }
175
+ [pad-="1 1"] { padding: 0.5rem; }
176
+ [pad-="1 2"] { padding: 0.5rem 1rem; }
177
+ [pad-="2 1"] { padding: 1rem 0.5rem; }
178
+ [self-="grow"] { flex-grow: 1; }
100
179
101
- /* ─── Navbar ─── */
102
180
.ja-navbar {
103
181
position: sticky;
104
182
top: 0;
105
183
z-index: 100;
106
- border-bottom: 2px solid var(--foreground0);
107
- background-color: var(--background1);
184
+ background: var(--background0);
185
+ border-bottom: 2px solid var(--background2);
108
186
}
187
+
109
188
.ja-navbar-bar {
189
+ background: var(--background1);
110
190
display: flex;
111
191
align-items: center;
112
- gap: 0.75ch;
113
- padding: 0.3rem 1rem;
114
- background-color: var(--background2);
115
- border-bottom: 1px solid var(--background3);
116
- font-size: 0.75rem;
117
- color: var(--foreground1);
192
+ padding: 0.25rem 1rem;
193
+ gap: 0.5rem;
194
+ font-size: 0.8rem;
195
+ color: var(--foreground2);
118
196
}
119
- .ja-dot { width: 0.8em; height: 0.8em; border-radius: 50%; display: inline-block; }
120
- .ja-dot:nth-child(1) { background: var(--red, #f00); }
121
- .ja-dot:nth-child(2) { background: var(--yellow, #ff0); }
122
- .ja-dot:nth-child(3) { background: var(--green, #0f0); }
123
- .ja-navbar-title { margin-left: 0.5ch; }
197
+
198
+ .ja-dot {
199
+ width: 0.75em;
200
+ height: 0.75em;
201
+ border-radius: 50%;
202
+ display: inline-block;
203
+ }
204
+
205
+ .ja-dot:nth-child(1) { background-color: var(--red); }
206
+ .ja-dot:nth-child(2) { background-color: var(--yellow); }
207
+ .ja-dot:nth-child(3) { background-color: var(--green); }
208
+
124
209
.ja-navbar-body {
125
210
display: flex;
126
- align-items: center;
211
+ flex-direction: row;
127
212
justify-content: space-between;
128
- padding: 0.6rem 1.5rem;
129
- gap: 1rem;
213
+ align-items: center;
214
+ padding: 0.75rem 1.5rem;
130
215
}
216
+
131
217
.ja-nav-brand a {
132
- font-weight: var(--font-weight-bold);
218
+ font-weight: bold;
133
219
color: var(--foreground0);
134
220
text-decoration: none;
135
- font-size: 1.05rem;
221
+ display: flex;
222
+ align-items: center;
223
+ gap: 0.5rem;
136
224
}
137
- .ja-nav-brand .nf { color: var(--green, var(--foreground0)); }
138
- .ja-nav-links { display: flex; gap: 1.5rem; align-items: center; flex-wrap: wrap; }
225
+
226
+ .ja-nav-links {
227
+ display: flex;
228
+ flex-direction: row;
229
+ gap: 1.25rem;
230
+ align-items: center;
231
+ }
232
+
139
233
.ja-nav-links a {
140
234
color: var(--foreground1);
141
235
text-decoration: none;
142
- font-size: 0.9rem;
143
236
}
144
- .ja-nav-links a:hover { color: var(--green, var(--foreground0)); text-decoration: underline; }
145
- .ja-nav-links .nf { font-size: 1rem; }
146
237
147
- /* ─── Layout grid ─── */
238
+ .ja-nav-links a:hover {
239
+ color: var(--foreground0);
240
+ }
241
+
242
+ .ja-search-trigger {
243
+ background: var(--background1);
244
+ border: 1px solid var(--background2);
245
+ border-radius: 4px;
246
+ padding: 0.25rem 0.5rem;
247
+ display: flex;
248
+ align-items: center;
249
+ gap: 0.5rem;
250
+ color: var(--foreground1);
251
+ cursor: pointer;
252
+ }
253
+
254
+ .ja-search-trigger kbd {
255
+ background: var(--background2);
256
+ padding: 0.1rem 0.3rem;
257
+ border-radius: 3px;
258
+ font-size: 0.75em;
259
+ }
260
+
148
261
.ja-layout {
149
262
display: grid;
150
- grid-template-columns: 260px minmax(0, 1fr) 240px;
151
- gap: 0;
152
- min-height: calc(100vh - 70px);
263
+ grid-template-columns: 260px minmax(0, 1fr) 220px;
264
+ min-height: calc(100vh - 100px);
265
+ margin-bottom: 2.2rem;
153
266
}
154
267
155
- /* ─── Left sidebar ─── */
156
268
.ja-sidebar {
157
- border-right: 2px solid var(--foreground0);
158
- background-color: var(--background0);
159
- padding: 1rem 0.5rem;
160
- overflow-y: auto;
161
- max-height: calc(100vh - 70px);
269
+ border-right: 1px solid var(--background2);
270
+ background: var(--background0);
162
271
position: sticky;
163
272
top: 70px;
164
- font-size: 0.85rem;
273
+ overflow-y: auto;
274
+ max-height: calc(100vh - 100px);
275
+ padding: 1.5rem;
276
+ }
277
+
278
+ .ja-nav-group {
279
+ text-transform: uppercase;
280
+ font-size: 0.75rem;
281
+ font-weight: bold;
282
+ color: var(--foreground2);
283
+ margin-top: 1.5rem;
284
+ margin-bottom: 0.5rem;
285
+ }
286
+
287
+ .ja-sidebar .ja-nav-tree ul {
288
+ list-style: none;
289
+ padding-left: 0;
290
+ margin: 0;
165
291
}
166
- .ja-nav-tree a {
292
+
293
+ .ja-sidebar .ja-nav-tree li {
294
+ margin: 0;
295
+ }
296
+
297
+ .ja-sidebar .ja-nav-tree li::before {
298
+ display: none;
299
+ }
300
+
301
+ .ja-sidebar .ja-nav-tree a {
167
302
display: block;
168
- padding: 0.3rem 0.75rem;
169
303
color: var(--foreground1);
170
304
text-decoration: none;
171
- white-space: nowrap;
172
- overflow: hidden;
173
- text-overflow: ellipsis;
305
+ padding: 0.4rem 0.75rem;
306
+ border-radius: 4px;
307
+ margin-bottom: 0.2rem;
174
308
}
175
- .ja-nav-tree a:hover { color: var(--foreground0); background-color: var(--background1); }
176
- .ja-nav-tree a.active {
177
- color: var(--green, var(--foreground0));
178
- font-weight: var(--font-weight-bold);
179
- background-color: var(--background1);
180
- border-left: 3px solid var(--green, var(--foreground0));
309
+
310
+ .ja-sidebar .ja-nav-tree a:hover {
311
+ background: var(--background1);
312
+ color: var(--foreground0);
181
313
}
182
- .ja-nav-group {
183
- margin: 0.75rem 0 0.25rem;
184
- padding: 0.25rem 0.75rem;
185
- font-weight: var(--font-weight-bold);
186
- color: var(--foreground2);
187
- text-transform: uppercase;
188
- font-size: 0.7rem;
189
- letter-spacing: 0.05em;
314
+
315
+ .ja-sidebar .ja-nav-tree a.active {
316
+ color: var(--green);
317
+ font-weight: bold;
318
+ background: var(--background1);
190
319
}
191
- .ja-nav-sub { margin-left: 0.75rem; border-left: 1px solid var(--background2); }
192
320
193
- /* ─── Content ─── */
194
321
.ja-content {
195
- padding: 2rem 2.5rem 3rem;
322
+ padding: 2rem 2.5rem 4rem;
196
323
min-width: 0;
197
324
}
198
- .ja-content article { max-width: 52rem; }
199
325
200
- /* Typography */
201
- .ja-content h1 { font-size: 1.7rem; margin: 0 0 1.5rem; }
202
- .ja-content h2 {
203
- font-size: 1.3rem;
204
- margin: 2.5rem 0 0.75rem;
205
- padding-bottom: 0.4rem;
206
- border-bottom: 1px solid var(--background2);
326
+ .ja-content article {
327
+ max-width: 52rem;
328
+ margin: 0 auto;
329
+ }
330
+
331
+ .ja-content h1::before,
332
+ .ja-content h2::before,
333
+ .ja-content h3::before,
334
+ .ja-content h4::before,
335
+ .ja-content h5::before,
336
+ .ja-content h6::before {
337
+ content: "# ";
338
+ color: var(--green);
207
339
}
208
- .ja-content h3 { font-size: 1.05rem; margin: 2rem 0 0.5rem; }
209
- .ja-content h4 { font-size: 0.95rem; margin: 1.5rem 0 0.5rem; }
210
- .ja-content h1::before, .ja-content h2::before, .ja-content h3::before, .ja-content h4::before {
211
- color: var(--green, var(--foreground0));
340
+
341
+ .ja-content p, .ja-content li {
342
+ color: var(--foreground1);
343
+ margin-bottom: 1rem;
344
+ line-height: 1.6;
212
345
}
213
- .ja-content h1::before { content: '# '; }
214
- .ja-content h2::before { content: '## '; }
215
- .ja-content h3::before { content: '### '; }
216
- .ja-content h4::before { content: '#### '; }
217
346
218
- .ja-content p, .ja-content li { color: var(--foreground1); line-height: var(--line-height); }
219
- .ja-content a { color: var(--blue, var(--foreground2)); text-decoration: underline; text-underline-offset: 3px; }
220
- .ja-content a:hover { color: var(--cyan, var(--foreground0)); }
347
+ .ja-content a:not([is-~="button"]) {
348
+ color: var(--sky);
349
+ text-decoration: underline;
350
+ }
351
+
352
+ .ja-content a:hover:not([is-~="button"]) {
353
+ color: var(--blue);
354
+ }
221
355
222
356
.ja-content :not(pre) > code {
223
- background-color: var(--background2);
224
- color: var(--red, var(--foreground0));
225
- padding: 0 0.75ch;
357
+ background: var(--background2);
358
+ color: var(--red);
359
+ padding: 0.2em 0.4em;
360
+ border-radius: 3px;
361
+ font-size: 0.9em;
226
362
}
227
363
228
- /* Code blocks */
229
364
.ja-content pre {
230
- background-color: var(--background1);
231
- border: 2px solid var(--foreground0);
232
- border-radius: 0;
233
- padding: 1rem 1.25rem;
365
+ background: var(--background1);
366
+ border: 1px solid var(--background2);
367
+ padding: 1rem;
234
368
overflow-x: auto;
235
- font-family: var(--font-family);
236
- font-size: 0.85rem;
237
- line-height: 1.6;
369
+ border-radius: 6px;
370
+ margin-bottom: 1.5rem;
238
371
}
239
- .ja-content pre code { background: none; color: var(--foreground0); padding: 0; }
240
372
241
- /* Tables */
242
- .ja-content table { border-collapse: collapse; margin: 1.25rem 0; font-size: 0.9rem; }
243
- .ja-content th, .ja-content td {
244
- border: 1px solid var(--table-border-color, var(--foreground0));
245
- padding: 0.45rem 1ch;
246
- text-align: left;
373
+ .ja-content blockquote {
374
+ border-left: 4px solid var(--green);
375
+ background: var(--background1);
376
+ padding: 1rem 1.5rem;
377
+ margin: 1.5rem 0;
378
+ border-radius: 0 4px 4px 0;
247
379
}
248
- .ja-content th { color: var(--foreground0); font-weight: var(--font-weight-bold); background-color: var(--background1); }
249
- .ja-content td { color: var(--foreground1); }
250
- .ja-content tbody tr:hover { background-color: var(--background1); }
251
380
252
- /* Blockquote / callouts */
253
- .ja-content blockquote {
254
- margin: 1.25rem 0;
255
- padding: 0.75rem 1.5ch;
256
- border: 2px solid var(--foreground0);
257
- border-left: 0.75ch solid var(--green, var(--foreground0));
258
- background-color: var(--background1);
259
- color: var(--foreground1);
381
+ .ja-content blockquote p {
382
+ margin-bottom: 0;
383
+ }
384
+
385
+ .ja-content img {
386
+ max-width: 100%;
387
+ height: auto;
388
+ border-radius: 8px;
389
+ display: block;
390
+ margin: 1.5rem auto;
391
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
260
392
}
261
- .ja-content blockquote p { margin: 0; }
262
393
263
- /* Lists */
264
- .ja-content ul, .ja-content ol { color: var(--foreground1); }
394
+ [data-webtui-theme=dark] .ja-content img,
395
+ [data-webtui-theme=catppuccin] .ja-content img {
396
+ filter: invert(0.88) hue-rotate(180deg);
397
+ }
265
398
266
- /* Footer */
267
- .ja-footer {
268
- margin-top: 3rem;
269
- padding-top: 1rem;
270
- border-top: 2px solid var(--background2);
271
- font-size: 0.8rem;
272
- color: var(--foreground2);
399
+ .ja-doc-nav {
400
+ display: flex;
401
+ justify-content: space-between;
402
+ margin-top: 4rem;
403
+ padding-top: 2rem;
404
+ border-top: 1px solid var(--background2);
405
+ }
406
+
407
+ .ja-doc-nav a {
408
+ display: flex;
409
+ align-items: center;
410
+ gap: 0.5rem;
411
+ padding: 1rem 1.5rem;
412
+ background: var(--background1);
413
+ border: 1px solid var(--background2);
414
+ border-radius: 8px;
415
+ text-decoration: none !important;
416
+ color: var(--foreground0) !important;
417
+ font-weight: bold;
418
+ transition: transform 0.2s, box-shadow 0.2s, border-color 0.2s;
419
+ }
420
+
421
+ .ja-doc-nav a:hover {
422
+ border-color: var(--green);
423
+ transform: translateY(-2px);
424
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
425
+ }
426
+
427
+ .ja-content table {
428
+ border-collapse: collapse;
429
+ width: 100%;
430
+ margin: 1.5rem 0;
431
+ background: var(--background1);
432
+ }
433
+
434
+ .ja-content th, .ja-content td {
435
+ border: 1px solid var(--background2);
436
+ padding: 0.75rem 1rem;
437
+ text-align: left;
438
+ }
439
+
440
+ .ja-content th {
441
+ background: var(--background2);
442
+ font-weight: bold;
443
+ color: var(--foreground0);
273
444
}
274
- .ja-footer a { color: var(--foreground2); }
275
445
276
- /* ─── Right TOC ─── */
277
446
.ja-toc-sidebar {
278
- border-left: 2px solid var(--foreground0);
279
- background-color: var(--background0);
280
- padding: 1.5rem 1rem;
281
- font-size: 0.82rem;
282
- overflow-y: auto;
283
- max-height: calc(100vh - 70px);
447
+ border-left: 1px solid var(--background2);
284
448
position: sticky;
285
449
top: 70px;
450
+ height: max-content;
451
+ max-height: calc(100vh - 100px);
452
+ overflow-y: auto;
453
+ padding: 1.5rem;
454
+ font-size: 0.82rem;
286
455
}
456
+
287
457
.ja-toc-title {
288
- font-weight: var(--font-weight-bold);
289
- color: var(--foreground2);
290
458
text-transform: uppercase;
291
- font-size: 0.7rem;
292
- letter-spacing: 0.05em;
293
- margin-bottom: 0.75rem;
459
+ font-size: 0.75rem;
460
+ font-weight: bold;
461
+ color: var(--foreground2);
462
+ margin-bottom: 1rem;
463
+ }
464
+
465
+ .ja-toc ul {
466
+ list-style: none;
467
+ padding: 0;
468
+ margin: 0;
294
469
}
470
+
471
+ .ja-toc li {
472
+ margin-bottom: 0.5rem;
473
+ }
474
+
295
475
.ja-toc a {
296
476
display: block;
297
- padding: 0.25rem 0;
477
+ padding: 0.2rem 0;
298
478
color: var(--foreground1);
299
479
text-decoration: none;
480
+ line-height: 1.4;
481
+ }
482
+
483
+ .ja-toc a:hover {
484
+ color: var(--green);
485
+ }
486
+
487
+ .ja-toc a.h3 {
488
+ padding-left: 1rem;
489
+ }
490
+
491
+ #statusline {
492
+ position: fixed;
493
+ bottom: 0;
494
+ left: 0;
495
+ right: 0;
496
+ z-index: 999;
497
+ background: var(--background1);
498
+ border-top: 1px solid var(--background2);
499
+ display: flex;
500
+ flex-direction: row;
501
+ justify-content: space-between;
502
+ align-items: center;
503
+ padding: 0 1rem;
504
+ height: 2.2rem;
505
+ font-size: 0.85rem;
506
+ color: var(--foreground1);
507
+ }
508
+
509
+ #statusline .statusline-left, #statusline .statusline-right {
510
+ display: flex;
511
+ gap: 1rem;
512
+ align-items: center;
300
513
}
301
- .ja-toc a:hover { color: var(--green, var(--foreground0)); }
302
- .ja-toc a.h2 { padding-left: 0.75rem; }
303
- .ja-toc a.h3 { padding-left: 1.5rem; }
304
514
305
- /* ─── Theme switcher ─── */
306
515
.ja-theme-switcher {
307
516
position: fixed;
308
- bottom: 1rem;
517
+ bottom: 2.3rem;
309
518
right: 1rem;
310
519
z-index: 1000;
311
520
display: flex;
521
+ flex-direction: row;
312
522
gap: 0.25rem;
313
- padding: 0.4rem;
314
- border: 2px solid var(--foreground0);
315
- background-color: var(--background0);
523
+ background: var(--background1);
524
+ border: 1px solid var(--background2);
525
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
526
+ padding: 0.25rem;
527
+ border-radius: 6px;
316
528
}
529
+
317
530
.ja-theme-btn {
318
- font-family: 'Symbols Nerd Font';
319
- font-size: 1.05rem;
320
- line-height: 1;
321
- width: 2.1rem;
322
- height: 2.1rem;
323
- padding: 0;
324
- border: 1px solid var(--foreground2);
325
- background: none;
326
- color: var(--foreground0);
531
+ background: transparent;
532
+ border: none;
533
+ color: var(--foreground1);
534
+ padding: 0.25rem 0.5rem;
327
535
cursor: pointer;
328
- transition: transform 0.15s ease, background-color 0.15s ease;
536
+ border-radius: 4px;
537
+ font-size: 0.85rem;
329
538
}
330
- .ja-theme-btn:hover { transform: scale(1.15); background-color: var(--background2); }
331
- .ja-theme-btn.active { border: 2px solid var(--foreground0); color: var(--green, var(--foreground0)); }
332
539
333
- /* ─── Landing components ─── */
334
- .ja-hero { text-align: center; padding: 3rem 1rem 2rem; }
335
- .ja-hero h1 { font-size: clamp(2rem, 5vw, 3rem); margin: 1rem 0; }
336
- .ja-hero h1::before { content: ''; }
337
- .ja-hero h1 .accent { color: var(--green, var(--foreground0)); }
338
- .ja-hero-sub { max-width: 42rem; margin: 0 auto 2rem; color: var(--foreground1); font-size: 1.05rem; }
339
- .ja-hero-actions { display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; margin-bottom: 1rem; }
340
- .ja-hero-actions a { font-family: var(--font-family); font-size: var(--font-size); }
540
+ .ja-theme-btn:hover {
541
+ background: var(--background2);
542
+ color: var(--foreground0);
543
+ }
544
+
545
+ .ja-theme-btn.active {
546
+ background: var(--green);
547
+ color: var(--background0);
548
+ font-weight: bold;
549
+ }
550
+
551
+ #search-dialog {
552
+ position: fixed;
553
+ inset: 0;
554
+ z-index: 2000;
555
+ display: none;
556
+ align-items: flex-start;
557
+ justify-content: center;
558
+ padding-top: 10vh;
559
+ background: rgba(0, 0, 0, 0.5);
560
+ backdrop-filter: blur(4px);
561
+ }
562
+
563
+ #search-dialog[open] {
564
+ display: flex;
565
+ }
566
+
567
+ .ja-search-box {
568
+ width: 100%;
569
+ max-width: 620px;
570
+ background: var(--background0);
571
+ border: 1px solid var(--background2);
572
+ border-radius: 8px;
573
+ box-shadow: 0 10px 25px rgba(0,0,0,0.2);
574
+ display: flex;
575
+ flex-direction: column;
576
+ overflow: hidden;
577
+ }
578
+
579
+ .ja-search-input {
580
+ width: 100%;
581
+ padding: 1rem 1.5rem;
582
+ font-size: 1.1rem;
583
+ background: transparent;
584
+ border: none;
585
+ border-bottom: 1px solid var(--background2);
586
+ color: var(--foreground0);
587
+ }
588
+
589
+ .ja-search-input:focus {
590
+ outline: none;
591
+ border-bottom-color: var(--green);
592
+ }
593
+
594
+ .ja-search-results {
595
+ max-height: 400px;
596
+ overflow-y: auto;
597
+ padding: 0.5rem 0;
598
+ }
599
+
600
+ .ja-search-item {
601
+ display: block;
602
+ padding: 0.75rem 1.5rem;
603
+ color: var(--foreground0);
604
+ text-decoration: none;
605
+ }
606
+
607
+ .ja-search-item:hover, .ja-search-item[aria-selected="true"] {
608
+ background: var(--background1);
609
+ }
610
+
611
+ #intro-backdrop {
612
+ background: var(--crust);
613
+ min-height: 70vh;
614
+ display: flex;
615
+ align-items: center;
616
+ justify-content: center;
617
+ position: relative;
618
+ overflow: hidden;
619
+ padding: 2rem;
620
+ }
621
+
622
+ #widgets-grid {
623
+ position: absolute;
624
+ inset: 0;
625
+ display: grid;
626
+ grid-template-columns: repeat(3, 1fr);
627
+ gap: 1rem;
628
+ opacity: 0.35;
629
+ padding: 2rem;
630
+ pointer-events: none;
631
+ }
632
+
633
+ .terminal-widget {
634
+ background: var(--background0);
635
+ border: 1px solid var(--background2);
636
+ border-radius: 6px;
637
+ padding: 1rem;
638
+ font-size: 0.75rem;
639
+ color: var(--foreground1);
640
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
641
+ }
642
+
643
+ #main-hero {
644
+ z-index: 10;
645
+ background: var(--background0);
646
+ max-width: 64ch;
647
+ width: 100%;
648
+ box-sizing: border-box;
649
+ text-align: center;
650
+ border: 2px solid var(--foreground0);
651
+ box-shadow: 8px 8px 0 var(--foreground0);
652
+ padding: 3rem 2rem;
653
+ border-radius: 8px;
654
+ }
655
+
656
+ .ja-hero h1 {
657
+ font-size: clamp(2rem, 5vw, 3.5rem);
658
+ margin-bottom: 1rem;
659
+ line-height: 1.2;
660
+ color: var(--foreground0);
661
+ }
662
+
663
+ .ja-hero h1 .accent {
664
+ color: var(--green);
665
+ }
666
+
667
+ .ja-hero-sub {
668
+ font-size: 1.2rem;
669
+ color: var(--foreground2);
670
+ margin-bottom: 2rem;
671
+ }
672
+
673
+ .ja-hero-actions {
674
+ display: flex;
675
+ flex-direction: row;
676
+ gap: 1rem;
677
+ justify-content: center;
678
+ }
679
+
680
+ .ja-section {
681
+ display: flex;
682
+ align-items: center;
683
+ gap: 1ch;
684
+ margin: 2.5rem 0 1rem;
685
+ font-size: 1.05rem;
686
+ font-weight: bold;
687
+ color: var(--foreground0);
688
+ }
689
+
690
+ .ja-section::before {
691
+ content: '\f489';
692
+ font-family: 'Symbols Nerd Font';
693
+ color: var(--green);
694
+ }
695
+
696
+ .ja-section::after {
697
+ content: '';
698
+ flex: 1;
699
+ border-bottom: 1px solid var(--background2);
700
+ }
341
701
342
702
.ja-stats {
343
703
display: grid;
344
- grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
345
- gap: 0.75rem;
346
- margin: 2rem 0;
704
+ grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
705
+ gap: 1rem;
347
706
}
707
+
348
708
.ja-stat {
349
- border: 2px solid var(--foreground0);
350
- background-color: var(--background1);
351
- padding: 1rem 0.5rem;
709
+ background: var(--background1);
710
+ border: 1px solid var(--background2);
711
+ border-radius: 8px;
712
+ padding: 1.5rem;
352
713
text-align: center;
353
714
}
354
- .ja-stat-value { font-size: 1.5rem; font-weight: var(--font-weight-bold); color: var(--green, var(--foreground0)); }
355
- .ja-stat-label { font-size: 0.8rem; color: var(--foreground1); }
356
715
357
- .ja-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 0.75rem; margin: 1.5rem 0; }
358
- .ja-card {
359
- border: 2px solid var(--foreground0);
360
- background-color: var(--background0);
361
- padding: 1.25rem;
362
- transition: background-color 0.15s ease;
716
+ .ja-stat-value {
717
+ color: var(--green);
718
+ font-size: 1.6rem;
719
+ font-weight: bold;
720
+ margin-bottom: 0.5rem;
721
+ }
722
+
723
+ .ja-stat-label {
724
+ color: var(--foreground2);
725
+ font-size: 0.9rem;
726
+ text-transform: uppercase;
727
+ }
728
+
729
+ .ja-tabs {
730
+ border: 1px solid var(--background2);
731
+ background: var(--background0);
732
+ border-radius: 8px;
733
+ overflow: hidden;
734
+ }
735
+
736
+ .ja-tabs-bar {
737
+ display: flex;
738
+ flex-direction: row;
739
+ border-bottom: 1px solid var(--background2);
740
+ background: var(--background1);
741
+ }
742
+
743
+ .ja-tab-btn {
744
+ padding: 0.75rem 1.5rem;
745
+ background: transparent;
746
+ border: none;
747
+ color: var(--foreground1);
748
+ cursor: pointer;
749
+ font-size: 0.9rem;
750
+ }
751
+
752
+ .ja-tab-btn:hover {
753
+ background: var(--background2);
754
+ }
755
+
756
+ .ja-tab-btn.active {
757
+ border-bottom: 2px solid var(--green);
758
+ color: var(--foreground0);
759
+ font-weight: bold;
760
+ }
761
+
762
+ .ja-tab-panel {
763
+ padding: 1.5rem;
764
+ }
765
+
766
+ .ja-terminal {
767
+ border: 1px solid var(--background2);
768
+ background: var(--background1);
769
+ border-radius: 8px;
770
+ overflow: hidden;
363
771
}
364
- .ja-card:hover { background-color: var(--background1); }
365
- .ja-card-icon { font-family: 'Symbols Nerd Font'; font-size: 1.3rem; color: var(--green, var(--foreground0)); margin-bottom: 0.5rem; }
366
- .ja-card h3 { margin: 0 0 0.5rem; font-size: 1rem; }
367
- .ja-card h3::before { content: ''; }
368
- .ja-card p { font-size: 0.85rem; color: var(--foreground1); margin: 0; line-height: 1.5; }
369
772
370
- .ja-terminal { border: 2px solid var(--foreground0); background-color: var(--background1); margin: 1.5rem 0; }
371
773
.ja-terminal-bar {
372
- display: flex; align-items: center; gap: 1ch;
774
+ background: var(--background2);
373
775
padding: 0.5rem 1rem;
374
- border-bottom: 2px solid var(--foreground0);
375
- background-color: var(--background2);
776
+ display: flex;
777
+ align-items: center;
778
+ gap: 0.5rem;
376
779
font-size: 0.8rem;
780
+ color: var(--foreground1);
781
+ }
782
+
783
+ .ja-terminal-body {
784
+ padding: 1rem;
785
+ font-family: var(--font-family);
786
+ font-size: 0.9rem;
787
+ line-height: 1.5;
788
+ overflow-x: auto;
377
789
}
378
- .ja-terminal-title { color: var(--foreground0); margin-left: 1ch; }
379
- .ja-terminal-body { padding: 1rem 1.5rem; font-size: 0.85rem; line-height: 1.7; color: var(--green, var(--foreground0)); overflow-x: auto; }
790
+
380
791
.ja-terminal-body .cm { color: var(--foreground2); }
381
- .ja-terminal-body .ok { color: var(--green, var(--foreground0)); }
382
- .ja-terminal-body .dim { color: var(--foreground2); }
383
-
384
- .ja-bars { margin: 1.5rem 0; display: flex; flex-direction: column; gap: 0.6rem; }
385
- .ja-bar-row { display: grid; grid-template-columns: 8rem 1fr 4.5rem; align-items: center; gap: 1ch; font-size: 0.85rem; }
386
- .ja-bar-label { text-align: right; color: var(--foreground1); }
387
- .ja-bar-row.winner .ja-bar-label { color: var(--green, var(--foreground0)); font-weight: var(--font-weight-bold); }
388
- .ja-bar-track { height: 1.5rem; border: 2px solid var(--foreground0); background-color: var(--background0); padding: 2px; }
389
- .ja-bar-fill { height: 100%; width: 0%; background-color: var(--green, var(--foreground0)); transition: width 1.2s cubic-bezier(0.16, 1, 0.3, 1); }
390
- .ja-bar-value { color: var(--foreground0); text-align: right; }
391
-
392
- .ja-section { display: flex; align-items: center; gap: 1ch; margin: 2.5rem 0 1rem; font-size: 1.05rem; font-weight: var(--font-weight-bold); color: var(--foreground0); }
393
- .ja-section::before { content: '\f489'; font-family: 'Symbols Nerd Font'; color: var(--green, var(--foreground0)); }
394
- .ja-section::after { content: ''; flex: 1; border-bottom: 2px solid var(--background2); }
395
-
396
- .ja-cta { border: 2px solid var(--foreground0); background-color: var(--background1); padding: 2.5rem 1rem; text-align: center; margin: 2rem 0; }
397
- .ja-cta h2 { border: none; margin: 0 0 0.5rem; }
398
- .ja-cta h2::before { content: ''; }
399
- .ja-cta p { color: var(--foreground1); margin: 0 0 1.5rem; }
400
-
401
- /* Responsive */
402
- @media (max-width: 1100px) {
403
- .ja-layout { grid-template-columns: 220px minmax(0, 1fr); }
404
- .ja-toc-sidebar { display: none; }
792
+ .ja-terminal-body .ok { color: var(--green); }
793
+ .ja-terminal-body .dim { color: var(--foreground2); opacity: 0.7; }
794
+
795
+ .ja-bars {
796
+ display: flex;
797
+ flex-direction: column;
798
+ gap: 1rem;
405
799
}
406
- @media (max-width: 800px) {
407
- .ja-layout { grid-template-columns: 1fr; }
408
- .ja-sidebar { display: none; }
409
- .ja-content { padding: 1.5rem 1rem 2rem; }
410
- .ja-bar-row { grid-template-columns: 5.5rem 1fr 3.5rem; }
411
- .ja-stats { grid-template-columns: repeat(2, 1fr); }
800
+
801
+ .ja-bar-row {
802
+ display: grid;
803
+ grid-template-columns: 100px 1fr 60px;
804
+ gap: 1rem;
805
+ align-items: center;
412
806
}
413
807
414
- ::selection { background: var(--foreground0); color: var(--background0); }
415
- :focus-visible { outline: 2px solid var(--foreground0); outline-offset: 2px; }
416
-}
808
+ .ja-bar-track {
809
+ background: var(--background2);
810
+ height: 12px;
811
+ border-radius: 6px;
812
+ overflow: hidden;
813
+ }
417
814
418
-/* ─── Landing tabs ─── */
419
-@layer components {
420
- .ja-tabs { border: 2px solid var(--foreground0); background-color: var(--background0); margin: 1.5rem 0; }
421
- .ja-tabs-bar { display: flex; border-bottom: 2px solid var(--foreground0); background-color: var(--background1); overflow-x: auto; }
422
- .ja-tab-btn {
423
- font-family: var(--font-family);
424
- padding: 0.5rem 2ch;
815
+ .ja-bar-fill {
816
+ background: var(--green);
817
+ height: 100%;
818
+ transition: width 1s ease-out;
819
+ }
820
+
821
+ .ja-grid {
822
+ display: grid;
823
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
824
+ gap: 1.5rem;
825
+ }
826
+
827
+ .ja-card {
828
+ background: var(--background1);
829
+ border: 1px solid var(--background2);
830
+ border-radius: 8px;
831
+ padding: 1.5rem;
832
+ transition: transform 0.2s, box-shadow 0.2s;
833
+ }
834
+
835
+ .ja-card:hover {
836
+ transform: translateY(-2px);
837
+ box-shadow: 0 8px 15px rgba(0,0,0,0.1);
838
+ border-color: var(--green);
839
+ }
840
+
841
+ .ja-card-icon {
842
+ font-family: 'Symbols Nerd Font';
843
+ font-size: 2rem;
844
+ color: var(--green);
845
+ margin-bottom: 1rem;
846
+ }
847
+
848
+ .ja-card h3 {
849
+ margin-top: 0;
850
+ margin-bottom: 0.5rem;
851
+ }
852
+
853
+ .ja-cta {
854
+ background: var(--background1);
855
+ border: 1px solid var(--background2);
856
+ border-radius: 8px;
857
+ padding: 3rem 2rem;
858
+ text-align: center;
859
+ margin-top: 4rem;
860
+ }
861
+
862
+ .ja-cta h2 {
863
+ margin-top: 0;
864
+ }
865
+
866
+ .ja-footer {
867
+ padding: 2rem;
868
+ text-align: center;
425
869
font-size: 0.85rem;
870
+ color: var(--foreground2);
871
+ border-top: 1px solid var(--background2);
872
+ }
873
+
874
+ .ja-footer a {
426
875
color: var(--foreground1);
427
- background: none;
428
- border: none;
429
- border-right: 1px solid var(--background2);
430
- cursor: pointer;
876
+ text-decoration: none;
877
+ }
878
+
879
+ .ja-footer a:hover {
880
+ color: var(--green);
881
+ }
882
+
883
+ @media (max-width: 1100px) {
884
+ .ja-layout {
885
+ grid-template-columns: 260px minmax(0, 1fr);
886
+ }
887
+ .ja-toc-sidebar {
888
+ display: none;
889
+ }
890
+ }
891
+
892
+ @media (max-width: 800px) {
893
+ .ja-layout {
894
+ grid-template-columns: 1fr;
895
+ }
896
+ .ja-sidebar {
897
+ display: none;
898
+ }
899
+ .ja-content {
900
+ padding: 1.5rem 1rem 3rem;
901
+ }
902
+ #widgets-grid {
903
+ display: none;
904
+ }
905
+ #statusline .right {
906
+ display: none;
907
+ }
908
+ }
909
+
910
+ ::selection {
911
+ background: var(--foreground0);
912
+ color: var(--background0);
913
+ }
914
+
915
+ :focus-visible {
916
+ outline: 2px solid var(--green);
917
+ outline-offset: 2px;
431
918
}
432
- .ja-tab-btn:hover { background-color: var(--background2); color: var(--foreground0); }
433
- .ja-tab-btn[data-active='true'] { background-color: var(--foreground0); color: var(--background0); font-weight: var(--font-weight-bold); }
434
- .ja-tab-panel { padding: 1rem; }
435
- .ja-tab-panel pre { margin: 0; border: none; background: none; }
436
919
}
437
920
β–Όwebtui/src/main.js
+155-26
1
-// JustAPI Docs β€” terminal UI (pure JS, no framework)
1
+// JustAPI Docs β€” WebTUI Terminal UI Client Controller
2
2
(function () {
3
3
'use strict';
4
4
5
- // ─── Theme switcher ───
6
- var THEMES = ['dark', 'light', 'catppuccin', 'everforest', 'nord'];
7
- var ICONS = {
8
- dark: '\uf1db', // nf-fa-moon
9
- light: '\uf185', // nf-fa-sun
10
- catppuccin: '\uf870', // nf-md-palette
11
- everforest: '\uf1a0', // nf-fa-leaf
12
- nord: '\uf0c8', // nf-fa-square
13
- };
5
+ // ─── Theme Switcher System ───
6
+ var THEMES = [
7
+ { id: 'catppuccin', name: 'Catppuccin', icon: '\uf870' },
8
+ { id: 'dark', name: 'Dark', icon: '\uf1db' },
9
+ { id: 'light', name: 'Light', icon: '\uf185' }
10
+ ];
14
11
15
12
function applyTheme(t) {
16
13
document.documentElement.setAttribute('data-webtui-theme', t);
24
21
function initThemeSwitcher() {
25
22
var saved = null;
26
23
try { saved = localStorage.getItem('ja-theme'); } catch (e) {}
27
- var w = document.getElementById('themeSwitcher');
28
- if (!w) return;
24
+ var container = document.getElementById('themeSwitcher');
25
+ if (!container) return;
26
+ container.innerHTML = '';
27
+
29
28
THEMES.forEach(function (t) {
30
- var b = document.createElement('button');
31
- b.className = 'ja-theme-btn';
32
- b.dataset.theme = t;
33
- b.title = t;
34
- b.setAttribute('aria-label', 'Theme: ' + t);
35
- b.innerHTML = ICONS[t];
36
- b.addEventListener('click', function () { applyTheme(t); });
37
- w.appendChild(b);
29
+ var btn = document.createElement('button');
30
+ btn.type = 'button';
31
+ btn.className = 'ja-theme-btn';
32
+ btn.dataset.theme = t.id;
33
+ btn.title = 'Switch theme to ' + t.name;
34
+ btn.setAttribute('aria-label', 'Theme: ' + t.name);
35
+ btn.innerHTML = '<span class="nf">' + t.icon + '</span> ' + t.name;
36
+ btn.addEventListener('click', function () { applyTheme(t.id); });
37
+ container.appendChild(btn);
38
38
});
39
- applyTheme(saved && THEMES.indexOf(saved) !== -1 ? saved : 'catppuccin');
39
+
40
+ var validTheme = THEMES.some(function (t) { return t.id === saved; });
41
+ applyTheme(validTheme ? saved : 'catppuccin');
40
42
}
41
43
42
- // ─── Terminal tabs ───
44
+ // ─── Quick Navigation Pages Index for Search Modal ───
45
+ var SEARCH_PAGES = [
46
+ { title: "Overview & Philosophy", url: "/getting-started/overview/" },
47
+ { title: "Installation", url: "/getting-started/installation/" },
48
+ { title: "First Steps", url: "/getting-started/first-steps/" },
49
+ { title: "CLI Scaffolder", url: "/getting-started/cli-scaffolder/" },
50
+ { title: "Migrate from FastAPI", url: "/getting-started/migrating-from-fastapi/" },
51
+ { title: "Hello World Tutorial", url: "/tutorials/hello-world/" },
52
+ { title: "Path Parameters", url: "/tutorials/path-params/" },
53
+ { title: "Query Parameters", url: "/tutorials/query-params/" },
54
+ { title: "Request Body", url: "/tutorials/request-body/" },
55
+ { title: "Error Handling", url: "/tutorials/error-handling/" },
56
+ { title: "Dependency Injection", url: "/tutorials/dependency-injection/" },
57
+ { title: "Middleware", url: "/tutorials/middleware/" },
58
+ { title: "CORS", url: "/tutorials/cors/" },
59
+ { title: "Database Integration", url: "/tutorials/database-integration/" },
60
+ { title: "Background Tasks", url: "/tutorials/background-tasks/" },
61
+ { title: "Testing", url: "/tutorials/testing/" },
62
+ { title: "Static Files", url: "/tutorials/static-files/" },
63
+ { title: "Zero-GIL Architecture", url: "/advanced/zero-gil-architecture/" },
64
+ { title: "Rust Core Deep Dive", url: "/advanced/rust-core-deep-dive/" },
65
+ { title: "Native Fast Path", url: "/advanced/native-fast-path/" },
66
+ { title: "Streaming Output", url: "/advanced/streaming-output/" },
67
+ { title: "WebSockets Advanced", url: "/advanced/websockets-advanced/" },
68
+ { title: "Templates", url: "/advanced/templates/" },
69
+ { title: "Performance Tuning", url: "/advanced/performance-tuning/" },
70
+ { title: "Resilience Patterns", url: "/advanced/resilience-patterns/" },
71
+ { title: "JustAPIApp API Reference", url: "/api-reference/justapiapp/" },
72
+ { title: "Routing API Reference", url: "/api-reference/routing/" },
73
+ { title: "Request API Reference", url: "/api-reference/request/" },
74
+ { title: "Responses API Reference", url: "/api-reference/responses/" },
75
+ { title: "Dependency Injection API Reference", url: "/api-reference/dependency-injection/" },
76
+ { title: "Exceptions API Reference", url: "/api-reference/exceptions/" },
77
+ { title: "Schema Validation API Reference", url: "/api-reference/schema-validation/" },
78
+ { title: "WebSockets API Reference", url: "/api-reference/websockets/" },
79
+ { title: "Background Tasks API Reference", url: "/api-reference/background-tasks/" },
80
+ { title: "Scheduler API Reference", url: "/api-reference/scheduler/" },
81
+ { title: "Database API Reference", url: "/api-reference/database/" },
82
+ { title: "Testing Client API Reference", url: "/api-reference/testing-client/" },
83
+ { title: "UploadFile API Reference", url: "/api-reference/uploadfile/" },
84
+ { title: "Docker Deployment", url: "/deployment/docker/" },
85
+ { title: "Kubernetes & Helm Deployment", url: "/deployment/kubernetes-helm/" },
86
+ { title: "Cloudflare Pages Deployment", url: "/deployment/cloudflare-pages/" },
87
+ { title: "Production Checklist", url: "/deployment/production-checklist/" },
88
+ { title: "Security Policy", url: "/security/policy/" },
89
+ { title: "OWASP Compliance", url: "/security/owasp-compliance/" },
90
+ { title: "Secure Configuration", url: "/security/secure-configuration/" },
91
+ { title: "Metrics & Monitoring", url: "/observability/metrics-monitoring/" },
92
+ { title: "OpenTelemetry Tracing", url: "/observability/opentelemetry/" },
93
+ { title: "Structured Logging", url: "/observability/structured-logging/" },
94
+ { title: "Health Checks", url: "/observability/health-checks/" },
95
+ { title: "CLI Command Reference", url: "/reference/cli/" },
96
+ { title: "Configuration Reference", url: "/reference/configuration/" },
97
+ { title: "API Stability", url: "/reference/api-stability/" },
98
+ { title: "Release Notes", url: "/reference/release-notes/" },
99
+ { title: "Glossary", url: "/reference/glossary/" }
100
+ ];
101
+
102
+ // ─── Search Dialog Controller ───
103
+ function initSearchModal() {
104
+ var dialog = document.getElementById('search-dialog');
105
+ var openBtn = document.getElementById('openSearchBtn');
106
+ var closeBtn = document.getElementById('closeSearchBtn');
107
+ var input = document.getElementById('searchInput');
108
+ var results = document.getElementById('searchResults');
109
+
110
+ if (!dialog || !openBtn || !input || !results) return;
111
+
112
+ function renderResults(query) {
113
+ results.innerHTML = '';
114
+ var q = (query || '').toLowerCase().trim();
115
+ var filtered = SEARCH_PAGES.filter(function (p) {
116
+ return !q || p.title.toLowerCase().indexOf(q) !== -1 || p.url.toLowerCase().indexOf(q) !== -1;
117
+ });
118
+
119
+ if (!filtered.length) {
120
+ results.innerHTML = '<div style="padding: 1rem; color: var(--foreground2);">No matching documentation pages found</div>';
121
+ return;
122
+ }
123
+
124
+ filtered.forEach(function (p, idx) {
125
+ var a = document.createElement('a');
126
+ a.className = 'ja-search-item' + (idx === 0 ? ' active' : '');
127
+ a.href = p.url;
128
+ a.innerHTML = '<span style="color: var(--green);">&#xf105;</span> ' + p.title + ' <span style="font-size: 0.75rem; color: var(--foreground2); float: right;">' + p.url + '</span>';
129
+ results.appendChild(a);
130
+ });
131
+ }
132
+
133
+ function openModal() {
134
+ dialog.setAttribute('open', '');
135
+ input.value = '';
136
+ renderResults('');
137
+ setTimeout(function () { input.focus(); }, 50);
138
+ }
139
+
140
+ function closeModal() {
141
+ dialog.removeAttribute('open');
142
+ }
143
+
144
+ openBtn.addEventListener('click', openModal);
145
+ if (closeBtn) closeBtn.addEventListener('click', closeModal);
146
+
147
+ dialog.addEventListener('click', function (e) {
148
+ if (e.target === dialog) closeModal();
149
+ });
150
+
151
+ input.addEventListener('input', function () {
152
+ renderResults(input.value);
153
+ });
154
+
155
+ document.addEventListener('keydown', function (e) {
156
+ if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'k') {
157
+ e.preventDefault();
158
+ if (dialog.hasAttribute('open')) closeModal();
159
+ else openModal();
160
+ } else if (e.key === 'Escape' && dialog.hasAttribute('open')) {
161
+ closeModal();
162
+ }
163
+ });
164
+ }
165
+
166
+ // ─── Terminal Tabs ───
43
167
function initTabs() {
44
168
document.querySelectorAll('.ja-tabs').forEach(function (tabs) {
45
169
var btns = tabs.querySelectorAll('[data-tab-btn]');
54
178
});
55
179
}
56
180
57
- // ─── Benchmark bars ───
181
+ // ─── Benchmark Bars Animation ───
58
182
function initBars() {
59
183
var bars = document.querySelectorAll('.ja-bar-fill');
60
184
if (!bars.length || !('IntersectionObserver' in window)) return;
65
189
io.unobserve(e.target);
66
190
}
67
191
});
68
- }, { threshold: 0.4 });
192
+ }, { threshold: 0.3 });
69
193
bars.forEach(function (b) { io.observe(b); });
70
194
}
71
195
72
- // ─── Sidebar active link + mobile toggle ───
196
+ // ─── Sidebar Link Highlight ───
73
197
function initSidebar() {
74
198
var path = window.location.pathname;
75
199
document.querySelectorAll('.ja-nav-tree a').forEach(function (a) {
76
- if (a.getAttribute('href') === path) a.classList.add('active');
200
+ var href = a.getAttribute('href');
201
+ if (href === path || (href !== '/' && path.indexOf(href) === 0)) {
202
+ a.classList.add('active');
203
+ }
77
204
});
78
205
}
79
206
80
207
if (document.readyState === 'loading') {
81
208
document.addEventListener('DOMContentLoaded', function () {
82
209
initThemeSwitcher();
210
+ initSearchModal();
83
211
initTabs();
84
212
initBars();
85
213
initSidebar();
86
214
});
87
215
} else {
88
216
initThemeSwitcher();
217
+ initSearchModal();
89
218
initTabs();
90
219
initBars();
91
220
initSidebar();
92
221
β–Όwebtui/templates/base.html
+50-10
3
3
<head>
4
4
<meta charset="utf-8">
5
5
<meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>{{ title }} β€” JustAPI</title>
6
+ <title>{{ title }} β€” JustAPI Docs</title>
7
7
<meta name="description" content="{{ description }}">
8
+ <!-- SEO & Open Graph -->
9
+ <meta name="google-site-verification" content="OGwxuIzhp_huelFM3j_dzbm2bESKAhpE2LA5RRnsQUA" />
10
+ <meta property="og:title" content="{{ title }} β€” JustAPI Docs">
11
+ <meta property="og:description" content="{{ description }}">
12
+ <meta property="og:type" content="article">
13
+ <meta name="twitter:card" content="summary_large_image">
14
+ <meta name="twitter:title" content="{{ title }} β€” JustAPI Docs">
15
+ <meta name="twitter:description" content="{{ description }}">
16
+ <link rel="canonical" href="https://justapi.pages.dev{{ url }}">
8
17
<link rel="preconnect" href="https://fonts.googleapis.com">
9
18
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
- <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
19
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600;700&display=swap" rel="stylesheet">
11
20
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/ryanoasis/nerd-fonts@master/css/nerd-fonts-generated.css">
12
21
<link rel="stylesheet" href="/css/main.css">
13
22
</head>
14
23
<body>
15
- <!-- ─── Terminal Navbar ─── -->
24
+ <!-- ─── WebTUI Terminal Navbar ─── -->
16
25
<header class="ja-navbar">
17
26
<div class="ja-navbar-bar">
18
27
<span class="ja-dot"></span><span class="ja-dot"></span><span class="ja-dot"></span>
20
29
</div>
21
30
<div class="ja-navbar-body">
22
31
<div class="ja-nav-brand">
23
- <a href="/"><span class="nf">&#xf120;</span> JUSTAPI</a>
32
+ <a href="/"><span class="nf">&#xf120;</span> &lt;/<span class="accent">#</span>&gt;JUSTAPI</a>
24
33
</div>
25
34
<nav class="ja-nav-links">
26
35
<a href="/">Home</a>
28
37
<a href="/tutorials/">Tutorials</a>
29
38
<a href="/api-reference/">API</a>
30
39
<a href="/deployment/">Deploy</a>
31
- <a href="https://github.com/swadhinbiswas/JustAPI" target="_blank"><span class="nf">&#xf09b;</span></a>
40
+ <a href="https://github.com/swadhinbiswas/JustAPI" target="_blank" rel="noopener"><span class="nf">&#xf09b;</span> GitHub</a>
41
+ <button type="button" class="ja-search-trigger" id="openSearchBtn">
42
+ <span class="nf">&#xea6d;</span> Search <kbd>Ctrl+K</kbd>
43
+ </button>
32
44
</nav>
33
45
</div>
34
46
</header>
35
47
36
48
<div class="ja-layout">
37
- <!-- ─── Left sidebar: docs nav ─── -->
49
+ <!-- ─── Left Sidebar: Docs Navigation Tree ─── -->
38
50
<aside class="ja-sidebar">
39
51
<nav class="ja-nav-tree">
40
52
{{ sidebar }}
41
53
</nav>
42
54
</aside>
43
55
44
- <!-- ─── Main content ─── -->
56
+ <!-- ─── Main Article Content ─── -->
45
57
<main class="ja-content">
46
58
<article>
47
59
{{ content }}
48
60
</article>
49
61
50
62
<footer class="ja-footer">
51
- <p>JustAPI v2.0.9 β€” MIT License Β· <a href="https://github.com/swadhinbiswas/JustAPI">github.com/swadhinbiswas/JustAPI</a></p>
63
+ <p>JustAPI v2.0.9 β€” Open Source MIT License Β· Built with WebTUI Β· <a href="https://github.com/swadhinbiswas/JustAPI" target="_blank" rel="noopener">GitHub</a></p>
52
64
</footer>
53
65
</main>
54
66
55
- <!-- ─── Right sidebar: page TOC ─── -->
67
+ <!-- ─── Right Sidebar: Table of Contents ─── -->
56
68
<aside class="ja-toc-sidebar">
57
69
<div class="ja-toc">
58
70
<div class="ja-toc-title">On this page</div>
61
73
</aside>
62
74
</div>
63
75
64
- <!-- ─── Theme switcher ─── -->
76
+ <!-- ─── WebTUI Neovim Statusline Bar (Fixed Bottom) ─── -->
77
+ <div id="statusline">
78
+ <div class="statusline-left">
79
+ <span is-="badge" variant-="blue" style="--badge-text: var(--background0)">NORMAL</span>
80
+ <span is-="badge" variant-="background1" style="--badge-text: var(--blue)"><span class="nf">&#xe725;</span> master</span>
81
+ <span is-="badge" variant-="background0">justapi/docs</span>
82
+ </div>
83
+ <div class="statusline-right" id="statusline-right">
84
+ <span is-="badge" variant-="background0">utf-8</span>
85
+ <span is-="badge" variant-="background1">Top</span>
86
+ <span is-="badge" variant-="blue" style="--badge-text: var(--background0)">1:1</span>
87
+ </div>
88
+ </div>
89
+
90
+ <!-- ─── WebTUI Search Dialog Modal ─── -->
91
+ <dialog id="search-dialog" is-="dialog" size-="default" position-="center">
92
+ <div class="ja-search-box" box-="square">
93
+ <div class="ja-search-header">
94
+ <span is-="badge" variant-="green" cap-="round"><span class="nf">&#xea6d;</span> Search Documentation</span>
95
+ <button type="button" is-="button" box-="square" size-="small" id="closeSearchBtn">βœ•</button>
96
+ </div>
97
+ <input type="text" id="searchInput" class="ja-search-input" placeholder="Type to filter pages (e.g. routing, zero-gil, db)..." autofocus>
98
+ <div class="ja-search-results" id="searchResults">
99
+ <!-- populated dynamically via JS -->
100
+ </div>
101
+ </div>
102
+ </dialog>
103
+
104
+ <!-- ─── Floating Theme Switcher Widget ─── -->
65
105
<div class="ja-theme-switcher" id="themeSwitcher"></div>
66
106
67
107
<script src="/js/main.js"></script>
68
108
β–Όwebtui/templates/landing.html
+230-61
4
4
<meta charset="utf-8">
5
5
<meta name="viewport" content="width=device-width, initial-scale=1">
6
6
<title>JustAPI β€” Python writes the logic. Rust does everything else.</title>
7
- <meta name="description" content="JustAPI β€” Python web framework with a Rust core. 766k req/s hello-world. FastAPI-compatible.">
7
+ <meta name="description" content="JustAPI β€” Ultra-fast Python web framework with a Rust core. 766k req/s hello-world. FastAPI-compatible.">
8
+ <!-- SEO & Open Graph -->
9
+ <meta name="google-site-verification" content="OGwxuIzhp_huelFM3j_dzbm2bESKAhpE2LA5RRnsQUA" />
10
+ <meta property="og:title" content="JustAPI β€” Python writes the logic. Rust does everything else.">
11
+ <meta property="og:description" content="JustAPI β€” Ultra-fast Python web framework with a Rust core. 766k req/s hello-world. FastAPI-compatible.">
12
+ <meta property="og:type" content="website">
13
+ <meta name="twitter:card" content="summary_large_image">
14
+ <meta name="twitter:title" content="JustAPI β€” Python writes the logic. Rust does everything else.">
15
+ <meta name="twitter:description" content="JustAPI β€” Ultra-fast Python web framework with a Rust core. 766k req/s hello-world. FastAPI-compatible.">
16
+ <link rel="canonical" href="https://justapi.pages.dev/">
8
17
<link rel="preconnect" href="https://fonts.googleapis.com">
9
18
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
- <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
19
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600;700&display=swap" rel="stylesheet">
11
20
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/ryanoasis/nerd-fonts@master/css/nerd-fonts-generated.css">
12
21
<link rel="stylesheet" href="/css/main.css">
13
22
</head>
14
23
<body>
24
+ <!-- ─── WebTUI Navbar ─── -->
15
25
<header class="ja-navbar">
16
26
<div class="ja-navbar-bar">
17
27
<span class="ja-dot"></span><span class="ja-dot"></span><span class="ja-dot"></span>
19
29
</div>
20
30
<div class="ja-navbar-body">
21
31
<div class="ja-nav-brand">
22
- <a href="/"><span class="nf">&#xf120;</span> JUSTAPI</a>
32
+ <a href="/"><span class="nf">&#xf120;</span> &lt;/<span class="accent">#</span>&gt;JUSTAPI</a>
23
33
</div>
24
34
<nav class="ja-nav-links">
25
- <a href="/">Home</a>
35
+ <a href="/" class="active">Home</a>
26
36
<a href="/getting-started/">Getting Started</a>
27
37
<a href="/tutorials/">Tutorials</a>
28
38
<a href="/api-reference/">API</a>
29
39
<a href="/deployment/">Deploy</a>
30
- <a href="https://github.com/swadhinbiswas/JustAPI" target="_blank"><span class="nf">&#xf09b;</span></a>
40
+ <a href="https://github.com/swadhinbiswas/JustAPI" target="_blank" rel="noopener"><span class="nf">&#xf09b;</span> GitHub</a>
41
+ <button type="button" class="ja-search-trigger" id="openSearchBtn">
42
+ <span class="nf">&#xea6d;</span> Search <kbd>Ctrl+K</kbd>
43
+ </button>
31
44
</nav>
32
45
</div>
33
46
</header>
34
47
35
- <main class="ja-content ja-landing">
36
- <!-- ─── Hero ─── -->
37
- <div class="ja-hero">
38
- <span is="badge" variant="green">&#xf17d; v2.0.9 Β· published on PyPI Β· 8 platforms</span>
48
+ <!-- ─── Hero Section with WebTUI Desktop Backdrop ─── -->
49
+ <div id="intro-backdrop">
50
+ <!-- Background Desktop Widgets Grid -->
51
+ <div id="widgets-grid">
52
+ <div class="terminal-widget" box-="square">
53
+ <span style="color: var(--blue);">&#xf07b; ~/projects/justapi-app</span>
54
+ <ul marker-="tree">
55
+ <li style="color: var(--blue);">&#xf07b; .git</li>
56
+ <li style="color: var(--blue);">&#xf07b; app</li>
57
+ <ul marker-="tree">
58
+ <li><span style="color: var(--yellow);">&#xe606;</span> main.py</li>
59
+ <li><span style="color: var(--yellow);">&#xe606;</span> routes.py</li>
60
+ </ul>
61
+ <li>&#xe7a8; Cargo.toml</li>
62
+ </ul>
63
+ </div>
64
+
65
+ <div class="terminal-widget" box-="square">
66
+ <div box-="round" pad-="1 1" style="--box-border-color: var(--background2); color: var(--mauve);">
67
+ <div style="display: flex; justify-content: space-between; align-items: center;">
68
+ <strong style="color: var(--green);">justapi-core</strong>
69
+ <span>200 OK</span>
70
+ </div>
71
+ <div style="display: flex; gap: 0.5ch; margin-top: 0.5lh; align-items: center;">
72
+ <span>[</span>
73
+ <div is-="separator" style="--separator-color: var(--lavender); width: 14ch;"></div>
74
+ <div is-="separator" style="--separator-color: var(--background2); flex-grow: 1;"></div>
75
+ <span>]</span>
76
+ </div>
77
+ </div>
78
+ </div>
79
+
80
+ <div class="terminal-widget" box-="square">
81
+ <div style="display: flex; align-items: center; justify-content: space-between;">
82
+ <span style="color: var(--foreground2);">766k RPS</span>
83
+ <span is-="badge" variant-="green" cap-="round">ACTIVE</span>
84
+ </div>
85
+ </div>
86
+ </div>
87
+
88
+ <!-- Centered Hero Box Window -->
89
+ <div id="main-hero" box-="square">
90
+ <div style="display: flex; gap: 0.5ch; justify-content: center; margin-bottom: 1rem; flex-wrap: wrap;">
91
+ <span is-="badge" variant-="green" cap-="round"><span class="nf">&#xf17d;</span> v2.0.9 Β· PyPI</span>
92
+ <span is-="badge" variant-="blue" cap-="round"><span class="nf">&#xeb01;</span> Zero-GIL CPython 3.14t</span>
93
+ <span is-="badge" variant-="mauve" cap-="round"><span class="nf">&#xf0e7;</span> 766k req/s</span>
94
+ </div>
39
95
<h1>Python writes the logic.<br><span class="accent">Rust does everything else.</span></h1>
40
- <p class="ja-hero-sub">JustAPI is a Python web framework with a Rust core. One import, zero config β€” routing, TLS, validation, serialization and the server itself run in Rust.</p>
96
+ <p class="ja-hero-sub">JustAPI is an ultra-fast Python web framework built on a Rust core. One import, zero config β€” routing, TLS, request validation, serialization, and connection handling run in Rust.</p>
41
97
<div class="ja-hero-actions">
42
- <a href="/getting-started/overview/" is="button" size="large">&#xf061; Get Started</a>
43
- <a href="https://github.com/swadhinbiswas/JustAPI" is="button" box size="large">&#xf09b; GitHub</a>
98
+ <a href="/getting-started/" is-="button" variant-="green" size-="large"><span class="nf">&#xf061;</span> Get Started</a>
99
+ <a href="https://github.com/swadhinbiswas/JustAPI" is-="button" box-="square" size-="large" target="_blank" rel="noopener"><span class="nf">&#xf09b;</span> GitHub</a>
44
100
</div>
45
101
</div>
102
+ </div>
46
103
47
- <!-- ─── Performance stats ─── -->
48
- <div class="ja-section">Performance</div>
104
+ <main class="ja-content ja-landing" style="max-width: 60rem; margin: 0 auto;">
105
+ <!-- ─── Performance Stats ─── -->
106
+ <div class="ja-section">Performance Overview</div>
49
107
<div class="ja-stats">
50
- <div class="ja-stat"><div class="ja-stat-value">766k</div><div class="ja-stat-label">req/s hello-world</div></div>
51
- <div class="ja-stat"><div class="ja-stat-value">782k</div><div class="ja-stat-label">req/s JSON echo</div></div>
52
- <div class="ja-stat"><div class="ja-stat-value">2.4x</div><div class="ja-stat-label">faster than Granian</div></div>
53
- <div class="ja-stat"><div class="ja-stat-value">53x</div><div class="ja-stat-label">native async DB</div></div>
54
- <div class="ja-stat"><div class="ja-stat-value">181k</div><div class="ja-stat-label">native CRUD SELECT</div></div>
55
- <div class="ja-stat"><div class="ja-stat-value">12MB</div><div class="ja-stat-label">resident memory</div></div>
108
+ <div class="ja-stat">
109
+ <div class="ja-stat-value">766k</div>
110
+ <div class="ja-stat-label">req/s hello-world</div>
111
+ </div>
112
+ <div class="ja-stat">
113
+ <div class="ja-stat-value">782k</div>
114
+ <div class="ja-stat-label">req/s JSON echo</div>
115
+ </div>
116
+ <div class="ja-stat">
117
+ <div class="ja-stat-value">2.4x</div>
118
+ <div class="ja-stat-label">faster than Granian</div>
119
+ </div>
120
+ <div class="ja-stat">
121
+ <div class="ja-stat-value">53x</div>
122
+ <div class="ja-stat-label">native async DB queries</div>
123
+ </div>
124
+ <div class="ja-stat">
125
+ <div class="ja-stat-value">181k</div>
126
+ <div class="ja-stat-label">native CRUD SELECT req/s</div>
127
+ </div>
128
+ <div class="ja-stat">
129
+ <div class="ja-stat-value">12MB</div>
130
+ <div class="ja-stat-label">resident RAM footprint</div>
131
+ </div>
56
132
</div>
57
133
58
- <!-- ─── Quick start ─── -->
59
- <div class="ja-section">Quick start</div>
60
- <div class="ja-tabs">
134
+ <!-- ─── Quick Start Tabs ─── -->
135
+ <div class="ja-section">Quick Start</div>
136
+ <div class="ja-tabs" box-="square">
61
137
<div class="ja-tabs-bar">
62
- <button class="ja-tab-btn" data-tab-btn="pip" data-active="true">&#xf472; pip</button>
63
- <button class="ja-tab-btn" data-tab-btn="uv">&#xf42e; uv</button>
64
- <button class="ja-tab-btn" data-tab-btn="scaffold">&#xf4c2; scaffold</button>
138
+ <button type="button" class="ja-tab-btn" data-tab-btn="pip" data-active="true"><span class="nf">&#xf472;</span> pip</button>
139
+ <button type="button" class="ja-tab-btn" data-tab-btn="uv"><span class="nf">&#xf42e;</span> uv</button>
140
+ <button type="button" class="ja-tab-btn" data-tab-btn="scaffold"><span class="nf">&#xf4c2;</span> scaffold</button>
65
141
</div>
66
142
<div class="ja-tab-panel" data-tab-panel="pip">
67
143
<pre>pip install justapi
78
154
</div>
79
155
</div>
80
156
81
- <!-- ─── Minimal app ─── -->
82
- <div class="ja-section">Minimal app</div>
83
- <div class="ja-terminal">
157
+ <!-- ─── Minimal App Code Showcase ─── -->
158
+ <div class="ja-section">Minimal Application</div>
159
+ <div class="ja-terminal" box-="square">
84
160
<div class="ja-terminal-bar">
85
161
<span class="ja-dot"></span><span class="ja-dot"></span><span class="ja-dot"></span>
86
- <span class="ja-terminal-title">&#xf1c5; main.py</span>
162
+ <span class="ja-terminal-title"><span class="nf">&#xf1c5;</span> main.py</span>
87
163
</div>
88
164
<div class="ja-terminal-body">
89
165
<span class="cm">from</span> justapi <span class="cm">import</span> JustAPIApp<br><br>
92
168
<span class="cm">def</span> hello():<br>
93
169
&nbsp;&nbsp;&nbsp;&nbsp;<span class="cm">return</span> &#123;<span class="ok">"Hello"</span>: <span class="ok">"World"</span>&#125;<br><br>
94
170
app.run()<br>
95
- <span class="ok">&#xf00c;</span> <span class="dim">server started Β· /docs Β· /redoc Β· OpenAPI auto</span>
171
+ <span class="ok"><span class="nf">&#xf00c;</span> server started</span> <span class="dim">Β· OpenAPI auto-generated at /docs</span>
96
172
</div>
97
173
</div>
98
174
99
- <!-- ─── Benchmarks ─── -->
100
- <div class="ja-section">Benchmarks</div>
175
+ <!-- ─── Benchmarks Comparison ─── -->
176
+ <div class="ja-section">Throughput Benchmarks</div>
101
177
<div class="ja-bars">
102
178
<div class="ja-bar-row winner">
103
179
<span class="ja-bar-label">JustAPI</span>
120
196
<span class="ja-bar-value">36k</span>
121
197
</div>
122
198
</div>
199
+ <p style="font-size: 0.8rem; color: var(--foreground2); margin-top: 0.4rem;">
200
+ <em>hello-world, 100 concurrent connections, 30s benchmark duration on identical hardware β€” see detailed methodology in BENCHMARKS.md</em>
201
+ </p>
123
202
124
- <blockquote>hello-world, 100 concurrent connections, 30s, same hardware β€” full methodology in BENCHMARKS.md</blockquote>
125
-
126
- <!-- ─── Features ─── -->
127
- <div class="ja-section">Features</div>
203
+ <!-- ─── Feature Grid ─── -->
204
+ <div class="ja-section">Framework Features</div>
128
205
<div class="ja-grid">
129
- <div class="ja-card"><div class="ja-card-icon">&#xf40d;</div><h3>Rust core</h3><p>Networking, TLS, routing, serialization run in Rust. Python executes only your logic.</p></div>
130
- <div class="ja-card"><div class="ja-card-icon">&#xf471;</div><h3>Native async DB</h3><p><code>await app.db.query_async()</code> runs SQL on Rust's tokio runtime β€” 53Γ— faster on slow queries.</p></div>
131
- <div class="ja-card"><div class="ja-card-icon">&#xf295;</div><h3>Rust-native SSE</h3><p><code>app.sse_native()</code> streams events with zero Python per event.</p></div>
132
- <div class="ja-card"><div class="ja-card-icon">&#xf085;</div><h3>Multi-worker</h3><p><code>justapi serve --workers N</code> β€” 1.88Γ— throughput at 4 workers, auto-scale.</p></div>
133
- <div class="ja-card"><div class="ja-card-icon">&#xf4e7;</div><h3>Free-threaded</h3><p>CPython 3.14t wheels β€” CPU-bound Python scales with cores, 12.4Γ— measured.</p></div>
134
- <div class="ja-card"><div class="ja-card-icon">&#xf4ab;</div><h3>Security built-in</h3><p>JWT, rate limiting, CORS, security headers β€” all in Rust middleware.</p></div>
135
- <div class="ja-card"><div class="ja-card-icon">&#xf4dc;</div><h3>Production-ready</h3><p>Circuit breakers, OTel tracing, Prometheus metrics, health checks.</p></div>
136
- <div class="ja-card"><div class="ja-card-icon">&#xf487;</div><h3>Type-checked</h3><p><code>py.typed</code> + complete stubs β€” mypy-clean on user code.</p></div>
137
- <div class="ja-card"><div class="ja-card-icon">&#xf44f;</div><h3>FastAPI-compatible</h3><p>Same decorators, Pydantic models, Depends. Migrate with one import.</p></div>
206
+ <div class="ja-card">
207
+ <div class="ja-card-icon">&#xf40d;</div>
208
+ <h3>Rust Core Architecture</h3>
209
+ <p>Sockets, TLS termination, HTTP parsing (hyper), routing, and response writing run entirely in Rust.</p>
210
+ </div>
211
+ <div class="ja-card">
212
+ <div class="ja-card-icon">&#xf471;</div>
213
+ <h3>Native Async DB Integration</h3>
214
+ <p><code>await app.db.query_async()</code> runs SQL queries on Rust's tokio pool β€” up to 53Γ— faster on slow queries.</p>
215
+ </div>
216
+ <div class="ja-card">
217
+ <div class="ja-card-icon">&#xf295;</div>
218
+ <h3>Rust-Native SSE & WebSockets</h3>
219
+ <p><code>app.sse_native()</code> streams real-time events with zero Python overhead per emitted message.</p>
220
+ </div>
221
+ <div class="ja-card">
222
+ <div class="ja-card-icon">&#xf085;</div>
223
+ <h3>Multi-Worker Auto-Scaling</h3>
224
+ <p><code>justapi serve --workers N</code> provides 1.88Γ— throughput scaling across multi-core systems.</p>
225
+ </div>
226
+ <div class="ja-card">
227
+ <div class="ja-card-icon">&#xf4e7;</div>
228
+ <h3>Free-Threaded CPython 3.14t</h3>
229
+ <p>Native support for GIL-free CPython β€” CPU-bound Python routes scale linearly across all CPU cores.</p>
230
+ </div>
231
+ <div class="ja-card">
232
+ <div class="ja-card-icon">&#xf4ab;</div>
233
+ <h3>Built-in Rust Security</h3>
234
+ <p>JWT verification, token bucket rate limiting, CORS headers, and OWASP security middleware run in Rust.</p>
235
+ </div>
236
+ <div class="ja-card">
237
+ <div class="ja-card-icon">&#xf4dc;</div>
238
+ <h3>Production Observability</h3>
239
+ <p>Circuit breakers, OpenTelemetry distributed tracing, Prometheus metrics, and automated health checks.</p>
240
+ </div>
241
+ <div class="ja-card">
242
+ <div class="ja-card-icon">&#xf487;</div>
243
+ <h3>Type-Checked & Stubbed</h3>
244
+ <p>Ships with <code>py.typed</code> marker and complete type stubs for 100% clean mypy type validation.</p>
245
+ </div>
246
+ <div class="ja-card">
247
+ <div class="ja-card-icon">&#xf44f;</div>
248
+ <h3>FastAPI Drop-in Compatibility</h3>
249
+ <p>Uses familiar decorators, Pydantic models, and <code>Depends</code> injection. Migrate with one import change.</p>
250
+ </div>
138
251
</div>
139
252
140
- <!-- ─── Comparison ─── -->
141
- <div class="ja-section">Framework comparison</div>
142
- <table>
143
- <thead><tr><th></th><th>FastAPI</th><th>JustAPI</th></tr></thead>
253
+ <!-- ─── Framework Comparison Table ─── -->
254
+ <div class="ja-section">FastAPI vs JustAPI Comparison</div>
255
+ <table class="shadow">
256
+ <thead>
257
+ <tr>
258
+ <th>Metric / Feature</th>
259
+ <th>FastAPI</th>
260
+ <th>JustAPI</th>
261
+ </tr>
262
+ </thead>
144
263
<tbody>
145
- <tr><td><strong>Server</strong></td><td>uvicorn (Python)</td><td>Rust (tokio + hyper)</td></tr>
146
- <tr><td><strong>Hello-world</strong></td><td>~36k req/s</td><td><strong>~766k req/s</strong></td></tr>
147
- <tr><td><strong>JSON echo</strong></td><td>~33k req/s</td><td><strong>~782k req/s</strong></td></tr>
148
- <tr><td><strong>Native DB SELECT</strong></td><td>Python driver</td><td><strong>181k req/s (Γ—125)</strong></td></tr>
149
- <tr><td><strong>Memory</strong></td><td>29 MB</td><td><strong>12 MB</strong></td></tr>
264
+ <tr>
265
+ <td><strong>Runtime Core</strong></td>
266
+ <td>uvicorn (Python)</td>
267
+ <td><strong>Rust (tokio + hyper)</strong></td>
268
+ </tr>
269
+ <tr>
270
+ <td><strong>Hello World Throughput</strong></td>
271
+ <td>~36k req/s</td>
272
+ <td><strong>~766k req/s (21x faster)</strong></td>
273
+ </tr>
274
+ <tr>
275
+ <td><strong>JSON Echo Throughput</strong></td>
276
+ <td>~33k req/s</td>
277
+ <td><strong>~782k req/s (23x faster)</strong></td>
278
+ </tr>
279
+ <tr>
280
+ <td><strong>Native DB SELECT</strong></td>
281
+ <td>Python DB Driver</td>
282
+ <td><strong>181k req/s (125x faster)</strong></td>
283
+ </tr>
284
+ <tr>
285
+ <td><strong>Memory Footprint</strong></td>
286
+ <td>~29 MB</td>
287
+ <td><strong>~12 MB (58% lower)</strong></td>
288
+ </tr>
150
289
</tbody>
151
290
</table>
152
291
153
- <!-- ─── CTA ─── -->
154
- <div class="ja-cta">
155
- <h2>Ready to build something fast?</h2>
156
- <p>One import. Rust underneath. Zero config.</p>
157
- <a href="/getting-started/overview/" is="button" size="large">&#xf061; Get Started</a>
292
+ <!-- ─── CTA Section ─── -->
293
+ <div class="ja-cta" box-="square">
294
+ <h2>Ready to build high-performance APIs?</h2>
295
+ <p>One import. Powered by Rust. Zero complex configuration.</p>
296
+ <a href="/getting-started/" is-="button" variant-="green" size-="large"><span class="nf">&#xf061;</span> Read the Documentation</a>
158
297
</div>
159
298
160
299
<footer class="ja-footer">
161
- <p>JustAPI v2.0.9 β€” MIT License Β· <a href="https://github.com/swadhinbiswas/JustAPI">github.com/swadhinbiswas/JustAPI</a></p>
300
+ <p>JustAPI v2.0.9 β€” Open Source MIT License Β· Built with WebTUI Β· <a href="https://github.com/swadhinbiswas/JustAPI" target="_blank" rel="noopener">GitHub</a></p>
162
301
</footer>
163
302
</main>
164
303
304
+ <!-- ─── WebTUI Neovim Statusline Bar (Fixed Bottom) ─── -->
305
+ <div id="statusline">
306
+ <div class="statusline-left">
307
+ <span is-="badge" variant-="blue" style="--badge-text: var(--background0)">NORMAL</span>
308
+ <span is-="badge" variant-="background1" style="--badge-text: var(--blue)"><span class="nf">&#xe725;</span> master</span>
309
+ <span is-="badge" variant-="background0">justapi/home.py</span>
310
+ </div>
311
+ <div class="statusline-right" id="statusline-right">
312
+ <span is-="badge" variant-="background0">utf-8</span>
313
+ <span is-="badge" variant-="background1">Top</span>
314
+ <span is-="badge" variant-="blue" style="--badge-text: var(--background0)">1:1</span>
315
+ </div>
316
+ </div>
317
+
318
+ <!-- ─── WebTUI Search Dialog Modal ─── -->
319
+ <dialog id="search-dialog" is-="dialog" size-="default" position-="center">
320
+ <div class="ja-search-box" box-="square">
321
+ <div class="ja-search-header">
322
+ <span is-="badge" variant-="green" cap-="round"><span class="nf">&#xea6d;</span> Search Documentation</span>
323
+ <button type="button" is-="button" box-="square" size-="small" id="closeSearchBtn">βœ•</button>
324
+ </div>
325
+ <input type="text" id="searchInput" class="ja-search-input" placeholder="Type to filter pages (e.g. routing, zero-gil, db)..." autofocus>
326
+ <div class="ja-search-results" id="searchResults">
327
+ <!-- populated dynamically via JS -->
328
+ </div>
329
+ </div>
330
+ </dialog>
331
+
332
+ <!-- ─── Floating Theme Switcher Widget ─── -->
165
333
<div class="ja-theme-switcher" id="themeSwitcher"></div>
334
+
166
335
<script src="/js/main.js"></script>
167
336
</body>
168
337
</html>
169
338