π¦ release: crates.io publish prep + webtui site updates
swadhinbiswas committed 2 months ago
6975fa8
1 parent 7867f8d
commit 6975fa8
6 files changed
+1319 -425
Showing 6 changed files+1319-425
βΌCargo.lock
+5-5
| @@ -2729,7 +2729,7 @@ dependencies = [ | ||
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,7 +2744,7 @@ dependencies = [ | ||
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,7 +2764,7 @@ dependencies = [ | ||
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,7 +2833,7 @@ dependencies = [ | ||
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,7 +2853,7 @@ dependencies = [ | ||
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,9 +115,11 @@ def build_sidebar(active_path: str) -> str: | ||
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,23 +143,56 @@ def slugify_headers(html: str) -> str: | ||
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"></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"></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,6 +209,8 @@ def build() -> None: | ||
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,6 +220,8 @@ def build() -> None: | ||
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,7 +230,41 @@ def build() -> None: | ||
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,436 +1,919 @@ | ||
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,16 +1,13 @@ | ||
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,22 +21,149 @@ | ||
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);"></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,7 +178,7 @@ | ||
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,27 +189,32 @@ | ||
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,16 +3,25 @@ | ||
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,7 +29,7 @@ | ||
20 | 29 | </div> |
21 | 30 | <div class="ja-navbar-body"> |
22 | 31 | <div class="ja-nav-brand"> |
23 | - <a href="/"><span class="nf"></span> JUSTAPI</a> | |
32 | + <a href="/"><span class="nf"></span> </<span class="accent">#</span>>JUSTAPI</a> | |
24 | 33 | </div> |
25 | 34 | <nav class="ja-nav-links"> |
26 | 35 | <a href="/">Home</a> |
| @@ -28,31 +37,34 @@ | ||
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"></span></a> | |
40 | + <a href="https://github.com/swadhinbiswas/JustAPI" target="_blank" rel="noopener"><span class="nf"></span> GitHub</a> | |
41 | + <button type="button" class="ja-search-trigger" id="openSearchBtn"> | |
42 | + <span class="nf"></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,7 +73,35 @@ | ||
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"></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"></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,14 +4,24 @@ | ||
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,49 +29,115 @@ | ||
19 | 29 | </div> |
20 | 30 | <div class="ja-navbar-body"> |
21 | 31 | <div class="ja-nav-brand"> |
22 | - <a href="/"><span class="nf"></span> JUSTAPI</a> | |
32 | + <a href="/"><span class="nf"></span> </<span class="accent">#</span>>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"></span></a> | |
40 | + <a href="https://github.com/swadhinbiswas/JustAPI" target="_blank" rel="noopener"><span class="nf"></span> GitHub</a> | |
41 | + <button type="button" class="ja-search-trigger" id="openSearchBtn"> | |
42 | + <span class="nf"></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"> 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);"> ~/projects/justapi-app</span> | |
54 | + <ul marker-="tree"> | |
55 | + <li style="color: var(--blue);"> .git</li> | |
56 | + <li style="color: var(--blue);"> app</li> | |
57 | + <ul marker-="tree"> | |
58 | + <li><span style="color: var(--yellow);"></span> main.py</li> | |
59 | + <li><span style="color: var(--yellow);"></span> routes.py</li> | |
60 | + </ul> | |
61 | + <li> 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"></span> v2.0.9 Β· PyPI</span> | |
92 | + <span is-="badge" variant-="blue" cap-="round"><span class="nf"></span> Zero-GIL CPython 3.14t</span> | |
93 | + <span is-="badge" variant-="mauve" cap-="round"><span class="nf"></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"> Get Started</a> | |
43 | - <a href="https://github.com/swadhinbiswas/JustAPI" is="button" box size="large"> GitHub</a> | |
98 | + <a href="/getting-started/" is-="button" variant-="green" size-="large"><span class="nf"></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"></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"> pip</button> | |
63 | - <button class="ja-tab-btn" data-tab-btn="uv"> uv</button> | |
64 | - <button class="ja-tab-btn" data-tab-btn="scaffold"> scaffold</button> | |
138 | + <button type="button" class="ja-tab-btn" data-tab-btn="pip" data-active="true"><span class="nf"></span> pip</button> | |
139 | + <button type="button" class="ja-tab-btn" data-tab-btn="uv"><span class="nf"></span> uv</button> | |
140 | + <button type="button" class="ja-tab-btn" data-tab-btn="scaffold"><span class="nf"></span> scaffold</button> | |
65 | 141 | </div> |
66 | 142 | <div class="ja-tab-panel" data-tab-panel="pip"> |
67 | 143 | <pre>pip install justapi |
| @@ -78,12 +154,12 @@ cd my_app && python app/main.py</pre> | ||
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"> main.py</span> | |
162 | + <span class="ja-terminal-title"><span class="nf"></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,12 +168,12 @@ cd my_app && python app/main.py</pre> | ||
92 | 168 | <span class="cm">def</span> hello():<br> |
93 | 169 | <span class="cm">return</span> {<span class="ok">"Hello"</span>: <span class="ok">"World"</span>}<br><br> |
94 | 170 | app.run()<br> |
95 | - <span class="ok"></span> <span class="dim">server started Β· /docs Β· /redoc Β· OpenAPI auto</span> | |
171 | + <span class="ok"><span class="nf"></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,49 +196,142 @@ cd my_app && python app/main.py</pre> | ||
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"></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"></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"></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"></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"></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"></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"></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"></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"></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"></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"></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"></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"></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"></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"></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"></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"></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"></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"> 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"></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"></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"></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 | |