diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 4f281cb..0000000 --- a/.dockerignore +++ /dev/null @@ -1,62 +0,0 @@ -# Git -.git -.gitignore -.gitattributes - -# Python -__pycache__/ -*.py[cod] -*$py.class -*.so -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST -*.pyc -*.pyo - -# Virtual environments -venv/ -ENV/ -env/ -.venv - -# IDE -.vscode/ -.idea/ -*.swp -*.swo -*~ -.DS_Store - -# MkDocs -site/ - -# Documentation not needed in image -*.md -!README.md -DEPLOYMENT.md -SETUP_SUMMARY.md - -# CI/CD -.github/ - -# Other -*.log -.cache/ -.pytest_cache/ -.coverage -htmlcov/ diff --git a/.gitignore b/.gitignore index 2d8736d..c4ed3a0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ -# MkDocs -site/ +# Build / cache .cache/ # Python diff --git a/CLAUDE.md b/CLAUDE.md index 1040d44..6bd35f0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,12 +1,12 @@ # Guide for Claude on this repo -This repo is the source for [blogs.jaseci.org](https://blogs.jaseci.org) — an MkDocs Material site with a custom editorial scheduling system on top. Two roles you may be asked to help with: **author** (writing or refining a post) and **reviewer** (evaluating an open PR). The rules differ. +This repo is the source for [blogs.jaseci.org](https://blogs.jaseci.org) — a Jac web app (jac-client + jac-scale, entrypoint [main.jac](main.jac)) that serves the blog, with a custom editorial scheduling system on top. Posts live as markdown under `docs/blog/posts/` and are parsed and rendered by the app at request time. Two roles you may be asked to help with: **author** (writing or refining a post) and **reviewer** (evaluating an open PR). The rules differ. ## Shared invariants (apply to everyone, always) - **Never force-push `main`.** Force-push history rewrites silently dropped three merged blog posts in May 2026 — see [issue #21](https://github.com/jaseci-labs/jaseci-blogs/issues/21). If your local branch is behind, `git pull --rebase` or merge — never `git push --force` on `main`. - **`docs/blog/.schedule.yml` is bot-owned.** Do not hand-edit it. Mutations go through `scripts/schedule_lib.py` (or the workflows that wrap it), which adds audit fields (`added_by`, `added_at`, etc). -- **The blog plugin only sees `docs/blog/posts/`.** Files in `docs/blog/unlisted/` and `docs/blog/archived_posts/` are invisible to the site by location alone. Don't move things between these dirs by hand — use the *Take down a post* workflow or `/hide` / `/unlist` / `/archive` slash-commands so the audit trail is preserved. +- **The app only lists posts in `docs/blog/posts/`.** ([main.jac](main.jac) scans that directory.) Files in `docs/blog/unlisted/` and `docs/blog/archived_posts/` are invisible to the site by location alone. Don't move things between these dirs by hand — use the *Take down a post* workflow or `/hide` / `/unlist` / `/archive` slash-commands so the audit trail is preserved. - **Posts identify by `slug:` frontmatter**, not filename. When the user says "the topology post," match against the `slug:` field first, fall back to the filename stem. - **File references in chat: use markdown links**, e.g. [scripts/schedule_lib.py](scripts/schedule_lib.py) — never backticks for paths. @@ -22,13 +22,13 @@ date: 2026-MM-DD # placeholder; auto-publisher will overwrite whe authors: - their_author_id categories: - - One of the categories allowed in mkdocs.yml + - One of the established categories (see below) slug: my-post-slug # kebab-case, matches the filename ideally draft: true # REQUIRED — PR check rejects without it --- ``` -- The `categories_allowed` list lives in [mkdocs.yml](mkdocs.yml) under the `blog:` plugin — if the author wants a new category, add it there in the same PR. +- Categories are **free-form** — the app builds the category index from whatever `categories:` appear in post frontmatter (the `GetCategories` walker in [main.jac](main.jac)), so there is no allowlist to edit. To avoid fragmenting the taxonomy, reuse an established category: **Jac Programming, Tutorials, Fixing the Broken, AI, Web Development, Developers, Community**. A genuinely new category just needs to be spelled consistently. - New authors need an entry in [docs/blog/.authors.yml](docs/blog/.authors.yml) with `name`, `description`, and `avatar`. Use `https://avatars.githubusercontent.com/u/?v=4` for the avatar. - **Do not suggest removing `draft: true`.** That's the editor's call, executed by the scheduling workflows. - **Do not suggest a precise future `date:` to "schedule" the post.** Dates in frontmatter do not gate publishing here. The scheduler overrides `date:` when it publishes. @@ -49,7 +49,7 @@ Your job is to evaluate the post and flag anything an editor needs to know befor - `draft: true` is present (the *Lint new posts* check enforces this; if it's missing the PR can't merge anyway). - The `slug:` is kebab-case and unique (grep existing posts for collisions). - Author is registered in `.authors.yml`. -- Category is in `categories_allowed` in [mkdocs.yml](mkdocs.yml). +- Category is spelled consistently with an established category (categories are free-form; see the author section above) so it doesn't fragment the index. - No oversized images committed under `docs/assets/`. **Verify editorially**: @@ -82,16 +82,17 @@ All four slash-commands and the workflows write commits to `main` via `github-ac ## Build & serve (when working locally) ```bash -pip install -e . # install deps + the Jac syntax highlighter -python scripts/mkdocs_serve.py # serves with the CORS headers Pyodide needs -mkdocs build # static build into site/ +pip install jaclang # the `jac` CLI +jac install # installs jac-scale, jac-client + deps from jac.toml into .jac/venv +jac start main.jac # serve the app locally (client + backend, one origin) +python scripts/handle_jac_compile_data.py # (re)generate docs/playground/jaclang.zip for runnable code blocks ``` -`mkdocs serve` works for everything except runnable Jac code blocks — those need the custom server. +`docs/playground/jaclang.zip` is gitignored and powers the in-browser Pyodide runner; regenerate it with the script above if runnable code blocks misbehave locally. Deployment is automated via [.github/workflows/deploy.yml](.github/workflows/deploy.yml) (`jac start main.jac --scale`). ## Things to NOT do -- Don't add `draft: false` explicitly anywhere. The scheduler removes the `draft:` key entirely when it publishes; "no draft key" and `draft: false` mean the same thing to MkDocs but the former is cleaner in `git log`. +- Don't add `draft: false` explicitly anywhere. The scheduler removes the `draft:` key entirely when it publishes; "no draft key" and `draft: false` mean the same thing to the publisher but the former is cleaner in `git log`. - Don't bypass the *Lint new posts* check by force-merging. If it's failing, fix the post. - Don't write to `.schedule.yml` from code other than `scripts/schedule_lib.py`. The header comment block is preserved by `save_schedule()`; ad-hoc yaml writes will lose it. - Don't suggest skipping CI hooks (`--no-verify`) for any reason. diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 7d8586f..0000000 --- a/Dockerfile +++ /dev/null @@ -1,29 +0,0 @@ -FROM python:3.12-slim - -# Install system dependencies -RUN apt-get update && apt-get install -y \ - curl \ - && rm -rf /var/lib/apt/lists/* - -# Set working directory -WORKDIR /app - -# Copy documentation files -COPY docs/ ./docs/ -COPY scripts/ ./scripts/ -COPY setup.py . -COPY jac_syntax_highlighter.py . -COPY mkdocs.yml . -COPY README.md . - -# Install Python dependencies -RUN pip install --no-cache-dir -e . - -# Expose port 8000 -EXPOSE 8000 - -# Health check -HEALTHCHECK --interval=30s --timeout=3s CMD curl -f http://localhost:8000/health || exit 1 - -# Start the custom mkdocs server -CMD ["python", "scripts/mkdocs_serve.py"] diff --git a/README.md b/README.md index 0dca731..766a406 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Jac Blog -A clean MkDocs blog setup with Jac syntax highlighting and interactive, runnable Jac code blocks. +A Jac web app (built on jac-client + jac-scale) that serves the Jaseci Labs engineering blog, with Jac syntax highlighting and interactive, runnable Jac code blocks. Posts are authored as markdown under `docs/blog/posts/` and rendered by the Jac app at request time. ## Contributing @@ -111,85 +111,60 @@ To get your GitHub avatar URL, just replace `YOUR_GITHUB_USER_ID` with your nume ## Features -- **Jac Syntax Highlighting**: Beautiful syntax highlighting for Jac code using custom Pygments and Monaco lexers +- **Jac Web App**: Served by `jac start main.jac` (jac-client React frontend + Jac backend walkers) +- **Jac Syntax Highlighting**: Highlighting for Jac code blocks via python-markdown `codehilite` - **Interactive Code Blocks**: Run Jac code directly in the browser using Pyodide (WebAssembly) -- **Clean Design**: Built with MkDocs Material theme -- **Easy to Use**: Simple markdown-based content creation -- **Fast**: Static site generation for optimal performance +- **Markdown content**: Posts authored as markdown under `docs/blog/posts/`, parsed and rendered at request time ## Prerequisites -- Python 3.8 or higher -- pip (Python package installer) +- Python 3.12 - Git +- Node/bun toolchain is fetched by jac-client at build time (you do not install it directly) ## Installation -1. **Clone this repository** (if you haven't already): +1. **Clone this repository** (if you haven't already). + +2. **Install the Jac toolchain and project dependencies**: ```bash - cd ~/blog + pip install jaclang + jac install ``` -2. **Install all dependencies and the Jac syntax highlighter**: + `jac install` reads the `[dependencies]` table in [jac.toml](jac.toml) and installs jac-scale, jac-client, and the Python/npm deps into the project venv at `.jac/venv`. + +3. **(Optional) Generate the playground runtime** so runnable code blocks work locally: ```bash - pip install -e . + python scripts/handle_jac_compile_data.py ``` - This will install all required dependencies (mkdocs-material, pymdown-extensions, pygments, mkdocs-video, starlette, uvicorn) and register the Jac syntax highlighter. + This downloads a pre-compiled `jaclang` from PyPI and writes `docs/playground/jaclang.zip`, which the app serves to the in-browser Pyodide runner ([main.jac](main.jac) `JACLANG_ZIP`). The zip is gitignored and regenerated on demand. ## Usage -### Development Server - -To start the development server with interactive code execution: - -```bash -python scripts/mkdocs_serve.py -``` - -This will start a server at `http://127.0.0.1:8000` with the necessary CORS headers for Pyodide to work. - -> **Note**: The custom server (`mkdocs_serve.py`) is required for runnable code blocks to work properly because it sets up the CORS headers needed for SharedArrayBuffer support. - -Alternatively, for basic preview without runnable code blocks: +### Running the app locally ```bash -mkdocs serve +jac start main.jac ``` -### Building the Site - -To build the static site: - -```bash -mkdocs build -``` - -The built site will be in the `site/` directory. +This serves the bundled client and the Jac backend from a single origin. For frontend HMR, jac-client splits the vite dev server (`:8000`) and the API (`:8001`) — see the comments in [jac.toml](jac.toml) under `[plugins.client]`. ### Deploying -Deploy to GitHub Pages: - -```bash -mkdocs gh-deploy -``` +Deployment is automated: pushing to `main` triggers [.github/workflows/deploy.yml](.github/workflows/deploy.yml), which runs `jac start main.jac --scale` to deploy to the Jaseci EKS cluster via jac-scale (config under `[plugins.scale]` in [jac.toml](jac.toml)). ## Writing Blog Posts ### Creating a New Post -1. Create a new markdown file in `docs/posts/`: +1. Create a new markdown file in `docs/blog/posts/`: ```bash - touch docs/posts/my-new-post.md + touch docs/blog/posts/my-new-post.md ``` -2. Add the post to the navigation in `mkdocs.yml`: - ```yaml - nav: - - Posts: - - My New Post: posts/my-new-post.md - ``` +2. Add the frontmatter shown in [How to Submit a Post](#how-to-submit-a-post). There is no navigation file to edit — the app discovers posts by scanning `docs/blog/posts/` and orders them by their frontmatter `date:`. ### Adding Jac Code Blocks @@ -244,115 +219,72 @@ with entry { ## Project Structure ``` -~/blog/ -├── docs/ # Documentation source files -│ ├── index.md # Homepage -│ ├── about.md # About page -│ ├── posts/ # Blog posts -│ │ └── welcome.md # Example post -│ ├── js/ # JavaScript files -│ │ ├── jac.monarch.js # Monaco Editor Jac syntax -│ │ ├── run-code.js # Interactive code execution -│ │ └── pyodide-worker.js # Pyodide web worker -│ ├── playground/ # Playground resources -│ │ ├── language-configuration.json -│ │ └── jaclang.zip # (Generated by build hook) -│ ├── extra.css # Custom CSS styling -│ └── assets/ # Images and other assets -├── scripts/ # Build and serve scripts -│ ├── handle_jac_compile_data.py # Build hook for Jac compiler -│ └── mkdocs_serve.py # Custom dev server -├── overrides/ # Theme overrides (optional) -├── jac_syntax_highlighter.py # Pygments lexer for Jac -├── mkdocs.yml # MkDocs configuration -└── README.md # This file +jaseci-blogs/ +├── main.jac # Backend walkers: parse posts/authors, render markdown, serve runtime zip +├── jac.toml # Project config: serve, jac-client, jac-scale, dependencies +├── pages/ # jac-client routes (React via Jac) +│ ├── index.jac # Landing +│ ├── layout.jac # Shared layout / chrome +│ └── blog/ # Blog index + posts/[slug] routes +├── components/ # Client components +│ ├── PostBody.cl.jac # Renders post HTML, boots Pyodide +│ └── RunnableJacBlock.cl.jac # Interactive "Run" code blocks +├── styles/ # App CSS (main.css, prose.css, typography.css) +├── public/ # Static assets served at / +├── docs/ +│ ├── assets/ # Images, referenced as /assets/ +│ └── blog/ +│ ├── posts/ # Published + draft blog posts (markdown) +│ ├── archived_posts/ # Retired posts (invisible to the app) +│ ├── unlisted/ # Retracted posts (invisible to the app) +│ ├── .authors.yml # Author metadata +│ └── .schedule.yml # Bot-owned editorial schedule +├── scripts/ +│ ├── schedule_lib.py # Editorial scheduler (source of truth for .schedule.yml) +│ └── handle_jac_compile_data.py # Generates docs/playground/jaclang.zip for the in-browser runtime +└── .github/workflows/ # Deploy + editorial scheduling automation ``` ## How It Works -### Syntax Highlighting +### Rendering -The blog uses two lexers for syntax highlighting: - -1. **Pygments Lexer** (`jac_syntax_highlighter.py`): Used for server-side static code highlighting during build -2. **Monaco Monarch Lexer** (`docs/js/jac.monarch.js`): Used for client-side syntax highlighting in the interactive code editor +[main.jac](main.jac) reads each post from `docs/blog/posts/`, parses its frontmatter, and renders the markdown body to HTML with python-markdown (`fenced_code` + `codehilite` + `md_in_html`). Authors and the editorial schedule come from `.authors.yml` and `.schedule.yml`. The jac-client frontend in [pages/](pages/) and [components/](components/) renders that HTML. ### Interactive Code Execution -The runnable code blocks use: +Runnable code blocks (authored by wrapping a fenced block in `
`) use: -1. **Pyodide**: A Python runtime compiled to WebAssembly that runs in the browser -2. **Monaco Editor**: The same code editor that powers VS Code -3. **Web Workers**: For isolated code execution without blocking the UI -4. **SharedArrayBuffer**: For synchronous input handling (requires special CORS headers) +1. **Pyodide**: a Python runtime compiled to WebAssembly that runs in the browser +2. **jaclang.zip**: the pre-compiled Jac runtime, generated by [scripts/handle_jac_compile_data.py](scripts/handle_jac_compile_data.py) and served by the `GetRuntimeZip` walker in [main.jac](main.jac); the client extracts it onto Pyodide's `sys.path` -When you click "Run": -1. The code is loaded into Monaco Editor -2. A web worker initializes Pyodide and loads the Jac compiler -3. The code is executed in the browser -4. Output is streamed back to the page in real-time +When you click "Run", the client boots Pyodide (once), loads the Jac runtime from the zip, executes the code, and streams output back to the page. See [notes.md](notes.md) for the authoring options (`run-serve`, `serve-only`, `run-dot`, `data-lang="python"`). ## Customization -### Changing Theme Colors - -Edit the `palette` section in `mkdocs.yml`: - -```yaml -theme: - palette: - scheme: slate # Use 'default' for light mode - primary: black # Primary color - accent: orange # Accent color -``` - -### Adding Social Links +### Theme & styles -Edit the `extra.social` section in `mkdocs.yml`: - -```yaml -extra: - social: - - icon: fontawesome/brands/github - link: https://github.com/yourusername - - icon: fontawesome/brands/twitter - link: https://twitter.com/yourusername -``` - -### Custom CSS - -Add your custom styles to `docs/extra.css`. +Edit the CSS in [styles/](styles/) (`main.css`, `prose.css`, `typography.css`) and the page chrome in [pages/layout.jac](pages/layout.jac). ## Troubleshooting ### Runnable code blocks not working -- Make sure you're using the custom server: `python scripts/mkdocs_serve.py` -- The custom server sets CORS headers required for SharedArrayBuffer -- Check browser console for errors - -### Syntax highlighting not working +- Ensure `docs/playground/jaclang.zip` exists — generate it with `python scripts/handle_jac_compile_data.py` +- Check the browser console for Pyodide errors -- Ensure `jac_syntax_highlighter.py` is installed properly -- Try rebuilding: `mkdocs build --clean` +### Posts not appearing -### Build hook errors - -- Make sure you have the Jac compiler installed -- The hook tries to create `docs/playground/jaclang.zip` from your Jac installation -- If you don't have Jac installed, comment out the hook in `mkdocs.yml` +- The app only sees `docs/blog/posts/`. Posts in `archived_posts/` and `unlisted/` are intentionally invisible +- Drafts (`draft: true`) are excluded from the published list — see [Editorial Scheduling](#editorial-scheduling) ## Dependencies -Core dependencies: -- `mkdocs-material`: Material theme for MkDocs -- `pymdown-extensions`: Markdown extensions for code highlighting -- `pygments`: Syntax highlighting library -- `starlette`: ASGI framework for custom server -- `uvicorn`: ASGI server - -Optional dependencies: -- `mkdocs-video`: Video embedding support +Declared in [jac.toml](jac.toml) `[dependencies]` (installed by `jac install`): +- `jaclang`: the Jac toolchain and `jac` CLI +- `jac-client`: React frontend served from Jac +- `jac-scale[deploy]`: Kubernetes/Docker deploy via `jac start --scale` +- `markdown`, `pyyaml`, `pygments`: post parsing and rendering ## License @@ -361,7 +293,6 @@ Optional dependencies: ## Acknowledgments -- Built with [MkDocs](https://www.mkdocs.org/) and [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) +- Built as a [Jac](https://www.jac-lang.org/) web app with jac-client and jac-scale - Interactive code execution powered by [Pyodide](https://pyodide.org/) -- Code editor powered by [Monaco Editor](https://microsoft.github.io/monaco-editor/) -- Based on the excellent documentation setup from the [Jaseci project](https://github.com/Jaseci-Labs/jaseci) +- Part of the [Jaseci project](https://github.com/Jaseci-Labs/jaseci) diff --git a/docs/blog/index.md b/docs/blog/index.md deleted file mode 100644 index 3f38542..0000000 --- a/docs/blog/index.md +++ /dev/null @@ -1 +0,0 @@ -# Latest Posts diff --git a/docs/blog/posts/building_agentic_ai_with_jac.md b/docs/blog/posts/building_agentic_ai_with_jac.md index 3957f6b..6d21481 100644 --- a/docs/blog/posts/building_agentic_ai_with_jac.md +++ b/docs/blog/posts/building_agentic_ai_with_jac.md @@ -1,10 +1,10 @@ --- date: 2026-05-12 authors: - - jayanaka +- jayanaka categories: - - Jac Programming - - AI +- Jac Programming +- AI slug: building-agentic-ai-with-jac --- @@ -14,7 +14,7 @@ Most of an agent codebase is not the agent. It is the supporting code every deve -## The Problem +## The Problem **The same intent gets written twice.** A tool is a function *and* a JSON schema describing it. A structured output is a Pydantic class *and* a `response_format` argument. If we rename the function but forget the JSON spec, the spec still advertises the old name to the model. If we rename a field but forget the prompt that asks for it, the prompt still references the old one. Nothing in the build catches the mismatch, because the link between the code and the string only exists in the developer's head. Drift is silent until the model misbehaves at runtime. @@ -32,7 +32,7 @@ The answer is already in the [**Jac**](https://docs.jaseci.org/) programming lan !!! info "About the code" - Every Jac snippet below is self-contained and runs with `jac run .jac` against a configured model. Copy any block, save it, and you'll see the agent execute end-to-end. + Every Jac snippet below runs with `jac run .jac` once a model is bound to `llm` — either project-wide in `jac.toml` or per file with `import from byllm.llm { Model }` and `glob llm = Model(model_name="openai/gpt-4o-mini");`. Complete, runnable versions of all seven patterns live in the companion repo: [**github.com/Jayanaka-98/agentic-ai-with-jac**](https://github.com/Jayanaka-98/agentic-ai-with-jac). ]; + } can do_draft with Draft entry { self.text = here.run(self.topic); visit [-->]; @@ -331,12 +338,20 @@ node Evaluate { def run(draft: str, topic: str) -> Review by llm(); } node Revise { def run(draft: str, feedback: str) -> str by llm(); } node Approved {} +sem Draft.run = "Write a first-draft tutorial section on the topic."; +sem Evaluate.run = "Critique the draft as a tutorial on the topic and return a typed verdict."; +sem Revise.run = "Rewrite the draft to address the feedback."; + walker TutorialWriter { has topic: str; has draft: str = ""; has feedback: str = ""; has version: int = 1; + can begin with Root entry { + visit [-->]; + } + can do_draft with Draft entry { self.draft = here.run(self.topic); visit [-->]; @@ -393,6 +408,8 @@ walker HardwareResearcher { self.result = self.investigate(self.topic); } } +sem HardwareResearcher.investigate = "Research the topic from a hardware angle using the tools."; + walker SoftwareResearcher { has topic: str; has result: str = ""; @@ -401,6 +418,8 @@ walker SoftwareResearcher { self.result = self.investigate(self.topic); } } +sem SoftwareResearcher.investigate = "Research the topic from a software angle using the tools."; + walker AIResearcher { has topic: str; has result: str = ""; @@ -409,24 +428,31 @@ walker AIResearcher { self.result = self.investigate(self.topic); } } +sem AIResearcher.investigate = "Research the topic from an AI/ML angle using the tools."; walker SurveyAgent { has topic: str; has response: str = ""; - def synthesize(topic: str, hw: str, sw: str, ai: str) -> str by llm(); + def synthesize( + topic: str, + hw: HardwareResearcher, + sw: SoftwareResearcher, + ai: AIResearcher + ) -> str by llm(); can start with Root entry { hw_task = flow root spawn HardwareResearcher(topic=self.topic); sw_task = flow root spawn SoftwareResearcher(topic=self.topic); ai_task = flow root spawn AIResearcher(topic=self.topic); - hw: any = wait hw_task; - sw: any = wait sw_task; - ai: any = wait ai_task; + hw: HardwareResearcher = (wait hw_task) as HardwareResearcher; + sw: SoftwareResearcher = (wait sw_task) as SoftwareResearcher; + ai: AIResearcher = (wait ai_task) as AIResearcher; - self.response = self.synthesize(self.topic, hw.result, sw.result, ai.result); + self.response = self.synthesize(self.topic, hw, sw, ai); } } +sem SurveyAgent.synthesize = "Combine the hardware, software, and AI research notes into one survey."; with entry { root spawn SurveyAgent(topic="Efficient inference for large language models"); @@ -460,3 +486,7 @@ Those languages weren't chosen because they fit agents. They were chosen for eco !!! quote "" **With these patterns in the language, building an agent is just building the agent.** + +
+ +
diff --git a/docs/extra.css b/docs/extra.css deleted file mode 100644 index 5e00139..0000000 --- a/docs/extra.css +++ /dev/null @@ -1,954 +0,0 @@ -/* Import Google Fonts */ -@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap'); - -/* CSS Variable System - Jaseci Corporate Dark Theme */ -:root { - /* Color Palette */ - --primary: #ff6b35; - --primary-hover: #ff7b4a; - --primary-light: #f7931e; - --secondary: #f7931e; - --secondary-hover: #ffa52f; - --background: #1a1a1a; - --surface: #2d2d2d; - --surface-hover: #3a3a3a; - --border: #404040; - --border-light: #4a4a4a; - --text-primary: #ffffff; - --text-secondary: #cccccc; - --text-muted: #999999; - --text-inverse: #000000; - - /* Code Colors */ - --code-background: #0d1117; - --code-border: #30363d; - --code-text: #e6edf3; - - /* Status Colors */ - --success: #28a745; - --warning: #ffc107; - --error: #dc3545; - --info: #17a2b8; - - /* Typography Scale */ - --font-family-text: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif; - --font-family-code: 'Roboto Mono', 'Fira Code', 'Consolas', 'Monaco', 'Courier New', monospace; - --text-xs: clamp(0.55rem, 1.6vw, 0.65rem); - --text-sm: clamp(0.7rem, 1.8vw, 0.8rem); - --text-base: clamp(0.8rem, 1.9vw, 0.9rem); - --text-lg: clamp(0.9rem, 2vw, 1rem); - --text-xl: clamp(1rem, 2.2vw, 1.125rem); - --text-2xl: clamp(1.3rem, 3vw, 1.5rem); - --text-3xl: clamp(1.5rem, 3.5vw, 1.75rem); - - /* Spacing Scale */ - --space-1: 0.25rem; - --space-2: 0.5rem; - --space-3: 0.75rem; - --space-4: 1rem; - --space-5: 1.25rem; - --space-6: 1.5rem; - --space-8: 2rem; - --space-10: 2.5rem; - --space-12: 3rem; - --space-16: 4rem; - --space-20: 5rem; - - /* Border Radius */ - --radius-sm: 4px; - --radius-md: 6px; - --radius-lg: 8px; - --radius-xl: 12px; - --radius-full: 9999px; - - /* Transitions */ - --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1); - --transition-base: 300ms cubic-bezier(0.4, 0, 0.2, 1); - --transition-slow: 500ms cubic-bezier(0.4, 0, 0.2, 1); - - /* Shadows */ - --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); - --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); - --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); - --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); - --shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25); - - /* Gradients */ - --gradient-primary: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); - --gradient-primary-hover: linear-gradient(90deg, var(--primary-hover) 0%, var(--secondary-hover) 100%); - --gradient-surface: linear-gradient(135deg, var(--surface) 0%, var(--surface-hover) 100%); -} - -/* Custom Accent Color for MkDocs Material */ -:root { - --md-accent-fg-color: var(--primary); - --md-accent-fg-color--transparent: rgba(255, 107, 53, 0.1); - --md-accent-bg-color: rgba(255, 107, 53, 0.1); -} - -/* Light Theme Overrides */ -[data-md-color-scheme="default"] { - --md-default-bg-color: var(--background); - --md-default-fg-color: var(--text-primary); - --md-default-fg-color--light: var(--text-secondary); - --md-default-fg-color--lighter: var(--text-muted); - --md-default-fg-color--lightest: var(--border); - --md-code-bg-color: var(--code-background); - --md-code-fg-color: var(--code-text); -} - -/* Dark Theme Overrides */ -[data-md-color-scheme="slate"] { - --md-default-bg-color: var(--background); - --md-default-fg-color: var(--text-primary); - --md-default-fg-color--light: var(--text-secondary); - --md-default-fg-color--lighter: var(--text-muted); - --md-default-fg-color--lightest: var(--border); - --md-code-bg-color: var(--code-background); - --md-code-fg-color: var(--code-text); -} - -/* Typography Overrides */ -.md-typeset { - font-family: var(--font-family-text); - font-size: var(--text-base); - line-height: 1.6; - color: var(--text-primary); -} - -.md-typeset h1, -.md-typeset h2, -.md-typeset h3, -.md-typeset h4, -.md-typeset h5, -.md-typeset h6 { - font-family: var(--font-family-text); - font-weight: 700; - color: var(--text-primary); -} - -.md-typeset code { - font-family: var(--font-family-code); - background-color: var(--surface); - color: var(--text-primary); - padding: var(--space-1) var(--space-2); - border-radius: var(--radius-sm); - border: 1px solid var(--border); -} - -/* ── Jac Code Block Component ── */ -.code-block { - margin: 1.2em 0; - padding: 0; - background: #0d1117; - color: #c9d1d9; - border: 1px solid #21262d; - border-radius: 6px; - overflow: hidden; - transition: border-color 0.2s ease, box-shadow 0.2s ease; -} - -.code-block.jcb-running { - border-color: #f0883e; -} - -/* Loading placeholder */ -.jcb-loading { - height: 3px; - overflow: hidden; -} - -.jcb-loading-bar { - height: 100%; - width: 40%; - background: #f0883e; - border-radius: 3px; - animation: jcb-slide 1s ease-in-out infinite; -} - -@keyframes jcb-slide { - 0% { transform: translateX(-100%); } - 100% { transform: translateX(350%); } -} - -/* Editor wrapper */ -.jcb-editor-wrap { - position: relative; -} - -.jcb-editor-wrap .jac-code { - border: none !important; -} - -/* Thin progress bar below editor */ -.jcb-progress { - height: 2px; - width: 100%; - background: #161b22; - position: relative; - overflow: hidden; -} - -.jcb-progress::after { - content: ""; - position: absolute; - top: 0; - left: 0; - height: 100%; - width: 40%; - background: linear-gradient(90deg, transparent, #f0883e, transparent); - animation: jcb-slide 1.2s ease-in-out infinite; -} - -/* Action buttons row */ -.jcb-actions { - display: flex; - justify-content: flex-start; - gap: 6px; - padding: 8px 12px; - background: #010409; - border-top: 1px solid #21262d; -} - -.jcb-btn { - display: inline-flex; - align-items: center; - gap: 5px; - padding: 5px 14px; - font-size: 12px; - font-weight: 500; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - border: 1px solid #30363d; - border-radius: 6px; - cursor: pointer; - transition: all 0.12s ease; - outline: none; - line-height: 18px; - background: transparent; - color: #c9d1d9; -} - -.jcb-btn:hover:not(:disabled) { - background: #21262d; - border-color: #8b949e; - color: #f0f6fc; -} - -.jcb-btn:disabled { - opacity: 0.35; - cursor: not-allowed; -} - -/* Color accents on hover */ -.jcb-btn--run:hover:not(:disabled) { - color: #3fb950; - border-color: #238636; - background: rgba(35, 134, 42, 0.1); -} - -.jcb-btn--serve:hover:not(:disabled) { - color: #58a6ff; - border-color: #1f6feb; - background: rgba(31, 111, 235, 0.1); -} - -.jcb-btn--graph:hover:not(:disabled) { - color: #bc8cff; - border-color: #8957e5; - background: rgba(137, 87, 229, 0.1); -} - -/* Running state button highlight */ -.jcb-running .jcb-btn--run, -.jcb-running .jcb-btn--serve, -.jcb-running .jcb-btn--graph { - border-color: #30363d; -} - -/* Output area */ -.jcb-output-wrap { - border-top: 1px solid #21262d; -} - -.code-block .code-output { - margin: 0; - background: #010409; - color: #7ee787; - padding: 10px 14px; - font-family: 'Fira Mono', 'JetBrains Mono', 'Consolas', monospace; - font-size: 13px; - line-height: 1.5; - max-height: 400px; - overflow: auto; - white-space: pre-wrap; - word-break: break-word; - border-radius: 0; - border: none; -} - -.code-block .code-output:empty::before { - content: " "; - display: block; - height: 1.5em; -} - -/* Input dialog */ -.jcb-input-dialog { - padding: 10px 14px; - background: #0d1117; - border-top: 1px solid #21262d; -} - -.jcb-input-row { - display: flex; - gap: 8px; - align-items: center; -} - -.jcb-input-dialog .input-prompt { - color: #c9d1d9; - font-family: 'Fira Mono', monospace; - font-size: 13px; - font-weight: 500; - white-space: nowrap; -} - -.jcb-input-dialog .user-input { - flex: 1; - padding: 5px 10px; - background: #010409; - border: 1px solid #30363d; - color: #c9d1d9; - border-radius: 6px; - font-family: 'Fira Mono', monospace; - font-size: 13px; - outline: none; - transition: border-color 0.15s ease; -} - -.jcb-input-dialog .user-input:focus { - border-color: #58a6ff; - box-shadow: 0 0 0 2px rgba(88, 166, 255, 0.15); -} - -.jcb-input-submit, -.jcb-input-cancel { - padding: 5px 14px; - border: 1px solid #30363d; - border-radius: 6px; - font-size: 12px; - font-weight: 500; - cursor: pointer; - transition: all 0.12s ease; - background: transparent; - color: #c9d1d9; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; -} - -.jcb-input-submit:hover { - color: #3fb950; - border-color: #238636; - background: rgba(35, 134, 42, 0.1); -} - -.jcb-input-cancel:hover { - color: #f85149; - border-color: #da3633; - background: rgba(218, 54, 51, 0.1); -} - -/* Graph container */ -.code-block .graph-container { - margin: 0; - border-top: 1px solid #21262d; - border-radius: 0; - overflow: auto; - background: #ffffff; - padding: 4px; - height: 340px; - max-height: 800px; -} - -/* Logo - Full height in navbar */ -.md-header__button.md-logo { - padding: 0; - margin: 0; -} - -.md-header__button.md-logo img, -.md-header__button.md-logo svg { - height: 100%; - width: auto; - max-height: 2.4rem; /* Adjust this value to control logo size */ - display: block; -} - -/* viewport for rendered SVG */ -.viz-viewport { - width: 100%; - height: 100%; - overflow: hidden; - position: relative; - touch-action: none; - cursor: grab; - display: flex; - justify-content: center; - align-items: center; - box-sizing: border-box; -} - -.viz-viewport.grabbing { - cursor: grabbing; -} - -.graph-controls { - position: absolute; - top: 8px; - right: 8px; - z-index: 120; - display: flex; - gap: 6px; - pointer-events: auto; -} - -.graph-reset-btn { - color: #111; - padding: 6px 10px; - font-size: 13px; - font-weight: 600; - min-width: 56px; - border-radius: 6px; - border: 1px solid rgba(0,0,0,0.12); - background: rgba(255,255,255,0.95); - cursor: pointer; - box-shadow: 0 1px 4px rgba(0,0,0,0.12); -} - -/* responsive tweak: allow scrolling on small screens */ -@media (max-width: 600px) { - .viz-viewport { - overflow: auto; - } -} - -@import url('https://fonts.googleapis.com/css2?family=Fira+Mono:wght@400;500;700&display=swap'); - -/* Blog preview author hover tooltip - Original styling restored */ -.md-author { - position: relative; - cursor: pointer; -} - -.md-author:hover { - background: rgba(0, 0, 0, 0.08) !important; - transform: translateY(-1px) !important; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) !important; -} - -.md-author img { - width: 36px; - height: 36px; - border-radius: 50%; - object-fit: fill; - border: 1px solid rgba(0, 0, 0, 0.1); -} - -.md-author img:hover { - transform: scale(1.1) !important; - transition: transform 0.2s ease !important; -} - -/* Dark mode adjustments for author */ -[data-md-color-scheme="slate"] .md-author { - background: rgba(255, 255, 255, 0.1); - border: 1px solid rgba(255, 255, 255, 0.1); -} - -[data-md-color-scheme="slate"] .md-author:hover { - background: rgba(255, 255, 255, 0.15) !important; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3) !important; -} - -[data-md-color-scheme="slate"] .md-author img { - border-color: rgba(255, 255, 255, 0.2); -} - -/* Additional theme enhancements for consistency */ -.md-header { - background: var(--surface) !important; - border-bottom: 1px solid var(--border); - box-shadow: var(--shadow-sm); - height: 3.5rem; - display: flex; - align-items: center; - justify-content: space-between; - padding: 0 var(--space-4); -} - -.md-header__inner { - align-items: center; - width: 80%; - max-width: 100%; -} - -.md-header__title { - font-family: var(--font-family-text); - font-weight: 700; - color: var(--text-primary); - margin: 0; - flex-shrink: 1; -} - -/* Enhanced Sidebar Navigation */ -.md-nav { - background: var(--background) !important; -} - -.md-nav--primary .md-nav__item { - border-radius: var(--radius-md); - margin: 2px 0; - transition: var(--transition-base); -} - -.md-nav--primary .md-nav__link { - color: var(--text-secondary) !important; - font-weight: 500; - font-size: var(--text-xs); - padding: var(--space-2) var(--space-3); - border-radius: var(--radius-md); - transition: var(--transition-base); - font-family: var(--font-family-text); - border: 1px solid transparent; -} - -.md-nav--primary .md-nav__link:hover { - color: var(--text-primary) !important; - background: var(--surface) !important; - border-color: var(--border); - transform: translateX(2px); -} - -.md-nav--primary .md-nav__link--active { - color: var(--primary) !important; - background: linear-gradient(135deg, rgba(255, 107, 53, 0.1) 0%, rgba(247, 147, 30, 0.1) 100%) !important; - border-color: var(--primary) !important; - font-weight: 700; - box-shadow: inset 0 0 0 1px rgba(255, 107, 53, 0.2); -} - -.md-nav--primary .md-nav__link--active:hover { - background: linear-gradient(135deg, rgba(255, 107, 53, 0.15) 0%, rgba(247, 147, 30, 0.15) 100%) !important; - transform: translateX(2px); -} - -/* Secondary Navigation */ -.md-nav--secondary .md-nav__link { - color: var(--text-secondary) !important; - font-size: var(--text-xs); - font-weight: 500; - padding: var(--space-1) var(--space-2); - transition: var(--transition-base); - border-left: 3px solid transparent; -} - -.md-nav--secondary .md-nav__link:hover { - color: var(--primary) !important; - background: var(--surface); - border-left-color: var(--primary); -} - -.md-nav--secondary .md-nav__link--active { - color: var(--primary) !important; - background: var(--surface); - border-left-color: var(--primary); - font-weight: 600; -} - -/* Navigation Tree Structure */ -.md-nav--primary .md-nav__item--nested > .md-nav__link::after { - content: ''; - position: absolute; - right: var(--space-2); - top: 50%; - transform: translateY(-50%); - width: 0; - height: 0; - border-left: 4px solid var(--border); - border-top: 3px solid transparent; - border-bottom: 3px solid transparent; - transition: var(--transition-base); -} - -.md-nav--primary .md-nav__item--nested > .md-nav__link:hover::after { - border-left-color: var(--primary); -} - -.md-nav--primary .md-nav__item--active > .md-nav__link::after { - border-left-color: var(--primary); -} - -/* Sidebar Header */ -.md-nav__title { - background: var(--background) !important; - color: var(--text-primary) !important; - font-weight: 700; - font-size: var(--text-sm); - font-family: var(--font-family-text); - padding: var(--space-2) var(--space-3); - border-bottom: 1px solid var(--border); - border-radius: var(--radius-md) var(--radius-md) 0 0; -} - -.md-nav__title .md-nav__button { - color: var(--text-secondary) !important; - transition: var(--transition-base); -} - -.md-nav__title .md-nav__button:hover { - color: var(--primary) !important; -} - -/* Mobile Navigation Adjustments */ -@media (max-width: 76.1875em) { - .md-nav--primary .md-nav__item { - margin: 2px var(--space-2); - } - - .md-nav--primary .md-nav__link { - padding: var(--space-2) var(--space-3); - } -} - -/* Blog post styling enhancements */ -.md-blog-post { - background: var(--surface); - border: 1px solid var(--border); - border-radius: var(--radius-lg); - padding: var(--space-6); - margin-bottom: var(--space-6); - transition: var(--transition-base); -} - -.md-blog-post:hover { - transform: translateY(-2px); - box-shadow: var(--shadow-lg); - border-color: var(--primary); -} - -.md-blog-post__title { - font-family: var(--font-family-text); - font-weight: 700; - color: var(--text-primary); -} - -.md-blog-post__title a { - color: var(--text-primary); - text-decoration: none; - transition: var(--transition-base); -} - -.md-blog-post__title a:hover { - color: var(--primary); -} - -.md-blog-post__excerpt { - color: var(--text-secondary); - line-height: 1.6; -} - -.md-blog-post__meta { - color: var(--text-muted); - font-size: var(--text-xs); - font-family: var(--font-family-text); -} - -/* Search improvements */ -.md-search__input { - background: var(--surface) !important; - color: var(--text-primary) !important; - border: 1px solid var(--border) !important; - border-radius: var(--radius-lg) !important; - font-family: var(--font-family-text); -} - -.md-search__input::placeholder { - color: var(--text-muted) !important; -} - -/* Footer styling */ -.md-footer { - background: var(--surface) !important; - border-top: 1px solid var(--border); - color: var(--text-secondary); -} - -.md-footer__link { - color: var(--text-secondary) !important; - transition: var(--transition-base); -} - -.md-footer__link:hover { - color: var(--primary) !important; -} - -/* Content area improvements */ -.md-content { - font-family: var(--font-family-text); - line-height: 1.7; - max-width: none; - padding-right: var(--space-4); -} - -.md-content h1, -.md-content h2, -.md-content h3, -.md-content h4, -.md-content h5, -.md-content h6 { - font-weight: 700; - margin-top: var(--space-8); - margin-bottom: var(--space-4); - color: var(--text-primary); -} - -.md-content p { - margin-bottom: var(--space-4); - color: var(--text-secondary); -} - -.md-content a { - color: var(--primary); - text-decoration: none; - font-weight: 500; - transition: var(--transition-base); -} - -.md-content a:hover { - color: var(--primary-hover); - text-decoration: underline; -} - -.md-content blockquote { - border-left: 4px solid var(--primary); - background: var(--surface); - padding: var(--space-4); - border-radius: var(--radius-md); - margin: var(--space-6) 0; - font-style: italic; -} - -.md-content table { - border: 1px solid var(--border); - border-radius: var(--radius-lg); - overflow: hidden; -} - -.md-content th, -.md-content td { - border: 1px solid var(--border); - padding: var(--space-3); -} - -.md-content th { - background: var(--surface); - font-weight: 700; - font-family: var(--font-family-text); -} - -/* Adjust main container for better space utilization */ -.md-main__inner { - max-width: 100%; -} - -.md-content > .container { - max-width: 100%; - padding: 0; -} - -/* Responsive adjustments for content width */ -@media (min-width: 76.25em) { - .md-content { - margin-right: 0; - } -} - -/* Button consistency */ -.md-button { - background: var(--gradient-primary); - color: var(--text-inverse); - border: none; - border-radius: var(--radius-lg); - font-weight: 600; - font-family: var(--font-family-text); - padding: var(--space-3) var(--space-6); - transition: var(--transition-base); - box-shadow: var(--shadow-md); -} - -.md-button:hover { - background: var(--gradient-primary-hover); - transform: translateY(-2px); - box-shadow: var(--shadow-lg); - text-decoration: none; -} - -.md-button--primary { - background: var(--gradient-primary); -} - -.md-button--primary:hover { - background: var(--gradient-primary-hover); -} - -/* Centered images in blog posts */ -.md-content img { - display: block; - margin-left: auto; - margin-right: auto; -} - -/* ---------- collapsible code blocks (Medium-style fade + floating pill) ---------- */ -.cb-collapse { - position: relative; - display: flex; - flex-direction: column; - margin-bottom: 0.5em; -} -.cb-toggle { - position: absolute; - opacity: 0; - pointer-events: none; -} -.cb-content { - order: 1; - max-height: var(--cb-max-height, 9em); - overflow: hidden; - position: relative; - transition: max-height 0.2s ease; -} -/* The fade-to-page-background gradient over the bottom of the collapsed code */ -.cb-content::after { - content: ""; - position: absolute; - bottom: 0; left: 0; right: 0; - height: 7em; - background: linear-gradient( - to bottom, - transparent 0%, - var(--md-default-bg-color, #ffffff) 70% - ); - pointer-events: none; -} - -/* The floating pill button, centered over the gradient. Background matches the - page so it appears to sit on it; text uses the page foreground color. */ -.cb-label { - order: 2; - position: absolute; - bottom: 0.9em; - left: 50%; - transform: translateX(-50%); - z-index: 5; - cursor: pointer; - color: var(--md-default-fg-color, #111); - background: var(--md-default-bg-color, #ffffff); - font: 500 0.82rem/1.4 -apple-system, system-ui, sans-serif; - padding: 8px 20px; - border-radius: 999px; - border: 1px solid var(--md-default-fg-color--lightest, rgba(0,0,0,0.12)); - user-select: none; - transition: transform 0.15s, box-shadow 0.15s, background 0.15s; - box-shadow: 0 2px 8px rgba(0,0,0,0.15); - white-space: nowrap; -} -.cb-label:hover { - background: var(--md-default-fg-color--lightest, rgba(0,0,0,0.04)); - transform: translateX(-50%) translateY(-1px); - box-shadow: 0 4px 14px rgba(0,0,0,0.2); -} -.cb-label::before { content: "▼ Show full code (" attr(data-lines) " lines)"; } - -/* Expanded state: full height, no gradient, label flows into the document below */ -.cb-toggle:checked ~ .cb-content { max-height: none; } -.cb-toggle:checked ~ .cb-content::after { display: none; } -.cb-toggle:checked + .cb-label { - position: relative; - bottom: auto; - left: auto; - transform: none; - margin: 8px auto 0; - align-self: center; -} -.cb-toggle:checked + .cb-label:hover { - transform: translateY(-1px); -} -.cb-toggle:checked + .cb-label::before { content: "▲ Collapse"; } - -/* ---------- blog index: each post excerpt rendered as a distinct card ---------- */ -.md-content .md-post--excerpt { - position: relative; - border: 1px solid var(--md-default-fg-color--lightest, rgba(0,0,0,0.12)); - border-radius: 14px; - background: var(--md-default-bg-color, #ffffff); - padding: 1.6em 1.8em 1.6em; - margin-bottom: 2em; - box-shadow: 0 1px 3px rgba(0,0,0,0.05); - transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease; -} -.md-content .md-post--excerpt:hover { - transform: translateY(-2px); - box-shadow: 0 8px 24px rgba(0,0,0,0.10); - border-color: var(--md-default-fg-color--lighter, rgba(0,0,0,0.25)); -} - -.md-content .md-post--excerpt .md-post__content { position: relative; } - -/* The peek block: a few lines after injected at build time by the - inject_excerpt_peek hook, with a gradient overlay that fades them into the - card background. The pre- content above renders normally. */ -.md-content .md-post--excerpt .md-post__peek { - position: relative; - margin-top: 0.4em; - max-height: 5em; - overflow: hidden; -} -.md-content .md-post--excerpt .md-post__peek::after { - content: ""; - position: absolute; - inset: 0; - pointer-events: none; - background: linear-gradient( - to bottom, - transparent 0%, - var(--md-default-bg-color, #ffffff) 90% - ); -} - -/* "Show more" pill — same visual language as .cb-label on code blocks. */ -.md-content .md-post--excerpt .md-post__action { - display: flex; - justify-content: center; - margin: 0.4em 0 0; -} -.md-content .md-post--excerpt .md-post__action a { - display: inline-block; - color: var(--md-default-fg-color, #111); - background: var(--md-default-bg-color, #ffffff); - font: 500 0 -apple-system, system-ui, sans-serif; - padding: 8px 22px; - border-radius: 999px; - border: 1px solid var(--md-default-fg-color--lightest, rgba(0,0,0,0.12)); - box-shadow: 0 2px 8px rgba(0,0,0,0.15); - text-decoration: none; - white-space: nowrap; - transition: transform 0.15s, box-shadow 0.15s, background 0.15s; -} -.md-content .md-post--excerpt .md-post__action a:hover { - background: var(--md-default-fg-color--lightest, rgba(0,0,0,0.04)); - transform: translateY(-1px); - box-shadow: 0 4px 14px rgba(0,0,0,0.2); -} -.md-content .md-post--excerpt .md-post__action a::before { - content: "Show more ▸"; - font-size: 0.82rem; -} diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index 5ba448a..0000000 --- a/docs/index.md +++ /dev/null @@ -1,4 +0,0 @@ -# Jaseci Labs - - - diff --git a/docs/js/author-tooltips.js b/docs/js/author-tooltips.js deleted file mode 100644 index 8d1446d..0000000 --- a/docs/js/author-tooltips.js +++ /dev/null @@ -1,12 +0,0 @@ -// Add title attributes to author images for tooltips -document.addEventListener('DOMContentLoaded', function() { - // Find all author images in blog previews - const authorImages = document.querySelectorAll('.md-post__authors .md-author img'); - - authorImages.forEach(img => { - // If image has alt text but no title, copy alt to title - if (img.alt && !img.title) { - img.title = img.alt; - } - }); -}); \ No newline at end of file diff --git a/docs/js/collapsible-code.js b/docs/js/collapsible-code.js deleted file mode 100644 index 476dedb..0000000 --- a/docs/js/collapsible-code.js +++ /dev/null @@ -1,67 +0,0 @@ -// Opt-in collapsible code blocks. -// -// Usage in markdown: -//
-// -// ```jac -// ...code... -// ``` -// -//
-// -// data-lines is optional (default 5). Only blocks longer than data-lines are collapsed. - -(function () { - const DEFAULT_LINES = 5; - let counter = 0; - - function applyCollapse() { - const wrappers = document.querySelectorAll('div.collapse'); - wrappers.forEach(function (wrapper) { - if (wrapper.dataset.cbProcessed) return; - wrapper.dataset.cbProcessed = '1'; - - const block = wrapper.querySelector('.highlight, .highlighttable'); - if (!block) return; - const pre = block.querySelector('pre'); - if (!pre) return; - - const text = pre.textContent || ''; - const totalLines = text.replace(/\n+$/, '').split('\n').length; - const showLines = parseInt(wrapper.dataset.lines || DEFAULT_LINES, 10); - - if (totalLines <= showLines + 1) return; // already short enough - - // Compute max-height from the rendered line-height of the
-            const cs = window.getComputedStyle(pre);
-            const lh = parseFloat(cs.lineHeight) || 24;
-            const padTop = parseFloat(cs.paddingTop) || 0;
-            const padBottom = parseFloat(cs.paddingBottom) || 0;
-            const maxH = (lh * showLines) + padTop + padBottom + 4;
-
-            const id = 'cb-' + (counter++) + '-' + Math.random().toString(36).slice(2, 7);
-            const checkbox = document.createElement('input');
-            checkbox.type = 'checkbox';
-            checkbox.id = id;
-            checkbox.className = 'cb-toggle';
-
-            const label = document.createElement('label');
-            label.htmlFor = id;
-            label.className = 'cb-label';
-            label.setAttribute('data-lines', String(totalLines));
-
-            wrapper.classList.add('cb-collapse');
-            block.classList.add('cb-content');
-            block.style.setProperty('--cb-max-height', maxH + 'px');
-
-            wrapper.insertBefore(checkbox, block);
-            wrapper.insertBefore(label, block);
-        });
-    }
-
-    if (document.readyState === 'loading') {
-        document.addEventListener('DOMContentLoaded', applyCollapse);
-    } else {
-        applyCollapse();
-    }
-})();
diff --git a/docs/js/jac.monarch.js b/docs/js/jac.monarch.js
deleted file mode 100644
index 8b0636a..0000000
--- a/docs/js/jac.monarch.js
+++ /dev/null
@@ -1,192 +0,0 @@
-// Monarch syntax definition for Jaclang
-window.jaclangMonarchSyntax = {
-  defaultToken: '',
-  tokenPostfix: '.jac',
-
-  functionKeywords: ['can', 'def', 'impl', 'with'],
-  variableKeywords: ['has', 'glob'],
-  typeKeywords: ['class', 'node', 'edge', 'walker', 'enum', 'obj', 'test', 'root', 'here'],
-  controlKeywords: [
-    'import', 'include', 'from', 'as',
-    'if', 'else', 'elif', 'while', 'for', 'in', 'match', 'case',
-    'return', 'break', 'continue', 'spawn', 'ignore', 'visit', 'disengage',
-    'entry', 'exit', 'pass', 'try', 'except', 'finally', 'raise', 'assert',
-    'async', 'await', 'lambda', 'by', 'to', 'del', 'check'
-  ],
-
-  literalKeywords: ['True', 'False', 'None'],
-
-  typeIdentifiers: [
-    'str', 'int', 'float', 'list', 'tuple', 'set', 'dict',
-    'bool', 'bytes', 'any', 'type'
-  ],
-
-  operators: [
-    '=', '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '<<=', '>>=', '**=',
-    '+', '-', '*', '**', '/', '//', '%', '@',
-    '==', '!=', '<', '<=', '>', '>=',
-    '<<', '>>', '&', '|', '^', '~',
-  ],
-
-  logicalOperators: [
-    'and', 'or', 'not', 'is', 'in', 'not in', 'is not'
-  ],
-
-  symbols: /[=> {
-    const { type, code, value, sab } = event.data;
-
-    if (type === "init") {
-        sabRef = sab;
-        self.shared_buf = sabRef;
-
-        importScripts("https://cdn.jsdelivr.net/pyodide/v0.27.0/full/pyodide.js");
-        pyodide = await loadPyodide();
-
-        // install required packages via micropip
-        await pyodide.loadPackage("micropip");
-        await pyodide.loadPackage("sqlite3");
-        await pyodide.runPythonAsync(`
-import micropip
-await micropip.install('pluggy')
-        `);
-
-        await loadPythonResources(pyodide);
-        await pyodide.runPythonAsync(`
-from jaclang.cli.commands import execution, tools
-`);
-        await pyodide.runPythonAsync(`
-from js import postMessage, Atomics, Int32Array, Uint8Array, shared_buf
-import builtins
-import sys
-
-ctrl = Int32Array.new(shared_buf)
-data = Uint8Array.new(shared_buf, 8)
-FLAG, LEN = 0, 1
-
-# Custom output handler for real-time streaming
-class StreamingOutput:
-    def __init__(self, stream_type="stdout"):
-        self.stream_type = stream_type
-
-    def write(self, text):
-        if text:
-            import json
-            message = json.dumps({
-                "type": "streaming_output",
-                "output": text,
-                "stream": self.stream_type
-            })
-            postMessage(message)
-        return len(text)
-
-    def flush(self):
-        pass
-
-    def isatty(self):
-        # Always return False for web playground to disable colors
-        # This prevents messy ANSI color codes in the output
-        return False
-
-def pyodide_input(prompt=""):
-    prompt_str = str(prompt)
-
-    import json
-    message = json.dumps({"type": "input_request", "prompt": prompt_str})
-    postMessage(message)
-
-    Atomics.wait(ctrl, FLAG, 0)
-
-    n = ctrl[LEN]
-    b = bytes(data.subarray(0, n).to_py())
-    s = b.decode("utf-8", errors="replace")
-
-    Atomics.store(ctrl, FLAG, 0)
-    return s
-
-builtins.input = pyodide_input
-        `);
-        self.postMessage({ type: "ready" });
-        return;
-    }
-
-    if (!pyodide) {
-        return;
-    }
-
-    try {
-        const jacCode = JSON.stringify(code);
-        const cliCommand = type === "serve" ? "start" : type === "dot" ? "dot" : "run";
-        const output = await pyodide.runPythonAsync(`
-from jaclang.cli.commands import execution, tools
-import sys, json, os
-import tempfile
-
-# Set up streaming output
-streaming_stdout = StreamingOutput("stdout")
-streaming_stderr = StreamingOutput("stderr")
-original_stdout = sys.stdout
-original_stderr = sys.stderr
-
-sys.stdout = streaming_stdout
-sys.stderr = streaming_stderr
-
-jac_code = ${jacCode}
-with tempfile.NamedTemporaryFile(mode="w", suffix=".jac", delete=False) as temp_jac:
-    temp_jac.write(jac_code)
-    temp_jac_path = temp_jac.name
-
-try:
-    if "${cliCommand}" == "start":
-        execution.start(temp_jac_path)
-
-    elif "${cliCommand}" == "dot":
-        dot_path = "/home/pyodide/temp.dot"
-
-        if os.path.exists(dot_path):
-            try:
-                os.remove(dot_path)
-            except Exception as e:
-                print(f"Warning: Could not remove old DOT file: {e}", file=sys.stderr)
-
-        tools.dot(temp_jac_path, saveto=dot_path)
-
-        if os.path.exists(dot_path):
-            with open(dot_path, "r") as f:
-                dot_content = f.read()
-            if not dot_content.strip():
-                print("Error: No DOT content generated.", file=sys.stderr)
-            else:
-                postMessage(json.dumps({"type": "dot", "dot": dot_content}))
-        else:
-            print("Error: DOT file not found after generation.", file=sys.stderr)
-
-    else:
-        execution.run(temp_jac_path)
-
-except SystemExit:
-    # The Jac compiler may call SystemExit on fatal errors (e.g., syntax errors).
-    # Detailed error reports are already emitted to stderr by the parser,
-    # so we suppress the exit here to avoid re-raising or duplicating messages.
-    pass
-except Exception as e:
-    print(f"Error: {e}", file=sys.stderr)
-finally:
-    try:
-        os.remove(temp_jac_path)
-    except Exception as e:
-        print(f"Warning: Could not remove temporary file: {e}", file=sys.stderr)
-
-# Restore original streams
-sys.stdout = original_stdout
-sys.stderr = original_stderr
-        `);
-        self.postMessage({ type: "execution_complete" });
-    } catch (error) {
-        self.postMessage({ type: "error", error: error.toString() });
-    }
-};
diff --git a/docs/js/run-code.js b/docs/js/run-code.js
deleted file mode 100644
index 00db9d4..0000000
--- a/docs/js/run-code.js
+++ /dev/null
@@ -1,481 +0,0 @@
-let pyodideWorker = null;
-let pyodideReady = false;
-let pyodideInitPromise = null;
-let monacoLoaded = false;
-let monacoLoadPromise = null;
-let sab = null;
-const initializedBlocks = new WeakSet();
-
-// Initialize Pyodide Worker
-function initPyodideWorker() {
-    if (pyodideWorker) return pyodideInitPromise;
-    if (pyodideInitPromise) return pyodideInitPromise;
-
-    const DATA_CAP = 4096;
-    sab = new SharedArrayBuffer(8 + DATA_CAP);
-    ctrl = new Int32Array(sab, 0, 2);
-    dataBytes = new Uint8Array(sab, 8);
-
-    pyodideWorker = new Worker("/js/pyodide-worker.js");
-    pyodideInitPromise = new Promise((resolve, reject) => {
-        pyodideWorker.onmessage = (event) => {
-            if (event.data.type === "ready") {
-                pyodideReady = true;
-                resolve();
-            }
-        };
-        pyodideWorker.onerror = (e) => reject(e);
-    });
-    pyodideWorker.postMessage({ type: "init", sab });
-    return pyodideInitPromise;
-}
-
-function executeJacCodeInWorker(code, inputHandler, commandType = "run") {
-    return new Promise(async (resolve, reject) => {
-        await initPyodideWorker();
-        const handleMessage = async (event) => {
-            let message;
-            if (typeof event.data === "string") {
-                message = JSON.parse(event.data);
-            } else {
-                message = event.data;
-            }
-
-            if (message.type === "streaming_output") {
-                const event = new CustomEvent('jacOutputUpdate', {
-                    detail: { output: message.output, stream: message.stream }
-                });
-                document.dispatchEvent(event);
-            } else if (message.type === "dot") {
-                const event = new CustomEvent('jacDotOutput', { detail: { dot: message.dot }});
-                document.dispatchEvent(event);
-            } else if (message.type === "execution_complete") {
-                pyodideWorker.removeEventListener("message", handleMessage);
-                resolve("");
-            } else if (message.type === "input_request") {
-                try {
-                    const userInput = await inputHandler(message.prompt || "Enter input:");
-                    const enc = new TextEncoder();
-                    const bytes = enc.encode(userInput);
-                    const n = Math.min(bytes.length, dataBytes.length);
-                    dataBytes.set(bytes.subarray(0, n), 0);
-                    Atomics.store(ctrl, 1, n);
-                    Atomics.store(ctrl, 0, 1);
-                    Atomics.notify(ctrl, 0, 1);
-                } catch (error) {
-                    pyodideWorker.removeEventListener("message", handleMessage);
-                    reject(error);
-                }
-            } else if (message.type === "error") {
-                pyodideWorker.removeEventListener("message", handleMessage);
-                reject(message.error);
-            }
-        };
-        pyodideWorker.addEventListener("message", handleMessage);
-        pyodideWorker.postMessage({ type: commandType, code });
-    });
-}
-
-// Load Monaco Editor Globally
-function loadMonacoEditor() {
-    if (monacoLoaded) return monacoLoadPromise;
-    if (monacoLoadPromise) return monacoLoadPromise;
-
-    monacoLoadPromise = new Promise((resolve, reject) => {
-        require.config({ paths: { vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.52.2/min/vs' } });
-        require(['vs/editor/editor.main'], function () {
-            monacoLoaded = true;
-            monaco.languages.register({ id: 'jac' });
-            monaco.languages.setMonarchTokensProvider('jac', window.jaclangMonarchSyntax);
-
-            fetch('/../playground/language-configuration.json')
-                .then(resp => resp.json())
-                .then(config => monaco.languages.setLanguageConfiguration('jac', config));
-            monaco.editor.defineTheme('jac-theme', {
-                base: 'vs-dark',
-                inherit: true,
-                rules: window.jacThemeRules,
-                colors: window.jacThemeColors
-            });
-            monaco.editor.setTheme('jac-theme');
-            resolve();
-        }, reject);
-    });
-    return monacoLoadPromise;
-}
-
-// SVG icons
-const ICONS = {
-    run: '',
-    serve: '',
-    graph: '',
-};
-
-// Setup Code Block with Monaco Editor
-async function setupCodeBlock(div) {
-    if (div._monacoInitialized) return;
-    div._monacoInitialized = true;
-    const originalCode = div.textContent.trim();
-
-    div.innerHTML = `
`; - - await loadMonacoEditor(); - - div.innerHTML = ` -
-
- -
-
- - - -
- - - - `; - - const container = div.querySelector(".jac-code"); - const runButton = div.querySelector(".run-code-btn"); - const serveButton = div.querySelector(".serve-code-btn"); - const dotButton = div.querySelector(".dot-code-btn"); - const graphContainer = div.querySelector(".graph-container"); - const outputWrap = div.querySelector(".jcb-output-wrap"); - const outputBlock = div.querySelector(".code-output"); - const progressBar = div.querySelector(".jcb-progress"); - const inputDialog = div.querySelector(".jcb-input-dialog"); - const inputPrompt = div.querySelector(".input-prompt"); - const userInput = div.querySelector(".user-input"); - const submitButton = div.querySelector(".submit-input"); - const cancelButton = div.querySelector(".cancel-input"); - - // Handle button visibility based on classnames - serveButton.style.display = 'none'; - dotButton.style.display = 'none'; - if (div.classList.contains('serve-only')) { - runButton.style.display = 'none'; - serveButton.style.display = 'inline-flex'; - } else if (div.classList.contains('run-serve')) { - serveButton.style.display = 'inline-flex'; - } else if (div.classList.contains('run-dot')) { - dotButton.style.display = 'inline-flex'; - } else if (div.classList.contains('serve-dot')) { - runButton.style.display = 'none'; - serveButton.style.display = 'inline-flex'; - dotButton.style.display = 'inline-flex'; - } else if (div.classList.contains('run-dot-serve')) { - dotButton.style.display = 'inline-flex'; - serveButton.style.display = 'inline-flex'; - } - - const editor = monaco.editor.create(container, { - value: originalCode || '# Write your Jac code here', - language: 'jac', - theme: 'jac-theme', - scrollBeyondLastLine: false, - scrollbar: { vertical: 'hidden', handleMouseWheel: false }, - minimap: { enabled: false }, - automaticLayout: true, - padding: { top: 10, bottom: 10 }, - fontSize: 13.5, - lineHeight: 20, - renderLineHighlight: 'none', - overviewRulerLanes: 0, - hideCursorInOverviewRuler: true, - overviewRulerBorder: false, - }); - - function updateEditorHeight() { - const lineCount = editor.getModel().getLineCount(); - const lineHeight = editor.getOption(monaco.editor.EditorOption.lineHeight); - const height = lineCount * lineHeight + 20; - container.style.height = `${height}px`; - editor.layout(); - } - updateEditorHeight(); - editor.onDidChangeModelContent(updateEditorHeight); - - // State helpers - function setRunning() { - progressBar.style.display = "block"; - div.classList.add("jcb-running"); - runButton.disabled = true; - serveButton.disabled = true; - dotButton.disabled = true; - } - - function setIdle() { - progressBar.style.display = "none"; - div.classList.remove("jcb-running"); - runButton.disabled = false; - serveButton.disabled = false; - dotButton.disabled = false; - } - - // Input handler - function createInputHandler() { - return function(prompt) { - return new Promise((resolve, reject) => { - inputPrompt.textContent = prompt; - inputDialog.style.display = "block"; - userInput.value = ""; - userInput.focus(); - - const handleSubmit = () => { - const value = userInput.value; - inputDialog.style.display = "none"; - outputBlock.textContent += `${prompt}${value}\n`; - outputBlock.scrollTop = outputBlock.scrollHeight; - resolve(value); - cleanup(); - }; - - const handleCancel = () => { - inputDialog.style.display = "none"; - reject(new Error("Input cancelled by user")); - cleanup(); - }; - - const handleKeyPress = (e) => { - if (e.key === "Enter") { e.preventDefault(); handleSubmit(); } - else if (e.key === "Escape") { e.preventDefault(); handleCancel(); } - }; - - const cleanup = () => { - submitButton.removeEventListener("click", handleSubmit); - cancelButton.removeEventListener("click", handleCancel); - userInput.removeEventListener("keypress", handleKeyPress); - }; - - submitButton.addEventListener("click", handleSubmit); - cancelButton.addEventListener("click", handleCancel); - userInput.addEventListener("keypress", handleKeyPress); - }); - }; - } - - function decodeHtmlEntities(str) { - const txt = document.createElement("textarea"); - txt.innerHTML = str; - return txt.value; - } - - function renderDotToGraph(dotText) { - const decoded = decodeHtmlEntities(dotText || ""); - if (!decoded.trim()) { graphContainer.style.display = "none"; return; } - if (typeof Viz === "undefined") { graphContainer.textContent = "Graph rendering library not loaded."; graphContainer.style.display = "block"; return; } - - const viz = new Viz(); - viz.renderSVGElement(decoded) - .then(svgEl => { - graphContainer.innerHTML = ""; - graphContainer.style.display = "block"; - graphContainer.style.position = graphContainer.style.position || "relative"; - - svgEl.style.display = "block"; - svgEl.style.maxWidth = "none"; - svgEl.style.maxHeight = "none"; - - const measureWrap = document.createElement("div"); - measureWrap.style.cssText = "position:absolute;visibility:hidden;pointer-events:none;"; - measureWrap.appendChild(svgEl); - graphContainer.appendChild(measureWrap); - - let svgW = NaN, svgH = NaN; - const vb = svgEl.getAttribute("viewBox"); - if (vb) { - const parts = vb.trim().split(/\s+/); - if (parts.length === 4) { svgW = parseFloat(parts[2]) || svgW; svgH = parseFloat(parts[3]) || svgH; } - } - try { - if (!isFinite(svgW) || !isFinite(svgH)) { - const bbox = svgEl.getBBox(); - svgW = svgW || bbox.width; svgH = svgH || bbox.height; - } - } catch (e) { - const wa = svgEl.getAttribute("width"), ha = svgEl.getAttribute("height"); - svgW = svgW || (wa ? parseFloat(String(wa).replace("px", "")) : 800); - svgH = svgH || (ha ? parseFloat(String(ha).replace("px", "")) : 400); - } - measureWrap.remove(); - if (!isFinite(svgW) || svgW <= 0) svgW = 800; - if (!isFinite(svgH) || svgH <= 0) svgH = 600; - - const containerW = Math.max(100, graphContainer.clientWidth || 800); - const containerH = Math.max(100, graphContainer.clientHeight || 400); - const fitScale = Math.min(containerW / svgW, containerH / svgH); - const displayW = Math.max(1, Math.round(svgW * fitScale)); - const displayH = Math.max(1, Math.round(svgH * fitScale)); - - svgEl.setAttribute("width", displayW); - svgEl.setAttribute("height", displayH); - svgEl.setAttribute("preserveAspectRatio", "xMidYMid meet"); - svgEl.style.display = "block"; - svgEl.style.transformOrigin = "0 0"; - - const wrapper = document.createElement("div"); - wrapper.className = "viz-viewport"; - wrapper.appendChild(svgEl); - - const controls = document.createElement("div"); - controls.className = "graph-controls"; - const resetBtn = document.createElement("button"); - resetBtn.type = "button"; - resetBtn.className = "graph-reset-btn"; - resetBtn.textContent = "Reset"; - controls.appendChild(resetBtn); - - graphContainer.appendChild(wrapper); - graphContainer.appendChild(controls); - - let scale = 1, translate = { x: 0, y: 0 }, isPanning = false, start = {}, startT = {}; - const setTransform = () => svgEl.style.transform = `translate(${translate.x}px, ${translate.y}px) scale(${scale})`; - - const onWheel = (ev) => { - ev.preventDefault(); - const rect = wrapper.getBoundingClientRect(); - const cx = ev.clientX - rect.left, cy = ev.clientY - rect.top; - const prev = scale; - scale = Math.max(0.2, Math.min(6, scale * (ev.deltaY > 0 ? 0.9 : 1.1))); - const px = (cx - translate.x) / prev, py = (cy - translate.y) / prev; - translate.x -= px * (scale - prev); translate.y -= py * (scale - prev); - setTransform(); - }; - - const onPointerDown = (ev) => { - if (ev.button !== 0) return; - isPanning = true; wrapper.setPointerCapture(ev.pointerId); wrapper.style.cursor = "grabbing"; - start = { x: ev.clientX, y: ev.clientY }; startT = { x: translate.x, y: translate.y }; - }; - const onPointerMove = (ev) => { - if (!isPanning) return; - translate.x = startT.x + (ev.clientX - start.x); translate.y = startT.y + (ev.clientY - start.y); - setTransform(); - }; - const onPointerUp = (ev) => { if (!isPanning) return; isPanning = false; try { wrapper.releasePointerCapture(ev.pointerId); } catch {} wrapper.style.cursor = "grab"; }; - - resetBtn.addEventListener("click", (e) => { e.stopPropagation(); scale = 1; translate = { x: 0, y: 0 }; setTransform(); }); - - wrapper.addEventListener("wheel", onWheel, { passive: false }); - wrapper.addEventListener("pointerdown", onPointerDown); - wrapper.addEventListener("pointermove", onPointerMove); - wrapper.addEventListener("pointerup", onPointerUp); - wrapper.addEventListener("pointercancel", onPointerUp); - - setTransform(); - }) - .catch(err => { - console.error("Viz render error:", err); - graphContainer.style.display = "block"; - graphContainer.textContent = "Failed to render graph. DOT:\n\n" + decoded; - }); - } - - function createButtonHandler(commandType) { - return async () => { - outputBlock.textContent = ""; - outputWrap.style.display = "block"; - inputDialog.style.display = "none"; - try { graphContainer.innerHTML = ""; graphContainer.style.display = "none"; } catch (e) { /* ignore */ } - - setRunning(); - - if (!pyodideReady) { - await initPyodideWorker(); - } - - const outputHandler = (event) => { - const { output } = event.detail; - if (output === ">>> Graph content saved to /home/pyodide/temp.dot") return; - outputBlock.textContent += output; - outputBlock.scrollTop = outputBlock.scrollHeight; - }; - - const dotHandler = (event) => { - graphContainer.innerHTML = ""; - renderDotToGraph(event.detail.dot); - }; - - document.addEventListener('jacOutputUpdate', outputHandler); - document.addEventListener('jacDotOutput', dotHandler); - - try { - const codeToRun = editor.getValue(); - const inputHandler = createInputHandler(); - await executeJacCodeInWorker(codeToRun, inputHandler, commandType); - } catch (error) { - outputBlock.textContent += `\nError: ${error}`; - } finally { - document.removeEventListener('jacDotOutput', dotHandler); - document.removeEventListener('jacOutputUpdate', outputHandler); - inputDialog.style.display = "none"; - setIdle(); - } - }; - } - - runButton.addEventListener("click", createButtonHandler("run")); - serveButton.addEventListener("click", createButtonHandler("serve")); - dotButton.addEventListener("click", createButtonHandler("dot")); -} - -// Lazy load code blocks using Intersection Observer -const lazyObserver = new IntersectionObserver((entries) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - const div = entry.target; - if (!initializedBlocks.has(div)) { - setupCodeBlock(div); - initializedBlocks.add(div); - lazyObserver.unobserve(div); - } - } - }); -}, { - root: null, - rootMargin: "0px", - threshold: 0.1 -}); - -function observeUninitializedCodeBlocks() { - document.querySelectorAll('.code-block').forEach((block) => { - if (!initializedBlocks.has(block)) { - lazyObserver.observe(block); - } - }); -} - -const domObserver = new MutationObserver(() => { - observeUninitializedCodeBlocks(); -}); - -domObserver.observe(document.body, { - childList: true, - subtree: true -}); - -document.addEventListener("DOMContentLoaded", async () => { - observeUninitializedCodeBlocks(); - initPyodideWorker(); -}); - -document.addEventListener("DOMContentLoaded", function () { - const observer = new MutationObserver(() => { - const links = document.querySelectorAll("nav a[href='/playground/']"); - links.forEach(link => { - link.setAttribute("target", "_blank"); - link.setAttribute("rel", "noopener"); - }); - }); - observer.observe(document.body, { childList: true, subtree: true }); -}); diff --git a/docs/playground/language-configuration.json b/docs/playground/language-configuration.json deleted file mode 100644 index 4effa91..0000000 --- a/docs/playground/language-configuration.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "comments": { - "lineComment": "#", - "blockComment": [ - "#*", - "*#" - ], - "inlineComment": "#" - }, - "brackets": [ - [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "(", - ")" - ] - ], - "autoClosingPairs": [ - { - "open": "{", - "close": "}" - }, - { - "open": "[", - "close": "]" - }, - { - "open": "(", - "close": ")" - }, - { - "open": "\"", - "close": "\"" - }, - { - "open": "'", - "close": "'" - }, - { - "open": "#*", - "close": "*#" - } - ], - "surroundingPairs": [ - [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "(", - ")" - ], - [ - "\"", - "\"" - ], - [ - "'", - "'" - ], - [ - "#*", - "*#" - ] - ] -} \ No newline at end of file diff --git a/jac_syntax_highlighter.py b/jac_syntax_highlighter.py deleted file mode 100644 index bc295ec..0000000 --- a/jac_syntax_highlighter.py +++ /dev/null @@ -1,901 +0,0 @@ -# flake8: noqa -"""Pygments Syntax highlighter for Jac code.""" -""" - pygments.lexers.python - ~~~~~~~~~~~~~~~~~~~~~~ - - Lexers for Python and related languages. - - :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" - -import keyword -from pygments.lexer import ( - RegexLexer, - include, - bygroups, - using, - default, - words, - combined, - this, -) -from pygments.util import shebang_matches -from pygments.token import ( - Text, - Comment, - Operator, - Keyword, - Name, - String, - Number, - Punctuation, - Whitespace, -) -from pygments import unistring as uni - - -class JacLexer(RegexLexer): - """ - For Jac source code, - Adapted from Python source code (version 3.x). - .. versionadded:: 0.10 - .. versionchanged:: 2.5 - This is now the default ``PythonLexer``. It is still available as the - alias ``Python3Lexer``. - """ - - name = "Jac" - url = "http://www.jac-lang.org" - aliases = ["jac"] - filenames = ["*.jac"] - mimetypes = [ - "text/x-jac", - "application/x-jac", - "text/x-jac3", - ] - - uni_name = "[%s][%s]*" % (uni.xid_start, uni.xid_continue) - - def innerstring_rules(ttype): - return [ - # the old style '%s' % (...) string formatting (still valid in Py3) - ( - r"%(\(\w+\))?[-#0 +]*([0-9]+|[*])?(\.([0-9]+|[*]))?" - "[hlL]?[E-GXc-giorsaux%]", - String.Interpol, - ), - # the new style '{}'.format(...) string formatting - ( - r"\{" - r"((\w+)((\.\w+)|(\[[^\]]+\]))*)?" # field name - r"(\![sra])?" # conversion - r"(\:(.?[<>=\^])?[-+ ]?#?0?(\d+)?,?(\.\d+)?[E-GXb-gnosx%]?)?" - r"\}", - String.Interpol, - ), - # backslashes, quotes and formatting signs must be parsed one at a time - (r'[^\\\'"%{\n]+', ttype), - (r'[\'"\\]', ttype), - # unhandled string formatting sign - (r"%|(\{{1,2})", ttype), - # newlines are an error (use "nl" state) - ] - - def fstring_rules(ttype): - return [ - # Assuming that a '}' is the closing brace after format specifier. - # Sadly, this means that we won't detect syntax error. But it's - # more important to parse correct syntax correctly, than to - # highlight invalid syntax. - (r"\}", String.Interpol), - (r"\{", String.Interpol, "expr-inside-fstring"), - # backslashes, quotes and formatting signs must be parsed one at a time - (r'[^\\\'"{}\n]+', ttype), - (r'[\'"\\]', ttype), - # newlines are an error (use "nl" state) - ] - - tokens = { - "root": [ - (r"\n", Whitespace), - ( - r'^(\s*)([rRuUbB]{,2})("""(?:.|\n)*?""")', - bygroups(Whitespace, String.Affix, String.Doc), - ), - ( - r"^(\s*)([rRuUbB]{,2})('''(?:.|\n)*?''')", - bygroups(Whitespace, String.Affix, String.Doc), - ), - (r"\A#!.+$", Comment.Hashbang), - (r"#\*(.|\n|\r)*?\*#", Comment.Multiline), - (r"#.*$", Comment.Single), - (r"\\\n", Text), - (r"\\", Text), - include("jsx"), - include("keywords"), - include("soft-keywords"), - (r"(static\s+can)((?:\s|\\\s)+)", bygroups(Keyword, Text), "funcname"), - (r"(static\s+def)((?:\s|\\\s)+)", bygroups(Keyword, Text), "funcname"), - (r"(can)((?:\s|\\\s)+)", bygroups(Keyword, Text), "funcname"), - (r"(def)((?:\s|\\\s)+)", bygroups(Keyword, Text), "funcname"), - (r"(enum)((?:\s|\\\s)+)", bygroups(Keyword, Text), "classname"), - (r"(class)((?:\s|\\\s)+)", bygroups(Keyword, Text), "classname"), - (r"(obj)((?:\s|\\\s)+)", bygroups(Keyword, Text), "classname"), - (r"(walker)((?:\s|\\\s)+)", bygroups(Keyword, Text), "classname"), - (r"(node)((?:\s|\\\s)+)", bygroups(Keyword, Text), "classname"), - (r"(edge)((?:\s|\\\s)+)", bygroups(Keyword, Text), "classname"), - (r"(test)((?:\s|\\\s)+)", bygroups(Keyword, Text), "classname"), - # Handle all import patterns directly in the root - # Import from with curly braces (no semicolon yet) - ( - r"(import)(\s+)(from)(\s+)([A-Za-z][A-Za-z0-9_\.]*)", - bygroups( - Keyword.Namespace, Text, Keyword.Namespace, Text, Name.Namespace - ), - "import_from_braces", - ), - # Standard import with semicolon - ( - r"(import)(\s+)([A-Za-z][A-Za-z0-9_\.]*)(\s*)(;)", - bygroups(Keyword.Namespace, Text, Name.Namespace, Text, Punctuation), - ), - # Import with alias and semicolon - ( - r"(import)(\s+)([A-Za-z][A-Za-z0-9_\.]*)(\s+)(as)(\s+)([A-Za-z][A-Za-z0-9_]*)(\s*)(;)", - bygroups(Keyword.Namespace, Text, Name.Namespace, Text, Punctuation), - ), - # Include with semicolon - ( - r"(include)(\s+)([A-Za-z][A-Za-z0-9_\.]*)(\s*)(;)", - bygroups(Keyword.Namespace, Text, Name.Namespace, Text, Punctuation), - ), - # Fallback for any other import forms - ( - r"(from)(\s+)([A-Za-z][A-Za-z0-9_\.]*)", - bygroups(Keyword.Namespace, Text, Name.Namespace), - "fromimport", - ), - include("expr"), - ], - "jsx": [ - # JSX Fragment: <>... - (r"(<>)", Punctuation, "jsx-fragment"), - # JSX opening tag: )", Punctuation, "#pop"), - include("jsx-children"), - ], - "jsx-tag": [ - # Self-closing tag end: /> - (r"(/>)", Punctuation, "#pop"), - # Opening tag end: > - (r"(>)", Punctuation, "jsx-children-tag"), - # Attributes - (r"\s+", Whitespace), - # Attribute name (including hyphenated names like data-id, aria-label) - ( - r"([A-Za-z_][-A-Za-z0-9_]*)(=)", - bygroups(Name.Attribute, Operator), - "jsx-attr-value", - ), - # Attribute without value - (r"[A-Za-z_][-A-Za-z0-9_]*", Name.Attribute), - # Spread attribute: {...} - (r"(\{)(\.\.\.)", bygroups(Punctuation, Operator), "jsx-spread"), - ], - "jsx-attr-value": [ - # String values - (r'"[^"]*"', String, "#pop"), - (r"'[^']*'", String, "#pop"), - # Expression values: {expr} - (r"\{", Punctuation, "jsx-expr-attr"), - ], - "jsx-spread": [ - (r"\}", Punctuation, "#pop"), - include("expr"), - ], - "jsx-expr-attr": [ - (r"\}", Punctuation, "#pop:2"), - include("expr"), - ], - "jsx-children-tag": [ - # Closing tag: - ( - r"()", - bygroups(Punctuation, Name.Class, Punctuation), - "#pop:2", - ), - ( - r"()", - bygroups(Punctuation, Name.Tag, Punctuation), - "#pop:2", - ), - include("jsx-children"), - ], - "jsx-children": [ - # Nested JSX elements - include("jsx"), - # JSX expressions: {expr} - (r"\{", Punctuation, "jsx-expr-child"), - # Text content (anything except < > { }) - (r"[^<>{}\n]+", String), - (r"\n", Whitespace), - ], - "jsx-expr-child": [ - (r"\}", Punctuation, "#pop"), - include("expr"), - ], - "expr": [ - # raw f-strings - ( - '(?i)(rf|fr)(""")', - bygroups(String.Affix, String.Double), - combined("rfstringescape", "tdqf"), - ), - ( - "(?i)(rf|fr)(''')", - bygroups(String.Affix, String.Single), - combined("rfstringescape", "tsqf"), - ), - ( - '(?i)(rf|fr)(")', - bygroups(String.Affix, String.Double), - combined("rfstringescape", "dqf"), - ), - ( - "(?i)(rf|fr)(')", - bygroups(String.Affix, String.Single), - combined("rfstringescape", "sqf"), - ), - # non-raw f-strings - ( - '([fF])(""")', - bygroups(String.Affix, String.Double), - combined("fstringescape", "tdqf"), - ), - ( - "([fF])(''')", - bygroups(String.Affix, String.Single), - combined("fstringescape", "tsqf"), - ), - ( - '([fF])(")', - bygroups(String.Affix, String.Double), - combined("fstringescape", "dqf"), - ), - ( - "([fF])(')", - bygroups(String.Affix, String.Single), - combined("fstringescape", "sqf"), - ), - # raw bytes and strings - ('(?i)(rb|br|r)(""")', bygroups(String.Affix, String.Double), "tdqs"), - ("(?i)(rb|br|r)(''')", bygroups(String.Affix, String.Single), "tsqs"), - ('(?i)(rb|br|r)(")', bygroups(String.Affix, String.Double), "dqs"), - ("(?i)(rb|br|r)(')", bygroups(String.Affix, String.Single), "sqs"), - # non-raw strings - ( - '([uU]?)(""")', - bygroups(String.Affix, String.Double), - combined("stringescape", "tdqs"), - ), - ( - "([uU]?)(''')", - bygroups(String.Affix, String.Single), - combined("stringescape", "tsqs"), - ), - ( - '([uU]?)(")', - bygroups(String.Affix, String.Double), - combined("stringescape", "dqs"), - ), - ( - "([uU]?)(')", - bygroups(String.Affix, String.Single), - combined("stringescape", "sqs"), - ), - # non-raw bytes - ( - '([bB])(""")', - bygroups(String.Affix, String.Double), - combined("bytesescape", "tdqs"), - ), - ( - "([bB])(''')", - bygroups(String.Affix, String.Single), - combined("bytesescape", "tsqs"), - ), - ( - '([bB])(")', - bygroups(String.Affix, String.Double), - combined("bytesescape", "dqs"), - ), - ( - "([bB])(')", - bygroups(String.Affix, String.Single), - combined("bytesescape", "sqs"), - ), - (r"[^\S\n]+", Text), - include("numbers"), - ( - r"(in|is|and|or|not|to|by)\b", - Operator.Word, - ), - ( - r"(global|nonlocal|visitor|here|self|def|self||init||super|root|impl|sem)", - Operator.Word, - ), - (r"\?:|\?|:\+:|!=|==|<<|>>|:=|[-~+/*%=<>&^|.]", Operator), - (r"[]{}:(),;[]", Punctuation), - include("expr-keywords"), - include("builtins"), - include("magicfuncs"), - include("magicvars"), - include("name"), - ], - "expr-inside-fstring": [ - (r"[{([]", Punctuation, "expr-inside-fstring-inner"), - # without format specifier - ( - r"(=\s*)?" # debug (https://bugs.python.org/issue36817) - r"(\![sraf])?" # conversion - r"\}", - String.Interpol, - "#pop", - ), - # with format specifier - # we'll catch the remaining '}' in the outer scope - ( - r"(=\s*)?" # debug (https://bugs.python.org/issue36817) - r"(\![sraf])?" # conversion - r":", - String.Interpol, - "#pop", - ), - (r"\s+", Whitespace), # allow new lines - include("expr"), - ], - "expr-inside-fstring-inner": [ - (r"[{([]", Punctuation, "expr-inside-fstring-inner"), - (r"[])}]", Punctuation, "#pop"), - (r"\s+", Whitespace), # allow new lines - include("expr"), - ], - "expr-keywords": [ - # Based on https://docs.python.org/3/reference/expressions.html - ( - words( - ( - # "async for", - # "await", - "else", - "for", - "if", - "lambda", - "yield", - # "yield from", - # ----- - ), - suffix=r"\b", - ), - Keyword, - ), - (words(("True", "False", "None"), suffix=r"\b"), Keyword.Constant), - ], - "keywords": [ - ( - words( - ( - "assert", - "async", - "await", - "break", - "continue", - "del", - "elif", - "else", - "except", - "finally", - "for", - "import", - "include", - "def", - "can", - "glob", - "if", - "lambda", - "pass", - "raise", - "nonlocal", - "return", - "report", - "try", - "while", - "yield", - "yield from", - "as", - "with", - "to", - "by", - # ----- - "let", - "abs", - "ignore", - "visit", - "revisit", - "spawn", - "entry", - "exit", - "disengage", - "skip", - "await", - "priv", - "pub", - "protect", - "has", - "check", - "cl", - ), - suffix=r"\b", - ), - Keyword, - ), - (words(("True", "False", "None"), suffix=r"\b"), Keyword.Constant), - ], - "soft-keywords": [ - # `match`, `case` and `_` soft keywords - ( - r"(^[ \t]*)" # at beginning of line + possible indentation - r"(match|case|switch|default)\b" # a possible keyword - r"(?![ \t]*(?:" # not followed by... - r"[:,;=^&|@~)\]}]|(?:" - + r"|".join( # characters and keywords that mean this isn't - keyword.kwlist - ) - + r")\b))", # pattern matching - bygroups(Text, Keyword), - "soft-keywords-inner", - ), - ], - "soft-keywords-inner": [ - # optional `_` keyword - (r"(\s+)([^\n_]*)(_\b)", bygroups(Whitespace, using(this), Keyword)), - default("#pop"), - ], - "builtins": [ - ( - words( - ( - "__import__", - "abs", - "aiter", - "all", - "any", - "bin", - "bool", - "bytearray", - "breakpoint", - "bytes", - "callable", - "chr", - "classmethod", - "compile", - "complex", - "delattr", - "dict", - "dir", - "divmod", - "enumerate", - "eval", - "filter", - "float", - "format", - "frozenset", - "getattr", - "globals", - "hasattr", - "hash", - "hex", - "id", - "input", - "int", - "isinstance", - "issubclass", - "iter", - "len", - "list", - "locals", - "map", - "max", - "memoryview", - "min", - "next", - "object", - "oct", - "open", - "ord", - "pow", - "print", - "property", - "range", - "repr", - "reversed", - "round", - "set", - "setattr", - "slice", - "sorted", - "static", - "override", - "str", - "sum", - "super", - "tuple", - "type", - "vars", - "zip", - ), - prefix=r"(?"; @@ -163,8 +163,7 @@ def:priv split_excerpt(body: str) -> tuple[str, str] { def:priv extract_peek_markdown(body: str) -> str { - # Mirror of scripts/inject_excerpt_peek.py:_extract_peek_markdown — the - # next PEEK_LINES non-blank, non-heading lines after the + # The next PEEK_LINES non-blank, non-heading lines after the # marker, used as a faded teaser on the index card. if EXCERPT_MARKER not in body { return ""; diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index b82916e..0000000 --- a/mkdocs.yml +++ /dev/null @@ -1,117 +0,0 @@ -site_name: Blogs from Jaseci Labs -site_url: https://blogs.jaseci.org/ -use_directory_urls: true - -nav: - - Jaseci Labs: index.md - - Blog: - - blog/index.md - - Newsletter: https://newsletter.jaseci.org/ - -theme: - name: material - logo: assets/jaseci_labs_logo.PNG - favicon: assets/jaseci_labs_logo.PNG - font: - text: Roboto - code: Roboto Mono - palette: - # Palette toggle for light mode - - scheme: default - primary: black - accent: custom orange - # Palette toggle for dark mode - - scheme: slate - primary: black - accent: custom orange - features: - - content.code.copy - -markdown_extensions: - - pymdownx.tabbed: - alternate_style: true - - pymdownx.highlight: - anchor_linenums: true - line_spans: __span - pygments_lang_class: true - - pymdownx.inlinehilite - - pymdownx.snippets: - base_path: ["."] - - pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - - pymdownx.tasklist: - custom_checkbox: true - - pymdownx.arithmatex: - generic: true - - attr_list - - md_in_html - - admonition - - pymdownx.details - - tables - - toc: - permalink: "#" - -extra_css: - - extra.css - -extra: - generator: false - homepage: https://www.jaseci.org/ - social: - - icon: fontawesome/brands/github - link: https://github.com/yourusername - - icon: fontawesome/brands/twitter - link: https://twitter.com/yourusername - -copyright: Copyright © 2025 Jaseci Labs. All rights reserved. - -extra_javascript: - - https://cdn.jsdelivr.net/pyodide/v0.27.0/full/pyodide.js - - https://unpkg.com/viz.js@2.1.2/viz.js - - https://unpkg.com/viz.js@2.1.2/full.render.js - - https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.52.2/min/vs/loader.min.js - - js/jac.monarch.js - - js/run-code.js - - js/author-tooltips.js - - js/collapsible-code.js - -plugins: - - search - - blog: - blog_dir: blog - post_dir: "{blog}/posts" - post_date_format: medium - post_url_date_format: yyyy/MM/dd - post_url_format: "{date}/{slug}" - post_readtime: true - post_readtime_words_per_minute: 200 - archive: true - archive_name: Archive - archive_date_format: MMMM yyyy - archive_url_date_format: yyyy/MM - archive_url_format: "archive/{date}" - categories: true - categories_name: Categories - categories_url_format: "category/{slug}" - categories_allowed: - - Jac Programming - - Tutorials - - Fixing the Broken - - AI - - Web Development - - Developers - - Community - authors: true - authors_file: "{blog}/.authors.yml" - pagination: true - pagination_per_page: 10 - pagination_format: "$link_first $link_previous ~2~ $link_next $link_last" - draft: false # drafts excluded from production builds (required for the editorial scheduler) - draft_on_serve: true # drafts visible during `mkdocs serve` so authors can preview locally - draft_if_future_date: false - -hooks: - - scripts/handle_jac_compile_data.py - - scripts/inject_excerpt_peek.py diff --git a/scripts/inject_excerpt_peek.py b/scripts/inject_excerpt_peek.py deleted file mode 100644 index 9c8add2..0000000 --- a/scripts/inject_excerpt_peek.py +++ /dev/null @@ -1,121 +0,0 @@ -"""Inject a faded "peek" of the next lines after into blog index cards. - -The mkdocs-material blog plugin truncates each post's excerpt at , -so the index card's HTML only contains pre-more content. This hook reads the -source post, captures a few lines after the marker, renders them -to HTML, and injects them into the card on the blog index (and paginated index) -pages — wrapped in a `.md-post__peek` div so CSS can fade them out. - -Authors do not need to change placement; the peek is purely a -build-time enhancement of the index page. -""" - -from __future__ import annotations - -import re -from pathlib import Path - -import markdown as _markdown - -# Number of non-blank source lines to capture after . -PEEK_LINES = 3 - -_FRONTMATTER_RE = re.compile(r"^---\n(.*?)\n---\n(.*)$", re.DOTALL) -_SLUG_RE = re.compile(r"^slug:\s*(.+)$", re.MULTILINE) -_MORE_RE = re.compile(r"") -_ARTICLE_RE = re.compile( - r'
.*?
', - re.DOTALL, -) -_HREF_RE = re.compile(r'