diff --git a/.firebaserc b/.firebaserc new file mode 100644 index 0000000..7d81607 --- /dev/null +++ b/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "earthbuild-cb08c" + } +} diff --git a/.github/renovate.json5 b/.github/renovate.json5 new file mode 100644 index 0000000..0c22fc7 --- /dev/null +++ b/.github/renovate.json5 @@ -0,0 +1,76 @@ +{ + $schema: 'https://docs.renovatebot.com/renovate-schema.json', + extends: [ + 'config:recommended', + 'helpers:pinGitHubActionDigests', + 'security:openssf-scorecard', + ':semanticCommits', + ], + configMigration: true, + prHourlyLimit: 0, + prConcurrentLimit: 2, + labels: [ + 'renovate', + ], + 'github-actions': { + enabled: true, + }, + dockerfile: { + enabled: true, + fileMatch: [ + '/(^|/)Earthfile$/', + '/.*\\.earth$/', + ], + }, + packageRules: [ + { + description: 'Group GitHub Actions updates monthly', + matchManagers: ['github-actions'], + groupName: 'github-actions', + schedule: ['every month'], + }, + { + description: 'Update EarthBuild release immediately', + matchPackageNames: ['EarthBuild/earthbuild'], + matchDatasources: ['github-releases'], + schedule: ['at any time'], + }, + { + description: 'Security vulnerability updates bypass scheduling', + matchCategories: ['security'], + schedule: ['at any time'], + }, + ], + customManagers: [ + { + customType: 'regex', + description: 'Update GitHub Actions runner versions', + fileMatch: ['/(^|/)\\.github/workflows/.+\\.ya?ml$/'], + matchStrings: [ + '(?:runs-on|RUNS_ON):\\s*[\'"]?(?ubuntu|macos|windows)-(?[^\'"\\s#]+)[\'"]?', + ], + datasourceTemplate: 'github-runners', + }, + { + customType: 'regex', + description: 'Update EarthBuild latest version in hugo.toml', + fileMatch: ['/^hugo\\.toml$/'], + matchStrings: [ + 'latestVersion\\s*=\\s*["\'](?v[0-9]+\\.[0-9]+\\.[0-9]+)["\']', + ], + depNameTemplate: 'EarthBuild/earthbuild', + datasourceTemplate: 'github-releases', + }, + { + customType: 'regex', + description: 'Update Three.js CDN version in digital-earth.ts', + fileMatch: ['/^assets/ts/digital-earth\\.ts$/'], + matchStrings: [ + 'cdnjs\\.cloudflare\\.com/ajax/libs/three\\.js/r(?\\d+)/three\\.min\\.js', + ], + depNameTemplate: 'three', + datasourceTemplate: 'npm', + extractVersionTemplate: '^0\\.(?\\d+)\\.', + }, + ], +} diff --git a/.github/scripts/lighthouse-summary.py b/.github/scripts/lighthouse-summary.py new file mode 100755 index 0000000..1ac23d5 --- /dev/null +++ b/.github/scripts/lighthouse-summary.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python3 +import glob +import json +import os +import subprocess +import sys +import urllib.parse + + +def format_score(score): + if score is None: + return "N/A", False + pct = int(round(score * 100)) + if pct >= 90: + return f"{pct}", True + elif pct >= 50: + return f"{pct} ⚠️", False + else: + return f"{pct} ❌", False + + +def combine_cell(m_tuple, d_tuple, both_devices): + if both_devices and m_tuple and d_tuple: + m_txt, m_green = m_tuple + d_txt, d_green = d_tuple + if m_green and d_green: + return f"{m_txt} / {d_txt} ✅" + else: + m_disp = m_txt if not m_green else f"{m_txt} ✅" + d_disp = d_txt if not d_green else f"{d_txt} ✅" + return f"{m_disp} / {d_disp}" + elif m_tuple: + return f"{m_tuple[0]} ✅" if m_tuple[1] else m_tuple[0] + elif d_tuple: + return f"{d_tuple[0]} ✅" if d_tuple[1] else d_tuple[0] + return "N/A" + + +LIGHTHOUSE_LOGO_URL = "https://raw.githubusercontent.com/GoogleChrome/lighthouse/main/assets/lighthouse-logo.svg" + + +def generate_summary(): + mobile_dir = ".lighthouseci/mobile" + desktop_dir = ".lighthouseci/desktop" + + has_mobile = os.path.exists(os.path.join(mobile_dir, "links.json")) + has_desktop = os.path.exists(os.path.join(desktop_dir, "links.json")) + + if not has_mobile and not has_desktop: + if os.path.exists(".lighthouseci/links.json"): + has_mobile = True + mobile_dir = ".lighthouseci" + else: + print("Warning: no Lighthouse links.json found. Skipping summary generation.", file=sys.stderr) + return None + + def load_device_data(target_dir): + links_file = os.path.join(target_dir, "links.json") + if not os.path.exists(links_file): + return {} + + with open(links_file, "r", encoding="utf-8") as f: + links = json.load(f) + + report_files = sorted(glob.glob(os.path.join(target_dir, "lhr-*.json"))) + reports = [] + for rf in report_files: + try: + with open(rf, "r", encoding="utf-8") as f: + reports.append(json.load(f)) + except Exception as e: + print(f"Warning: could not read {rf}: {e}", file=sys.stderr) + + results = {} + for link_url, report_url in links.items(): + parsed = urllib.parse.urlparse(link_url) + route_path = parsed.path or "/" + + matched_data = None + for data in reversed(reports): + data_req_path = urllib.parse.urlparse(data.get("requestedUrl", "")).path or "/" + data_fin_path = urllib.parse.urlparse(data.get("finalUrl", "")).path or "/" + if route_path in (data_req_path, data_fin_path): + matched_data = data + break + + if matched_data: + cats = matched_data.get("categories", {}) + perf = format_score(cats.get("performance", {}).get("score")) + a11y = format_score(cats.get("accessibility", {}).get("score")) + bp = format_score(cats.get("best-practices", {}).get("score")) + seo = format_score(cats.get("seo", {}).get("score")) + results[route_path] = { + "perf": perf, + "a11y": a11y, + "bp": bp, + "seo": seo, + "all_green": perf[1] and a11y[1] and bp[1] and seo[1], + "report_url": report_url, + } + else: + na = ("N/A", False) + results[route_path] = { + "perf": na, + "a11y": na, + "bp": na, + "seo": na, + "all_green": False, + "report_url": report_url, + } + return results + + mobile_data = load_device_data(mobile_dir) if has_mobile else {} + desktop_data = load_device_data(desktop_dir) if has_desktop else {} + + all_routes = sorted(set(list(mobile_data.keys()) + list(desktop_data.keys()))) + if "/" in all_routes: + all_routes.remove("/") + all_routes.insert(0, "/") + + rows = [] + both_devices = bool(mobile_data and desktop_data) + all_green = True + + for route in all_routes: + m = mobile_data.get(route) + d = desktop_data.get(route) + + if both_devices: + if not m or not m.get("all_green", False): + all_green = False + if not d or not d.get("all_green", False): + all_green = False + else: + if m and not m.get("all_green", False): + all_green = False + if d and not d.get("all_green", False): + all_green = False + + route_cell = f"`{route}`" + if both_devices and m and d: + perf_cell = combine_cell(m["perf"], d["perf"], True) + a11y_cell = combine_cell(m["a11y"], d["a11y"], True) + bp_cell = combine_cell(m["bp"], d["bp"], True) + seo_cell = combine_cell(m["seo"], d["seo"], True) + reports_cell = f'[📱]({m["report_url"]} "Mobile Report") [💻]({d["report_url"]} "Desktop Report")' + elif m: + perf_cell = combine_cell(m["perf"], None, False) + a11y_cell = combine_cell(m["a11y"], None, False) + bp_cell = combine_cell(m["bp"], None, False) + seo_cell = combine_cell(m["seo"], None, False) + reports_cell = f'[📱]({m["report_url"]} "Mobile Report")' + elif d: + perf_cell = combine_cell(None, d["perf"], False) + a11y_cell = combine_cell(None, d["a11y"], False) + bp_cell = combine_cell(None, d["bp"], False) + seo_cell = combine_cell(None, d["seo"], False) + reports_cell = f'[💻]({d["report_url"]} "Desktop Report")' + else: + continue + + rows.append(f"| {route_cell} | {perf_cell} | {a11y_cell} | {bp_cell} | {seo_cell} | {reports_cell} |") + + table_rows = "\n".join(rows) + footnote = "*Scores: 📱 Mobile / 💻 Desktop. Threshold: ≥ 90 ✅.*" if both_devices else "*Threshold: ≥ 90 ✅.*" + comment = f""" +### Lighthouse Lighthouse + +| Route | Perf | A11y | Best | SEO | Reports | +|:---|:---:|:---:|:---:|:---:|:---:| +{table_rows} + +{footnote} +""" + return comment, all_green + + +def main(): + res = generate_summary() + if not res: + print("::error::No Lighthouse CI results found to evaluate.", file=sys.stderr) + sys.exit(1) + comment, all_green = res + + # 1. Output to stdout + print(comment) + + # 2. Append to GitHub Actions Job Summary if running in Actions + summary_path = os.environ.get("GITHUB_STEP_SUMMARY") + if summary_path and os.path.exists(os.path.dirname(summary_path)): + try: + with open(summary_path, "a", encoding="utf-8") as f: + f.write(comment + "\n") + print("Successfully written to $GITHUB_STEP_SUMMARY") + except Exception as e: + print(f"Warning: failed to write to GITHUB_STEP_SUMMARY: {e}", file=sys.stderr) + + # 3. Post or update sticky comment on PR if PR_NUMBER is set + pr_number = os.environ.get("PR_NUMBER") + if pr_number: + try: + cmd = ["gh", "api", f"repos/:owner/:repo/issues/{pr_number}/comments"] + res_str = subprocess.check_output(cmd, text=True) + existing_comments = json.loads(res_str) + comment_id = None + for c in existing_comments: + if "" in c.get("body", ""): + comment_id = c.get("id") + break + + if comment_id: + print(f"Updating existing PR comment #{comment_id} on PR #{pr_number}...") + subprocess.run( + ["gh", "api", "-X", "PATCH", f"repos/:owner/:repo/issues/comments/{comment_id}", "-f", f"body={comment}"], + check=True, + ) + else: + print(f"Creating new PR comment on PR #{pr_number}...") + subprocess.run(["gh", "pr", "comment", pr_number, "--body", comment], check=True) + except subprocess.CalledProcessError as e: + print(f"Warning: gh command failed: {e}", file=sys.stderr) + except Exception as e: + print(f"Warning: unexpected error posting PR comment: {e}", file=sys.stderr) + + # 4. Fail the build if any score is not green (score < 90%) + if not all_green: + print("\n::error::Lighthouse CI audit failed: one or more categories scored below 90% (did not pass ✅).", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..6e6aa9e --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,90 @@ +name: Deploy to GitHub Pages + +on: + push: + branches: + - main + pull_request: + branches: + - main + workflow_dispatch: + +permissions: {} + +concurrency: + group: 'pages' + cancel-in-progress: false + +# Default shell +defaults: + run: + shell: bash + +jobs: + # Build job: Checks out code, configures Hugo extended, and compiles the static site + build: + name: Build Website + runs-on: ubuntu-26.04 + permissions: + contents: read + pull-requests: write + checks: write + steps: + - name: Checkout repository + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + with: + submodules: recursive + fetch-depth: 0 + persist-credentials: false + + - name: Setup Pages + id: pages + uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0 + + - name: Setup EarthBuild + uses: earthbuild/actions-setup@f4d20223e70dbb43b5fc08c4d857ab9cf0dbf3ae # v2.2.0 + with: + github-token: ${{ github.token }} + + - name: Run Tests + run: | + earth --ci +test + + - name: Build + env: + BASE_URL: ${{ steps.pages.outputs.base_url }}/ + run: | + earth --ci --output +build --BASE_URL="$BASE_URL" + + - name: Upload Pages artifact + if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 + with: + path: ./public + + - name: Deploy PR Preview to Firebase + if: | + github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name == github.repository + uses: FirebaseExtended/action-hosting-deploy@7c850a480ce753f4f06f010801fc5a43787740bb # v0.3.0 + with: + repoToken: ${{ secrets.GITHUB_TOKEN }} + firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_EARTHBUILD_CB08C }} + projectId: earthbuild-cb08c + + # Deployment job: Deploys the built artifact to the GitHub Pages environment + deploy: + name: Deploy to GitHub Pages + if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' + needs: build + runs-on: ubuntu-26.04 + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.0 diff --git a/.github/workflows/lighthouse.yml b/.github/workflows/lighthouse.yml new file mode 100644 index 0000000..20c1979 --- /dev/null +++ b/.github/workflows/lighthouse.yml @@ -0,0 +1,47 @@ +name: Lighthouse + +on: + push: + branches: + - main + pull_request: + branches: + - main + workflow_dispatch: + +# Disable permissions at root level; permissions are strictly scoped per job +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + lighthouse: + name: Lighthouse Audit + runs-on: ubuntu-26.04 + permissions: + contents: read + pull-requests: write + steps: + - name: Checkout repository + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + with: + submodules: recursive + fetch-depth: 0 + persist-credentials: false + + - name: Setup EarthBuild + uses: earthbuild/actions-setup@f4d20223e70dbb43b5fc08c4d857ab9cf0dbf3ae # v2.2.0 + with: + github-token: ${{ github.token }} + + - name: Run Lighthouse + run: earth --ci --output +lighthouse + + - name: Publish Results + if: always() + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: python3 .github/scripts/lighthouse-summary.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..12558d2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# Hugo build output and cache +public/ +resources/_gen/ +.hugo_build.lock +.lighthouseci/ +.DS_Store +node_modules/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..9af3519 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": true, + "singleQuote": true, + "trailingComma": "all", + "tabWidth": 2, + "printWidth": 100 +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..0103adc --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,231 @@ +# AGENTS.md — Agent Guidelines & Context for EarthBuild + +Welcome to the **EarthBuild** codebase. This document outlines the project mission, target audience, communication style, technical architecture, and strict operational constraints for AI agents working in this repository. + +--- + +## 1. Project Overview + +### What is EarthBuild? +**EarthBuild** is an open-source, container-powered build automation engine. It combines the approachable syntax of Makefiles with the hermetic isolation and layer caching of Docker. + +> [!IMPORTANT] +> ### Crucial Project Identity & Environment Variables +> - **This is EarthBuild, NOT Earthly**: This project is **EarthBuild**. While it originated as the community-supported, open-source fork and successor to Earthly (MPL-2.0), agents must **never** refer to the active project or current software as "Earthly". Always use **EarthBuild**. +> - **CLI Command is `earth`**: The tool package is `earthbuild`, but the command-line executable invoked by developers and CI workflows is strictly **`earth`** (e.g., `earth +build`, `earth +test`, `earth --ci +all`, `earth -i +target`). +> - **Strict `EARTH_` Environment Variable Prefix**: EarthBuild uses **`EARTH_`** prefixed environment variables (e.g., `EARTH_DOCKER_CONFIG`, `EARTH_CONFIG`, `EARTH_CI`, `EARTH_BUILDKIT_IMAGE`, `EARTH_DEBUG`, `EARTH_AUTO_SKIP`). **Never use legacy `EARTHLY_` environment variables.** + +- **Tagline**: *"It's like Docker for builds"* +- **Core Value Proposition**: *"Build Once. Run Anywhere. Seriously."* — Guaranteed local-to-CI parity. If it passes on your laptop with `earth +test`, it is guaranteed to pass in any CI runner. +- **Lineage & Heritage**: EarthBuild is the community-supported, fully open-source fork and successor to Earthly, maintained under the **Mozilla Public License Version 2.0 (MPL-2.0)**. +- **Compatibility**: 100% drop-in replacement for existing `Earthfile` configurations and workflows. +- **CLI**: Provided by the `earthbuild` package, invoked via the **`earth`** command (e.g., `earth +build`, `earth +test`, `earth --ci +all`). +- **Environment Variables**: Pervasively use the **`EARTH_`** prefix (e.g., `EARTH_DOCKER_CONFIG`, `EARTH_CONFIG`, `EARTH_CI`, `EARTH_BUILDKIT_IMAGE`). Legacy `EARTHLY_*` prefixes are obsolete. +- **Engine**: Powered by a **heavily customized, forked version of BuildKit** under the hood. **EarthBuild does NOT use standard upstream BuildKit.** Its engine is a deeply modified BuildKit fork engineered specifically for `Earthfile` semantics, providing non-linear target-level DAG resolution, custom Low-Level Builder (LLB) opcodes, direct host artifact streaming (`SAVE ARTIFACT ... AS LOCAL`), and interactive container debugging (`earth -i`). + +### Key Capabilities (Universal Engine Truths) +- **Heavily Customized Forked BuildKit Engine**: EarthBuild embeds and orchestrates a dedicated, customized fork of BuildKit rather than standard upstream BuildKit or standard Docker daemons. This custom engine introduces specialized LLB operations, multi-target graph execution, and container isolation features that standard BuildKit cannot execute. +- **Directed Acyclic Graph (DAG) with Multi-Core Parallelism**: Targets declare explicit dependencies. Independent pipeline branches (e.g., `+lint`, `+test-unit`, `+test-integration`, `+build`) execute concurrently by default across all available CPU cores without manual orchestration. +- **Sub-DAG Pruning & Incremental Builds**: Editing a single file only invalidates the specific target's leaf; upstream and sibling targets are instantly reused from cache. +- **Hermetic Container Sandboxing**: Every target executes inside an isolated OCI container namespace with deterministic inputs—immune to untracked host OS packages, glibc differences, or local environment variables. +- **Artifact Extraction**: Standalone host binaries, test coverage reports, or distribution bundles can be written directly to host disk using `SAVE ARTIFACT ... AS LOCAL`. +- **Zero Lock-In Remote OCI Caching**: Seamless cache export and pull via standard OCI registries (Docker Hub, GitHub Packages, AWS ECR, GCP Artifact Registry). +- **Secure Secret Isolation**: In-memory secret mounting (`RUN --secret`) that never leaks into image layers, build cache metadata, or remote registries. +- **Default Support for Podman, Docker & Apple Container Engines**: EarthBuild supports **Podman**, **Docker**, and **Apple container engines** out-of-the-box by default. It auto-detects the active local container daemon or remote BuildKit instance with zero manual setup. + +--- + +## 2. Target Audience + +EarthBuild is built for technical practitioners who demand high build velocity, reproducibility, reliability, and security: + +1. **Software Engineers & Monorepo Developers** (Completely language-agnostic — works with any language, runtime, or toolchain): + - Universally supports any tech stack (Go, Rust, C/C++, Python, TypeScript, Java, Zig, Ruby, C#, Elixir, or any tool that runs in a container) with zero proprietary plugins required. + - Developers tired of broken inner-loop iterations, lengthy CI feedback delays, and context switching between local scripts and CI YAML. + - Engineers who want fast local rebuilds that behave identically to production CI. + +2. **DevOps & Platform Engineers**: + - Engineers responsible for CI/CD infrastructure, cloud compute bills, and developer experience (DX). + - Teams seeking to slash GitHub Actions / GitLab CI runner minutes and cloud compute costs via shared remote caching. + +3. **Security Engineers & DevSecOps Practitioners**: + - Teams focused on software supply chain security, hermetic container sandboxing, and verifiable build reproducibility. + - Practitioners requiring secure in-memory secret handling (`RUN --secret`) that guarantees zero credential leakage into container layers, cache metadata, or remote registries. + +4. **Open-Source Maintainers & Enterprises**: + - Organizations looking for a truly open-source build tool (MPL-2.0) with zero closed-source tiers, zero cloud telemetry tracking, and zero surprise paywalls. + +--- + +## 3. Practical Usage & Execution Architecture (Dev Workstations vs. CI/CD) + +While the customized BuildKit engine behaves identically everywhere, EarthBuild solves fundamentally different operational problems on developer workstations versus automated CI/CD pipelines. + +### A. Developer Workstation Workflow (The Inner Loop & Interactive DX) +On local workstations, EarthBuild optimizes for developer velocity, host environment independence, and immediate feedback: + +1. **Host Toolchain Independence & Version Drift Immunity**: + - Developers typically have language runtimes (Go, Node, Rust, Python) installed on their host machine for editor tooling and LSPs (`gopls`, `rust-analyzer`). However, host toolchain versions often diverge from repository and CI pins (e.g., host has Go 1.28 while the project and CI are pinned to Go 1.27; or host macOS lacks Linux glibc/Cgo dependencies). + - EarthBuild completely decouples build and test execution from the host OS and local toolchain state. Every target executes inside the exact containerized toolchain pinned in the `Earthfile`. + - **Guaranteed Local-to-CI Parity**: If `earth +test` passes on a developer's workstation, it is guaranteed to pass in CI because both execute the identical pinned container environment. This eliminates "works on my machine" version mismatches and frees developers from juggling complex host version managers (`gvm`, `nvm`, `asdf`, `pyenv`). +2. **Sub-Second Local Inner Loop**: + - When editing source code, developers run `earth +test`. + - Local customized BuildKit cache reuses base images, compiler environments, and dependency downloads (`go mod download`, `npm ci`, `cargo fetch`). Only the invalidated target leaf recompiles. +3. **Direct Host Artifact Extraction for IDEs**: + - Developers use `SAVE ARTIFACT ... AS LOCAL bin/app` to export compiled binaries, generated protobuf stubs, or test coverage reports directly to their host filesystem for editor autocompletion and local testing. +4. **Interactive Failure Debugging (`earth -i`)**: + - When a step fails locally, running `earth -i +target` drops the developer into an interactive root shell inside the exact container environment and layer where the command failed. +5. **Private Local Workstation Cache**: + - All workstation builds read and write strictly to local disk storage with zero network latency. + +--- + +### B. CI/CD Runner Workflow (Pipeline Simplification & Runner Topologies) +In continuous integration environments (GitHub Actions, GitLab CI, Buildkite, CircleCI, Jenkins), EarthBuild execution generally falls into two distinct runner topologies: + +1. **Self-Hosted Persistent Runners (The Ideal Environment)**: + - **Persistent Local Engine Cache**: Runners retain their disk storage and customized BuildKit cache across pipeline runs. + - **Maximum Build Velocity**: Subsequent pipeline executions hit local disk cache instantly with zero network download latency, delivering sub-second incremental builds identical to the local workstation experience. + - This is the recommended, gold-standard operational setup for EarthBuild in production CI. +2. **Hosted / Ephemeral Git Platform Runners (GitHub-Hosted, GitLab SaaS, etc.)**: + - **Ephemeral Lifecycles**: Every job spins up inside a fresh, clean VM and discards its state on completion; local disk cache is destroyed when the runner terminates. + - **Remote Caching Mechanics**: Can import and export cache layers over the network via standard OCI registries (`--remote-cache=...`). However, network I/O transfer overhead for pulling and pushing large layers on short-lived VMs can offset build gains. + - **Active Area of Improvement**: Improving caching mechanics, transfer payloads, and ergonomics on ephemeral platform runners is an active, ongoing development focus for EarthBuild, though persistent runners currently provide the optimal experience. +3. **Slashing CI Configuration Complexity (The YAML Killer)**: + - Replaces hundreds of lines of fragile, vendor-specific CI YAML (`setup-node`, `setup-go`, caching actions, and custom bash scripts) with a single command: + ```bash + earth --ci +test + ``` + *(Or with remote OCI caching: `earth --ci --remote-cache=registry.hub.docker.com/myorg/cache:main +test`)* + - Eliminates CI vendor lock-in. Migrating between GitHub Actions, GitLab CI, or Jenkins requires zero pipeline rewrites. +4. **Eliminating Complex CI Matrix Setups**: + - Because EarthBuild parallelizes independent targets automatically on multi-core runners, teams do not need to configure, debug, and maintain complex multi-job CI matrices to run tests and linters concurrently. + +--- + +### C. Cache Security, Isolation & Secure Layer Sharing (Workstation vs. CI) +EarthBuild combines strict **execution hermeticity** with flexible cache security policies: + +1. **Inherently Secure Layers (No Credential Leakage)**: + - EarthBuild cache layers are cryptographically content-addressed and hermetically isolated. + - Secrets mounted via `RUN --secret` exist strictly as temporary in-memory files (`tmpfs`) during step execution; they are never written to disk, never baked into image layers, and never serialized into cache metadata. + - When properly configured, cache layers are completely clean and secure to store and distribute. +2. **Current Baseline Isolation (Poisoning Prevention)**: + - In standard production setups today, developer workstations and CI pipelines often keep write permissions separated. + - In CI, PR branches operate under a **Read-Only cache policy** (pulling base layers from `main` but never pushing), preventing malicious cache poisoning. Only authenticated builds on protected branches (`main`) write back to remote registries. +3. **Future Evolution (Secure Cross-Environment Layer Sharing)**: + - Sharing pre-warmed cache layers directly between CI/CD and developer workstations represents a major build acceleration opportunity. + - Because layers are hermetic and secure, organizations can set up authenticated, role-based OCI caching to share compiled layers across the entire engineering fleet, turning initial local builds into instant cache hits. +4. **Deterministic & Verifiable Parity**: + - Because targets execute in hermetic container namespaces without host leakage, deterministic inputs guarantee that if a target passes locally, it passes identically in CI. + +--- + +## 4. Language, Terminology & Tone + +### Communication Tone +- **Developer-Centric, Pragmatic & Direct**: Channel the craftsmanship and clarity of modern developer tools (Vite, Rust/Cargo, Go, Docker, Bazel). +- **No Marketing Fluff**: Avoid generic buzzwords ("revolutionary", "game-changing", "next-gen"). Focus on concrete mechanics: cache hit rates, build times, CPU utilization, local-to-CI parity. +- **Respectful & Transparent**: Acknowledge Earthly roots with pride while presenting EarthBuild as the future-proof, community-governed path forward. + +### Preferred Technical Terminology + +| Concept | Preferred Term | Avoid / Deprecated | Rationale | +| :--- | :--- | :--- | :--- | +| **Project Identity** | **EarthBuild** | *Earthly* (when referring to active project/software) | EarthBuild is the active project and community-supported successor. Never refer to the project as Earthly. | +| **CLI Command** | **`earth`** (`earth +target`, `earth --ci`) | *earthbuild* (as command), *earthly* | The binary/command is `earth`, distributed via the `earthbuild` package. | +| **Environment Variables** | **`EARTH_*`** (`EARTH_DOCKER_CONFIG`, `EARTH_CONFIG`, `EARTH_CI`) | *`EARTHLY_*`* (`EARTHLY_DOCKER_CONFIG`, `EARTHLY_CONFIG`, etc.) | **Strict invariant**: EarthBuild uses `EARTH_` prefixed environment variables. Legacy `EARTHLY_` variables are deprecated. | +| **01 (Zero Cache)** | **Clean Build** (or *Cold Run*) | *Cold Build* | Standard developer vocabulary (`make clean`, `cargo clean`, fresh CI runner). | +| **02 (1 File Changed)** | **Incremental Build** (or *Sub-DAG Rebuild*) | *Cache Miss* | **Crucial:** Calling 1 file changed a "Cache Miss" undersells EarthBuild. 90% of the DAG is reused; only the affected target recompiles. | +| **03 (Unchanged)** | **Zero-Work Rebuild** (or *Instant Replay*) | *Cache Hit* | Emphasizes zero CPU waste and sub-second validation. | +| **Build Configuration** | **`Earthfile`** | *EarthBuild file, Dockerfile* | Exact filename used by the CLI. | +| **Execution Engine** | **Customized BuildKit Engine** (or *Forked BuildKit*) | *Standard BuildKit, Docker daemon, upstream BuildKit* | **Crucial distinction:** EarthBuild does **NOT** use standard upstream BuildKit. It executes on a heavily customized, open-source fork of BuildKit with custom LLB operations, interactive container debugging (`earth -i`), and DAG extensions designed specifically for Earthfiles. | + +--- + +## 5. Web Application Architecture (`web/`) + +The website in `web/` is the public face of EarthBuild ([earthbuild.dev](https://earthbuild.dev)). + +### Technology Stack +- **Static Site Generator**: [Hugo](https://gohugo.io/) (extended with goldmark renderer). +- **Styling**: Pure **Vanilla CSS** (`static/css/style.css`). Do NOT introduce TailwindCSS or heavy CSS preprocessors without explicit instruction. +- **Interactivity**: Modular **Strict TypeScript** (`assets/ts/`), compiled natively with sub-millisecond speeds via **Hugo Pipes (`js.Build`)** and ESBuild (zero npm/node_modules dependencies). No React, Vue, or heavy client frameworks. +- **Fonts**: Inter (UI text) + JetBrains Mono (code, terminals, metric figures). + +### Design Standards +- **Dark Mode First**: Clean, developer-focused aesthetic. +- **Material Design 3 (M3) Adaptive Design**: Layouts must adapt structurally across M3 Window Size Classes rather than relying on arbitrary ad-hoc media breakpoints: + - **Compact (< 600px)**: Single-column vertical stacks, off-canvas navigation drawer, full-bleed cards, and ergonomic touch targets $\ge 48\text{px}$. + - **Medium (600px – 839px)**: Tablets and foldables. Single-column or hybrid 2-pane, compact touch-friendly controls. + - **Expanded (840px – 1199px)**: Small laptops and standard desktop viewports. Multi-column desktop grids and the CodeRabbit-style horizontal 3-window race runner deck. + - **Large & Extra-Large ($\ge$ 1200px)**: Desktop monitors and ultra-wides. Bounded max-width containers (`1140px` / `1200px`) that preserve optimal reading line length (`60–75` characters) and balanced whitespace. +- **Single Source of Truth**: All design tokens (palette, glassmorphism, semantic accents, shadows, and radii) and layout mechanics are defined in `static/css/style.css`. Always use existing CSS custom properties (e.g., `var(--bg-primary)`, `var(--accent-primary)`) rather than hardcoding ad-hoc hex values or inline styles. + +--- + +## 6. Toolchain & Runtime Version Policy + +It is **mandatory** that all code examples, documentation, Earthfiles, Dockerfiles, and CI workflow configurations across the website strictly pin the **latest current toolchain, compiler, and container image versions**. Outdated, end-of-life, or superseded runtime versions must never be introduced or retained. + +### Baseline Version Standards +- **Go**: `1.27+` (e.g., `golang:1.27-alpine3.24`, `go 1.27.0`, `actions/setup-go@v5` with `go-version: '1.27'`). +- **Node.js**: `26+` (e.g., `node:26-alpine`, `node:26.10.0-alpine3.24`). +- **Python**: `3.14+` (e.g., `python:3.14-slim`, `python:3.14.7-slim`). +- **Alpine Linux**: `3.24+` (e.g., `alpine:3.24`, `alpine:3.24.2`). +- **Ubuntu**: `26.04` (e.g., `ubuntu:26.04`, `runs-on: ubuntu-26.04`). +- **Rust**: `1.85+` (e.g., `rust:1.85-alpine`). +- **GitHub Actions**: Modern actions pinned to current majors (`actions/checkout@v4`, `actions/setup-go@v5`, `earthbuild/actions-setup@v2`). +- **Earthfile Specification**: Always use `VERSION 0.8`. + +### Anti-Patterns to Avoid +- **Strictly never use or invent hypothetical tooling**: Never fabricate, hallucinate, or present hypothetical SDKs, packages, CLI flags, APIs, or language bindings that do not exist in reality. If an external tool, framework, or ecosystem lacks support for a language or feature (e.g., Dagger has NO Dart SDK), state that limitation immediately, directly, and factually. Never provide speculative code examples or theoretical workarounds. +- **Strictly forbidden to use deprecated APIs, methods, or configurations**: Agents are strictly forbidden from introducing deprecated, superseded, or obsolete functions, APIs, configuration helpers, or packages. Always use current, actively supported idioms (e.g., use official `defineConfig` from `eslint/config` instead of deprecated `tseslint.config`; modern ESLint 9+ flat config standards; current major versions of tools and actions). +- **Never use legacy/EOL versions**: Do not use Go 1.21/1.24, Alpine 3.19/3.21, or Node 18/20/24 in examples or documentation. +- **Maintain version parity across examples**: When demonstrating side-by-side migrations (e.g., Dockerfile vs. Earthfile vs. Dagger vs. GitHub Actions), ensure all examples use the exact same runtime versions (`golang:1.27-alpine3.24` and `alpine:3.24`). +- **Never use `EARTHLY_` environment variables**: Always write `EARTH_DOCKER_CONFIG`, `EARTH_CONFIG`, `EARTH_CI`, etc. Legacy Earthly variables are forbidden. +- **Never invoke `earthbuild` as a CLI command**: Always invoke `earth`. + +### EarthBuild CLI & Environment Variable Standards +- **Binary Command**: Always invoke `earth` (e.g., `earth +test`, `earth --ci +all`, `earth -i +target`). +- **Environment Variables**: All environment variables for EarthBuild use the **`EARTH_`** prefix. Never generate or document legacy `EARTHLY_` variables. Examples: + - `EARTH_DOCKER_CONFIG` (replaces legacy `EARTHLY_DOCKER_CONFIG`) + - `EARTH_CONFIG` (replaces legacy `EARTHLY_CONFIG`) + - `EARTH_CI` (replaces legacy `EARTHLY_CI`) + - `EARTH_BUILDKIT_IMAGE` (replaces legacy `EARTHLY_BUILDKIT_IMAGE`) + - `EARTH_DEBUG` (replaces legacy `EARTHLY_DEBUG`) + - `EARTH_AUTO_SKIP` (replaces legacy `EARTHLY_AUTO_SKIP`) + - `EARTH_TMP_DIR` (replaces legacy `EARTHLY_TMP_DIR`) + +--- + +## 7. Strict Operational Rules for AI Agents + +> [!IMPORTANT] +> ### Strict Prohibition of Deprecated APIs & Tools +> - **Strictly forbidden to use deprecated stuff**: Agents must never introduce deprecated APIs, superseded functions, obsolete config helpers, or legacy conventions (e.g., `tseslint.config` is deprecated in favor of official `defineConfig` from `eslint/config`). Always check current documentation and official migration guides to ensure only modern, active standards are used. + +> [!IMPORTANT] +> ### Strict Reality & Verifiability Standard (Zero Hypothetical Stuff) +> - **Strictly NEVER use or invent hypothetical tooling**: Agents must never fabricate, speculate, or present hypothetical SDKs, language bindings, packages, CLI flags, or APIs. +> - **Directly acknowledge absence of support**: If an external tool, framework, or runtime does not support a language or feature in reality (e.g., Dagger has NO Dart SDK, NO Swift SDK, etc.), state this limitation immediately, unequivocally, and factually. Never write pseudocode, invent non-existent libraries, or propose theoretical "hypothetical" implementations. +> - **Verifiable artifacts only**: All code examples, dependency manifests (`Cargo.toml`, `package.json`, `pubspec.yaml`, `go.mod`), container base images, and commands must correspond strictly to real, verifiable software that actually exists in official registries and repositories. + +> [!IMPORTANT] +> ### Mandatory EarthBuild Invariants +> - **Project Identity**: This project is **EarthBuild** (not Earthly). Never refer to the project, software, or repository as Earthly. +> - **CLI Binary**: The CLI command is strictly **`earth`** (package `earthbuild`). +> - **Environment Variables**: All CLI and runtime environment variables must use the **`EARTH_`** prefix (e.g., `EARTH_DOCKER_CONFIG`, `EARTH_CONFIG`, `EARTH_CI`). Never use `EARTHLY_*`. + +> [!CAUTION] +> ### Mandatory Git & Version Control Rules +> - **NEVER stage changes**: Do NOT run `git add`, `git stage`, or any command that stages files into the git index. +> - **NEVER unstage user changes**: Do NOT run `git restore --staged`, `git reset`, or unstage files if the user has added files to staging. The user selectively stages files they have reviewed. +> - **NEVER commit or push**: Do NOT run `git commit`, `git push`, or modify branches/history unless explicitly commanded by the user. +> - **Leave agent changes unstaged**: All modifications, new files, and deletions made by the agent must remain unstaged in the working directory so the user retains complete review authority. +> - **Read-only inspection only**: Use strictly read-only inspection commands (`git status`, `git diff`, `git log`) to verify working tree status. Never execute commands that mutate the git index or repository history. + +### Testing & Validation +- **Hugo Site Build**: Run `hugo --cleanDestinationDir --minify` in `web/` to verify zero template or asset compilation errors and automatically purge obsolete hashed asset bundles. +- **Dev Server**: Hugo dev server runs on `http://localhost:1313/` (`-D --disableFastRender`). +- **Interactive Verification**: Validate all interactive components (tabs, copy buttons, race runner, mobile drawer) across desktop and mobile viewports. diff --git a/Earthfile b/Earthfile new file mode 100644 index 0000000..b72893a --- /dev/null +++ b/Earthfile @@ -0,0 +1,82 @@ +VERSION 0.8 + +hugo-base: + FROM alpine:3.24.2@sha256:294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6 + RUN apk add --no-cache git hugo + +src: + FROM +hugo-base + WORKDIR /site + COPY hugo.toml . + COPY --dir assets content layouts static . + +# build compiles the static website with Hugo and exports the public directory. +build: + FROM +src + ARG BASE_URL="" + IF [ -n "$BASE_URL" ] + RUN hugo --minify --baseURL "$BASE_URL" + ELSE + RUN hugo --minify + END + SAVE ARTIFACT public /public AS LOCAL public + +node-base: + FROM node:26.10.0-alpine3.24@sha256:0b36e8c136b94cd4fcf02188228e76c31ad5872eef3fec8cbd2eee500cfd9e80 + WORKDIR /site + COPY package.json package-lock.json ./ + RUN npm ci + +browser-base: + FROM +node-base + RUN apk add --no-cache chromium nss freetype harfbuzz ca-certificates ttf-freefont + ENV CHROME_PATH=/usr/bin/chromium-browser + +# lint runs ESLint and Prettier checks on TypeScript source and test suites. +lint: + FROM +node-base + COPY eslint.config.js .prettierrc ./ + COPY --dir assets tests . + RUN npm run lint + +# lint-workflows audits GitHub Actions workflows with zizmor (https://docs.zizmor.sh). +lint-workflows: + FROM ghcr.io/zizmorcore/zizmor:1.30.1 + WORKDIR /audit + COPY --dir .github . + RUN zizmor --no-online-audits --strict-collection .github + +# test executes End-to-End (E2E) browser & UI state tests against the compiled static site. +test: + FROM +browser-base + COPY --dir +build/public . + COPY --dir tests . + RUN npm test + +# check validates code quality, workflow security, and UI behavior concurrently. +check: + BUILD +lint + BUILD +lint-workflows + BUILD +test + +# all executes all validation checks. +all: + BUILD +check + +# lighthouse runs Lighthouse CI audits (both Mobile and Desktop) against the compiled static site. +lighthouse: + FROM +browser-base + RUN npm install -g @lhci/cli + ENV LHCI_BUILD_CONTEXT__CURRENT_HASH=0000000000000000000000000000000000000000 + ENV LHCI_BUILD_CONTEXT__CURRENT_BRANCH=main + ENV LHCI_BUILD_CONTEXT__COMMIT_TIME=1970-01-01T00:00:00Z + COPY --dir +build/public . + COPY lighthouserc.json . + RUN lhci collect && lhci upload && \ + mv .lighthouseci /tmp/mobile + RUN lhci collect --settings.preset=desktop && lhci upload && \ + mv .lighthouseci /tmp/desktop + RUN mkdir -p .lighthouseci/mobile .lighthouseci/desktop && \ + cp -r /tmp/mobile/* .lighthouseci/mobile/ && \ + cp -r /tmp/desktop/* .lighthouseci/desktop/ + SAVE ARTIFACT .lighthouseci /lighthouse-results AS LOCAL .lighthouseci diff --git a/assets/ts/accordion.ts b/assets/ts/accordion.ts new file mode 100644 index 0000000..63e4376 --- /dev/null +++ b/assets/ts/accordion.ts @@ -0,0 +1,96 @@ +import { SupportedLanguage, WindowKey } from './types'; +import { codeSamples } from './samples-data'; +import { TripleExecutionRaceEngine } from './race-runner'; +import { setMigrationLanguage } from './comparison'; + +export function initCodeExplorer(raceEngine: TripleExecutionRaceEngine): void { + const tabs = document.querySelectorAll('#raceLangTabs .race-tab-btn'); + const codePane = document.getElementById('raceCodePane'); + const fileLink = document.getElementById('raceFileLink'); + + // Sticky Language Navbar scale-up observer + const sentinel = document.getElementById('langNavSentinel'); + const langNavbar = document.getElementById('raceLangNavbar'); + + if (sentinel && langNavbar) { + const updateStuck = () => { + const rect = sentinel.getBoundingClientRect(); + const isStuck = rect.top <= 20; + langNavbar.classList.toggle('is-stuck', isStuck); + }; + + window.addEventListener('scroll', updateStuck, { passive: true }); + window.addEventListener('resize', updateStuck, { passive: true }); + updateStuck(); + } + + // Deck Elements (CodeRabbit-style horizontal cards) + const deck = document.getElementById('stackedWindowsDeck'); + const cards = document.querySelectorAll( + '.cr-feature-card, .stacked-terminal-window', + ); + + // Function to activate and expand a specific window card + function bringToFront(targetKey: WindowKey | string): void { + if (!deck) return; + deck.setAttribute('data-front', targetKey); + + cards.forEach((card) => { + const isFront = card.getAttribute('data-window') === targetKey; + card.classList.toggle('is-front', isFront); + card.setAttribute('data-active', isFront ? 'true' : 'false'); + card.setAttribute('aria-selected', isFront ? 'true' : 'false'); + const windowContent = card.querySelector('.cr-card-window-content'); + if (windowContent) { + windowContent.setAttribute('aria-hidden', isFront ? 'false' : 'true'); + } + }); + } + + // Click & focus interaction for each window card & header tab + cards.forEach((card) => { + const winKey = card.getAttribute('data-window') as WindowKey | null; + if (!winKey) return; + + const header = card.querySelector('.cr-card-tab-header, .stacked-window-header'); + + card.addEventListener('click', () => { + bringToFront(winKey); + }); + + if (header) { + header.addEventListener('click', (e) => { + e.stopPropagation(); + bringToFront(winKey); + }); + } + + card.addEventListener('focus', () => { + bringToFront(winKey); + }); + }); + + // Language Tabs + tabs.forEach((tab) => { + tab.addEventListener('click', () => { + tabs.forEach((t) => t.classList.remove('active')); + tab.classList.add('active'); + + const lang = tab.getAttribute('data-lang') as SupportedLanguage | null; + if (!lang || !codeSamples[lang]) return; + + if (fileLink) { + fileLink.innerHTML = `view code ↗`; + } + if (codePane) { + codePane.innerHTML = `${codeSamples[lang].code}`; + } + raceEngine.startRace(lang); + setMigrationLanguage(lang); + }); + }); + + // Initial Load (Render pre-computed static results across all 3 windows on initial load) + bringToFront('repeat'); + raceEngine.showStatic('python'); +} diff --git a/assets/ts/anchor-links.ts b/assets/ts/anchor-links.ts new file mode 100644 index 0000000..aaee2f8 --- /dev/null +++ b/assets/ts/anchor-links.ts @@ -0,0 +1,251 @@ +/** + * Anchor Links & Copy Link to Specific Content + * Allows users to copy direct URLs to sections, headings, and FAQ questions with instant clipboard feedback. + */ + +import { copyTextToClipboard } from './clipboard'; + +let toastTimeout: number | null = null; + +export function showCopyToast(message: string): void { + let toast = document.getElementById('copyToast'); + if (!toast) { + toast = document.createElement('div'); + toast.id = 'copyToast'; + toast.className = 'copy-toast'; + toast.setAttribute('role', 'status'); + toast.setAttribute('aria-live', 'polite'); + toast.innerHTML = ` + + + `; + document.body.appendChild(toast); + } + + const msgEl = toast.querySelector('.copy-toast-msg'); + if (msgEl) { + msgEl.textContent = message; + } + + toast.classList.add('show'); + + if (toastTimeout !== null) { + window.clearTimeout(toastTimeout); + } + toastTimeout = window.setTimeout(() => { + toast?.classList.remove('show'); + toastTimeout = null; + }, 2200); +} + +export function scrollElementIntoView(target: HTMLElement, smooth = true): void { + // If target is or contains a details element, make sure it is open before calculating position + if (target instanceof HTMLDetailsElement) { + target.open = true; + } + const parentDetails = target.closest('details'); + if (parentDetails) { + parentDetails.open = true; + } + + // If target has a .section-header inside (like
), + // the content begins at .section-header, not at the top of the outer section padding. + const contentStart = target.querySelector('.section-header') || target; + const targetTop = contentStart.getBoundingClientRect().top + window.pageYOffset - 32; // 2rem clearance + + window.scrollTo({ + top: Math.max(0, targetTop), + behavior: smooth ? 'smooth' : 'auto', + }); +} + +function createAnchorButton(id: string, titleText: string): HTMLAnchorElement { + const link = document.createElement('a'); + link.href = `#${id}`; + link.className = 'heading-anchor-btn'; + link.setAttribute('data-anchor-id', id); + link.setAttribute('aria-label', `Copy link to "${titleText}"`); + link.setAttribute('title', `Copy link to "${titleText}"`); + link.innerHTML = ` + + + `; + + link.addEventListener('click', async (e: MouseEvent) => { + // If user clicked with Ctrl/Cmd or middle click, allow browser to open in new tab + if (e.metaKey || e.ctrlKey || e.button === 1) return; + + e.preventDefault(); + e.stopPropagation(); + + const search = window.location.search || ''; + const url = `${window.location.origin}${window.location.pathname}${search}#${id}`; + + // 1. Always update browser history and address bar so user has the hash link + try { + window.history.pushState(null, '', `${search}#${id}`); + } catch { + window.location.hash = id; + } + + // 2. Smooth scroll target into view where content starts + const target = document.getElementById(id); + if (target) { + scrollElementIntoView(target, true); + target.classList.add('anchor-highlight'); + setTimeout(() => target.classList.remove('anchor-highlight'), 1500); + } + + // 3. Multi-tier copy to clipboard (Async Clipboard API -> fallback execCommand) + const copied = await copyTextToClipboard(url); + + // 4. Visual feedback + link.classList.add('copied'); + showCopyToast(copied ? 'Link copied to clipboard!' : 'Link updated in address bar!'); + + setTimeout(() => { + link.classList.remove('copied'); + }, 2000); + }); + + return link; +} + +function attachAnchorToTitle(titleEl: HTMLElement, anchorBtn: HTMLAnchorElement): void { + // Append zero-width, zero-height holder containing the absolutely positioned anchor button. + // This leaves all text and child elements 100% untouched and adds 0px to the line width, + // guaranteeing mathematical precision for centered headers and zero unwanted line wraps. + const holder = document.createElement('span'); + holder.className = 'heading-anchor-holder'; + holder.appendChild(anchorBtn); + titleEl.appendChild(holder); +} + +export function initHeadingAnchorLinks(): void { + // 1. Process all section titles in sections that have an ID + const sections = document.querySelectorAll('section[id]'); + sections.forEach((section) => { + const id = section.id; + if (!id) return; + + const titleEl = section.querySelector('.section-title'); + if (titleEl && !titleEl.querySelector('.heading-anchor-btn')) { + const cleanTitle = titleEl.textContent?.trim().replace(/\s+/g, ' ') || id; + const anchorBtn = createAnchorButton(id, cleanTitle); + attachAnchorToTitle(titleEl, anchorBtn); + } + }); + + // 2. Process documentation headings with IDs in markdown (.doc-content) + const docHeadings = document.querySelectorAll( + '.doc-content h2[id], .doc-content h3[id]', + ); + docHeadings.forEach((heading) => { + const id = heading.id; + if (!id || heading.querySelector('.heading-anchor-btn')) return; + + const cleanTitle = heading.textContent?.trim().replace(/\s+/g, ' ') || id; + const anchorBtn = createAnchorButton(id, cleanTitle); + attachAnchorToTitle(heading, anchorBtn); + }); + + // 3. Process FAQ items if they have an ID + const faqItems = document.querySelectorAll('.faq-item[id]'); + faqItems.forEach((item) => { + const id = item.id; + if (!id) return; + const summary = item.querySelector('.faq-summary'); + if (summary && !summary.querySelector('.heading-anchor-btn')) { + const cleanTitle = summary.textContent?.trim().replace(/\s+/g, ' ') || id; + const anchorBtn = createAnchorButton(id, cleanTitle); + const questionSpan = document.createElement('span'); + questionSpan.className = 'faq-question-text'; + while (summary.firstChild) { + questionSpan.appendChild(summary.firstChild); + } + attachAnchorToTitle(questionSpan, anchorBtn); + summary.appendChild(questionSpan); + } + }); + + // 4. In-page anchor click interception for smooth, exact scrolling + document.addEventListener('click', (e: MouseEvent) => { + const anchor = (e.target as HTMLElement)?.closest('a[href*="#"]'); + if (!anchor || anchor.classList.contains('heading-anchor-btn')) return; + + // Skip if external, modified click (cmd/ctrl/shift), or has target="_blank" + if (e.metaKey || e.ctrlKey || e.button === 1 || anchor.target === '_blank') return; + + const href = anchor.getAttribute('href'); + if (!href) return; + + let hash = ''; + if (href.startsWith('#')) { + hash = href.substring(1); + } else if ( + href.startsWith('/#') && + (window.location.pathname === '/' || window.location.pathname === '') + ) { + hash = href.substring(2); + } + + if (hash) { + const target = document.getElementById(hash); + if (target) { + e.preventDefault(); + window.history.pushState(null, '', `#${hash}`); + scrollElementIntoView(target, true); + target.classList.add('anchor-highlight'); + setTimeout(() => target.classList.remove('anchor-highlight'), 1500); + + // Close mobile menu if open + const drawer = document.getElementById('mobileNavDrawer'); + drawer?.classList.remove('open', 'active'); + const toggle = document.getElementById('mobileMenuToggle'); + toggle?.setAttribute('aria-expanded', 'false'); + } + } + }); + + // 5. Initial Hash Navigation handler: if user opens page with #hash, expand details and scroll cleanly to content start + const handleHashChange = (smooth = true) => { + if (window.location.hash) { + let rawId = window.location.hash.substring(1); + if (rawId.includes('?')) { + rawId = rawId.split('?')[0]; + } + const target = document.getElementById(rawId); + if (target) { + scrollElementIntoView(target, smooth); + setTimeout( + () => { + target.classList.add('anchor-highlight'); + setTimeout(() => target.classList.remove('anchor-highlight'), 1800); + }, + smooth ? 250 : 0, + ); + } + } + }; + + // Run on browser back/forward or manual hash changes + window.addEventListener('hashchange', () => handleHashChange(true)); + + // Run on initial load once styles/fonts have settled + if (window.location.hash) { + if (document.readyState === 'complete') { + setTimeout(() => handleHashChange(false), 50); + } else { + window.addEventListener('load', () => { + setTimeout(() => handleHashChange(false), 50); + }); + } + } +} diff --git a/assets/ts/clipboard.ts b/assets/ts/clipboard.ts new file mode 100644 index 0000000..32db68b --- /dev/null +++ b/assets/ts/clipboard.ts @@ -0,0 +1,81 @@ +/** + * Clipboard Copy Functionality with multi-tier fallback & transient visual feedback + */ + +export async function copyTextToClipboard(text: string): Promise { + // 1. Try modern navigator.clipboard if available + if ( + typeof navigator !== 'undefined' && + navigator.clipboard && + typeof navigator.clipboard.writeText === 'function' + ) { + try { + await navigator.clipboard.writeText(text); + return true; + } catch { + // Async clipboard can reject due to permissions, focus, or browser policies. + // Fall through immediately to synchronous execCommand copy. + } + } + + // 2. Synchronous fallback using temporary textarea + document.execCommand('copy') + try { + const textarea = document.createElement('textarea'); + textarea.value = text; + textarea.setAttribute('readonly', ''); + textarea.style.position = 'fixed'; + textarea.style.top = '0'; + textarea.style.left = '0'; + textarea.style.width = '2em'; + textarea.style.height = '2em'; + textarea.style.padding = '0'; + textarea.style.border = 'none'; + textarea.style.outline = 'none'; + textarea.style.boxShadow = 'none'; + textarea.style.background = 'transparent'; + textarea.style.opacity = '0.01'; + textarea.style.zIndex = '-1'; + textarea.style.pointerEvents = 'none'; + document.body.appendChild(textarea); + textarea.focus({ preventScroll: true }); + textarea.select(); + textarea.setSelectionRange(0, textarea.value.length); + const success = document.execCommand('copy'); + document.body.removeChild(textarea); + return Boolean(success); + } catch (err) { + console.error('execCommand copy fallback failed:', err); + return false; + } +} + +export function initClipboard(): void { + document.addEventListener('click', async (e: MouseEvent) => { + const btn = (e.target as HTMLElement)?.closest?.('.copy-btn') as HTMLButtonElement | null; + if (!btn) return; + + const targetId = btn.getAttribute('data-clipboard-target'); + const targetEl = targetId ? document.getElementById(targetId) : null; + const textToCopy = targetEl + ? targetEl.innerText || targetEl.textContent + : btn.getAttribute('data-copy-text'); + + if (textToCopy) { + const ok = await copyTextToClipboard(textToCopy.trim()); + if (ok) { + const originalHTML = btn.innerHTML; + btn.classList.add('copied'); + btn.innerHTML = ` + + Copied! + `; + setTimeout(() => { + btn.innerHTML = originalHTML; + btn.classList.remove('copied'); + }, 2000); + } + } + }); +} diff --git a/assets/ts/comparison.ts b/assets/ts/comparison.ts new file mode 100644 index 0000000..d4a7cfd --- /dev/null +++ b/assets/ts/comparison.ts @@ -0,0 +1,176 @@ +/** + * Comparison Table Filters & Migration Guide Tabs + */ + +import { SupportedLanguage } from './types'; +import { migrationData, MigrationTools } from './migration-data'; + +let currentLang: SupportedLanguage = 'go'; +let currentTool: MigrationTools = 'dockerfile'; +const activeFileIndexPerTool: Record = {}; + +export function updateMigrationDisplay(lang: SupportedLanguage, tool: MigrationTools): void { + const sample = migrationData[lang]?.[tool]; + if (!sample) return; + + const targetId = `mig-${tool}`; + const panel = document.getElementById(targetId); + if (!panel) return; + + const legacyTitle = panel.querySelector('.mig-legacy-title'); + const legacyCode = panel.querySelector('.mig-legacy-code'); + const earthfileTitle = panel.querySelector('.mig-earthfile-title'); + const earthfileCode = panel.querySelector('.mig-earthfile-code'); + const tabsContainer = panel.querySelector('.mig-legacy-file-tabs'); + + if (legacyTitle) legacyTitle.textContent = sample.legacyTitle; + if (earthfileTitle) earthfileTitle.textContent = sample.earthfileTitle; + if (earthfileCode) earthfileCode.innerHTML = sample.earthfileCode; + + // Handle multi-file tooling (e.g. docker-bake.hcl + Dockerfile, Makefile + Dockerfile) + if (sample.legacyFiles && sample.legacyFiles.length > 1) { + let fileIdx = activeFileIndexPerTool[tool] ?? 0; + if (fileIdx >= sample.legacyFiles.length) { + fileIdx = 0; + activeFileIndexPerTool[tool] = 0; + } + + if (tabsContainer) { + tabsContainer.style.display = 'inline-flex'; + tabsContainer.innerHTML = sample.legacyFiles + .map( + (file, idx) => ` + + `, + ) + .join(''); + + const btns = tabsContainer.querySelectorAll('.window-file-tab-btn'); + btns.forEach((btn) => { + btn.addEventListener('click', (e) => { + e.stopPropagation(); + const idx = parseInt(btn.getAttribute('data-file-idx') || '0', 10); + activeFileIndexPerTool[tool] = idx; + btns.forEach((b, bIdx) => { + const isActive = bIdx === idx; + b.classList.toggle('active', isActive); + b.setAttribute('aria-selected', isActive ? 'true' : 'false'); + }); + if (legacyCode && sample.legacyFiles && sample.legacyFiles[idx]) { + legacyCode.innerHTML = sample.legacyFiles[idx].code; + } + }); + }); + } + + if (legacyCode) { + legacyCode.innerHTML = sample.legacyFiles[fileIdx].code; + } + } else { + if (tabsContainer) { + tabsContainer.style.display = 'none'; + tabsContainer.innerHTML = ''; + } + if (legacyCode) { + legacyCode.innerHTML = sample.legacyCode; + } + } +} + +export function setMigrationLanguage(lang: SupportedLanguage): void { + currentLang = lang; + + const migLangTabs = document.querySelectorAll('#migLangTabs .race-tab-btn'); + migLangTabs.forEach((tab) => { + tab.classList.toggle('active', tab.getAttribute('data-lang') === lang); + }); + + updateMigrationDisplay(currentLang, currentTool); +} + +export function initComparisonFilters(): void { + const filterBtns = document.querySelectorAll('.matrix-filter-btn'); + const tableRows = document.querySelectorAll('.comparison-table tbody tr'); + + filterBtns.forEach((btn) => { + btn.addEventListener('click', () => { + filterBtns.forEach((b) => b.classList.remove('active')); + btn.classList.add('active'); + + const cat = btn.getAttribute('data-filter') || btn.getAttribute('data-category'); + + tableRows.forEach((row) => { + const rowCat = row.getAttribute('data-category'); + if (cat === 'all' || rowCat === cat) { + row.style.display = ''; + } else { + row.style.display = 'none'; + } + }); + }); + }); +} + +export function initMigrationTabs(): void { + const migLangTabs = document.querySelectorAll('#migLangTabs .race-tab-btn'); + const migToolTabs = document.querySelectorAll('.migration-tab-btn'); + const container = document.querySelector('.migration-container'); + + // Language Tabs in Migration Section + migLangTabs.forEach((tab) => { + tab.addEventListener('click', () => { + migLangTabs.forEach((t) => t.classList.remove('active')); + tab.classList.add('active'); + + const lang = tab.getAttribute('data-lang') as SupportedLanguage | null; + if (!lang || !migrationData[lang]) return; + + currentLang = lang; + updateMigrationDisplay(currentLang, currentTool); + }); + }); + + // Tool Tabs in Migration Section (Dockerfile, Bake, Makefile, Dagger, GHA) + migToolTabs.forEach((tab) => { + tab.addEventListener('click', () => { + migToolTabs.forEach((t) => t.classList.remove('active')); + tab.classList.add('active'); + + const targetId = tab.getAttribute('data-target'); + if (!targetId) return; + + currentTool = targetId.replace('mig-', '') as MigrationTools; + + // Lazy-mount panel from template if not yet present in DOM + if (!document.getElementById(targetId) && container) { + const tmpl = document.getElementById(`tmpl-${targetId}`) as HTMLTemplateElement | null; + if (tmpl) { + const clone = tmpl.content.cloneNode(true); + container.appendChild(clone); + } + } + + // Hide all panels, show target panel + const migPanels = document.querySelectorAll('.migration-panel'); + migPanels.forEach((panel) => { + if (panel.id === targetId) { + panel.style.display = 'grid'; + } else { + panel.style.display = 'none'; + } + }); + + // Update code contents for active language in this tool panel + updateMigrationDisplay(currentLang, currentTool); + }); + }); + + // Initial render with currentLang & currentTool + updateMigrationDisplay(currentLang, currentTool); +} diff --git a/assets/ts/digital-earth.ts b/assets/ts/digital-earth.ts new file mode 100644 index 0000000..d67eb4b --- /dev/null +++ b/assets/ts/digital-earth.ts @@ -0,0 +1,786 @@ +/** + * Geometric Voxel 3D Earth Generator + * Pure Cartesian voxel styling: ONLY CUBES, zero spheres, zero circles. + * Features: + * - High-resolution Cartesian voxel grid (~3,710 cubic blocks) + * - Geographically authentic Natural Earth 110m landmask (exact continents, islands, peninsulas) + * - Pure BoxGeometry instances only (strictly cubes) + * - Initial framing showcasing the Americas and Atlantic on load, rotating through Europe & Africa + * - Vibrant planetary voxel palette (Vibrant Blue, Forest Green, Sand Yellow, White) + * - Balanced lighting for crisp block facets and rich, saturated colors + * - Interactive Cursor Parallax & Tilt + Smooth Planetary Rotation. + */ + +declare const THREE: any; + +interface VoxelPart { + x: number; + y: number; + z: number; + color: number; +} + +// Earth Voxel Palette +const VOXEL_COLORS = { + brightBlue: 0x0d69ac, // Ocean (Deep Blue) + mediumBlue: 0x2bb2ff, // Coastal Waters / Shallow Reefs (Vibrant Cyan) + brightGreen: 0x3cb371, // Continents / Grassland (Bright Emerald Green) + darkGreen: 0x228b22, // Forests / Amazon / Congo (Deep Forest Green) + brickYellow: 0xdfce9f, // Deserts / Sand (Warm Gold / Sand) + darkStoneGrey: 0x64748b, // Mountain ranges (Slate Grey) + white: 0xffffff, // Polar Ice & Snowy Peaks (Pure White) +}; + +// 360x180 1-bit Equirectangular Landmask (Natural Earth 110m canonical coastline boundaries) +// 64,800 bits packed into 8,100 bytes (100% geographically authentic Earth continents) +const EARTH_LANDMASK_B64 = + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAP//gAAD//4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//////Hf///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAf/////////////AAAAAAAAAAAAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAB/////////////wAAAAX/8AAP8AAAAAAfwAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAOB////g////////AAAAD//wAAAAAAAAAAH/gAAAAAAAAAAAAAAAAAAAAAAAeP3vv/7////////+AAAAB//AAAAAAAAAAAAf8AAAAAAAAAAAA' + + 'AAAAAAAAAA8+AL4f/g////////+AAAAAfvgAAAAAAAAAAAF+AAAAAAAAAAAAAAAAAAAAAH/3R////gP////////AAAAAHAAAAAAAf4AAAHP//AAAA8AAAAAA' + + 'AAAAAAAAACP/5///8AAAf/////+AAAAAAAAAAAAf+AAAH////gAAD//gAAAAAAAAAAAAAf4+AN//+AAAP/////+AAAAAAAAAAAAeAAAAP///4AAAA/nAAAAA' + + 'AAAAAAAAAf/B8///3wAAH/////8AAAAAAAAAAAB4AEAP//////H4AfAAAAAAAAAAAAAAA//+5/9//4AAD/////wAAAAAAAAAAAHwAHt////////8Af/AAAAA' + + '///////////////////////////////////////////////////////////+wAA//+gAOYf//1+///+AB/////4AAAAAAH/wAAB8A/v/////////////8AID' + + 'AAD///+P/////v/j///AB////+AAAAAAAf//gAAA+/////////////////P+////////////////////////////////+AAAP///wMYAAAAAAAAAAAAAAAQA' + + 'B///////////////////////////////4AAAD/4AAAcAAAAAAAAAAAAAAAAATP//////////////////////////////wBAH/7AAABwAAAAAAAAAAAAAAAAA' + + '8f//////////////////////////////gHwD8AAAAAAAAAAAAAAAAAAAAAAA////////////////////////////////////////////////////////////' + + 'APH////////////PcB/wAP/gAAfgAAAP/3/////////////////////////+ACP////////////A///gAD/AAAAAAAB//H//////////////////////////' + + 'AAf///////////wAG/5AAD/AAAAAAAD//H/////////////////////////gAA////////////wAA/4QAB+AAAAAAAB//n//////////////////////D/+A' + + 'AAb/zwH///////wAA/44AAAAAAAAAAB//D//////////////////////D6IAAAAv4AA///////4AB//8AAAAAAAAA4B5/B///////////////////4AYPgAA' + + 'AAAH8AAD///////AAf/8AAAAAAAAA+AP+H///////////////////gAA/wAAAAAfQAAB///////wAf/+AAAAAAAAB8AO8H///////////////////AAB/wAA' + + 'AAD8AAAA////////x///gAAAAAAAD8AP4H//////////////////+AAB/AAAAAGAAAAD////////z///4AAAAAAAH/AP////////////////////+yAB/gAA' + + 'AAAAAAADP///////x///+AAAAAAAPfj//////////////////////+AB+AAAAAAAAAABv///////5///+AAAAAAAPfz///////////////////////AA4AAA' + + 'AAAAAAAAD///////////+AAAAAAAM/////////////////////////AA4AAAAAAAAAAAH///////////MAAAAAAAA////////////////////////7AAAAAA' + + 'AAAAAAAAH//////////+fgAAAAAAAj///////////////////////7AAAAAAAAAAAAAAA//////////w/wAAAAAAAf///////////////////////+gAAAAA' + + 'AAAAAAAAAf/////////x/wAAAAAAAf////////7//////////////yAAAAAAAAAAAAAAAf/////////9DgAAAAAAAD////////g//////////////nAAAAAA' + + 'AAAAAAAAAP//////////gAAAAAAAAD/////P/+A/////////////+GAAAAAAAAAAAAAAAP/////////4AAAAAAAAAD/////Hf8D/////////////8HQAAAAA' + + 'AAAAAAAAAf////////5gAAAAAAAAP//3z/+AH+D/////////////8PwAAAAAAAAAAAAAAf////////wAAAAAAAAAH/4P5/8HB+A////////////+wfgAAAAA' + + 'AAAAAAAAAf////////4AAAAAAAAAP/8M+f//n/Af///////////8AdAAAAAAAAAAAAAAAf////////gAAAAAAAAAH/gMP/////gf///////////8AcAAAAAA' + + 'AAAAAAAAAP///////8AAAAAAAAAAP/AMH/P///g///////////fwAMAAAAAAAAAAAAAAAP///////8AAAAAAAAAAP/gI/Pv///A//////////+T4AYAAAAAA' + + 'AAAAAAAAAH///////4AAAAAAAAAAH/A3+Hn///g///////////78C4AAAAAAAAAAAAAAAD///////4AAAAAAAAAAH8//MHH///w///////////48D4AAAAAA' + + 'AAAAAAAAAB///////4AAAAAAAAAAB//+AB4P//////////////A8/4AAAAAAAAAAAAAAAB///////wAAAAAAAAAAB//+AAgP//////////////g7/gAAAAAA' + + 'AAAAAAAAAAf//////AAAAAAAAAAAH///AAAB//////////////gH8AAAAAAAAAAAAAAAAAP/////8AAAAAAAAAAAP///8PAD//////////////wHwAAAAAAA' + + 'AAAAAAAAAAH/////4AAAAAAAAAAAP///+f/7//////////////wDAAAAAAAAAAAAAAAAAAH/////4AAAAAAAAAAAP/////////////////////wAAAAAAAAA' + + 'AAAAAAAAAAD////i8AAAAAAAAAAAf/////////////////////4AAAAAAAAAAAAAAAAAAAB///gAcAAAAAAAAAAB//////////v///////////wAAAAAAAAA' + + 'AAAAAAAAAAD//+AAcgAAAAAAAAAD//////////3///////////gAAAAAAAAAAAAAAAAAAAA7/+AAdwAAAAAAAAAH///////+//9///////////AAAAAAAAAA' + + 'AAAAAAAAAAAf/+AANgAAAAAAAAAH///////+//8f//////////QAAAAAAAAAAAAAAAAAAAAc/+AABgAAAAAAAAAP///////+f//4Af///////+wAAAAAAAAA' + + 'AAAAAAAAAAAGf+AAcgAAAAAAAAAf////////P//+Af///////8wAAAAAAAAAAAAIAAAAAAAGf+AB/gAAAAAAAAAf////////n///AH///////wwAAAAAAAAA' + + 'AAAPAAAAAAAAP+B9H4AAAAAAAAA/////////3///AD///v//8AgAAAAAAAAAAAABgAAAAAAAP+B8B8AAAAAAAAAf////////n//+AD//8P/+YAAAAAAAAAAA' + + 'AAABwAAAAAAAP/B4A/4AAAAAAAAf////////z//8AA//wH/88AAAAAAAAAAAAAAAgAAAAAAAD//4B3/gAAAAAAAf////////x//8AAf/AD/+4A4AAAAAAAAA' + + 'AAAAAAAAAAAAB//wAwjgAAAAAAAf////////5//4AAf/AD/+AA4AAAAAAAAAAAAAAAAAAAAAAP/0AAAAAAAAAAAf////////4//gAAf+AD//gB4AAAAAAAAA' + + 'AAAAAAAAAAAAADP/gAAAAAAAAAAf////////8/+AAAf4ADf/gBwAAAAAAAAAAAAAAAAAAAAAAAH/gAAAAAAAAAA///////////wAAAP4AAf/gA4AAAAAAAAA' + + 'AAAAAAAAAAAAAAD/gAAAAAAAAAAf//////////gAAAP4AAP/wA8AAAAAAAAAAAAAAAAAAAAAAAAPgAoAAAAAAAAf/////////8MAAAPwAAP/wAzAAAAAAAAA' + + 'AAAAAAAAAAAAAAAHgH8AAAAAAAAf/////////x8AAAHwAAMfwBfAAAAAAAAAAAAAAAAAAAAAAAADgP//AAAAAAAH//////////8AAADwAAMfADfAAAAAAAAA' + + 'AAAAAAAAAAAAAAAD/f//AAAAAAAH//////////4AAAD4AAMOAGNgAAAAAAAAAAAAAAAAAAAAAAAA////gAAAAAAD//////////4AAAD8AAOEAEPgAAAAAAAA' + + 'AAAAAAAAAAAAAAAAN///wAAAAAAB//////////wAAABcAAOAAAfgAAAAAAAAAAAAAAAAAAAAAAAAA///6AAAAAAA//////////wAAAAcAAHgAMPgAAAAAAAA' + + 'AAAAAAAAAAAAAAAAA////wAAAAAAP/x///////gAAAAIABzgAfBAAAAAAAAAAAAAAAAAAAAAAAAAA////4AAAAAAPuB///////AAAAAAAB7wA+AAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAA////4AAAAAAAAAP/////+AAAAAAAA/wD8AAAAAAAAAAAAAAAAAAAAAAAAAAB////4AAAAAAAAAH/////+AAAAAAAAfwX+AQAAAAAAAA' + + 'AAAAAAAAAAAAAAAAB////+AAAAAAAAAH/////4AAAAAAAAP4f8xYAAAAAAAAAAAAAAAAAAAAAAAAH////8AAAAAAAAAH/////wAAAAAAAAHw//+YAAAAAAAA' + + 'AAAAAAAAAAAAAAAAH/////wAAAAAAAAP/////gAAAAAAAADwf98bwAAAAAAAAAAAAAAAAAAAAAAAH/////8AAAAAAAAP/////AAAAAAAAAD4f58D5gAAAAAA' + + 'AAAAAAAAAAAAAAAAH//////4AAAAAAAH////+AAAAAAAAAD8P7wN78AwAAAAAAAAAAAAAAAAAAAAH//////8AAAAAAAB////+AAAAAAAAAA+H55+//gcAAAA' + + 'AAAAAAAAAAAAAAAAP//////+AAAAAAAB////8AAAAAAAAAA8Ap8A3/wcAAAAAAAAAAAAAAAAAAAAH///////gAAAAAAB////4AAAAAAAAAAfABsAJ/zzAAAA' + + 'AAAAAAAAAAAAAAAAP///////wAAAAAAA////8AAAAAAAAAAH+AAAI//hgAAAAAAAAAAAAAAAAAAAD///////wAAAAAAA////8AAAAAAAAAAD/gAAA/8A8AAA' + + 'AAAAAAAAAAAAAAAAD///////gAAAAAAA////8AAAAAAAAAAAP37wB/OAHAAAAAAAAAAAAAAAAAAAB///////gAAAAAAA////8AAAAAAAAAAAAPnAAGHgHAAA' + + 'AAAAAAAAAAAAAAAAB///////AAAAAAAAf///+AAAAAAAAAAAAAmAACHgBgAAAAAAAAAAAAAAAAAAA//////+AAAAAAAAf///+AAAAAAAAAAAAAAB+HAAAAAA' + + 'AAAAAAAAAAAAAAAAA//////+AAAAAAAAf///+AwAAAAAAAAAAAAD+HAAAAAAAAAAAAAAAAAAAAAAAf/////8AAAAAAAA////+B4AAAAAAAAAAAA3+HAAAAAA' + + 'AAAAAAAAAAAAAAAAAf/////8AAAAAAAA////+B4AAAAAAAAAAAB/8HwAADAAAAAAAAAAAAAAAAAAAP/////8AAAAAAAB////+H4AAAAAAAAAAAD//HwAADAA' + + '////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAA/////4AAAAAAAB////4fwAAAAAAAAAAAP///4AAAAG' + + 'AAAAAAAAAAAAAAAAAAf////4AAAAAAAB////gPwAAAAAAAAAAAf///4AAAAGAAAAAAAAAAAAAAAAAAf////4AAAAAAAA////APwAAAAAAAAAAB////+AAAAA' + + 'AAAAAAAAAAAAAAAAAAf////wAAAAAAAAf//+AfgAAAAAAAAAAP////+AAMAAAAAAAAAAAAAAAAAAAAf////wAAAAAAAAf///AfAAAAAAAAAAA//////AAOAA' + + 'AAAAAAAAAAAAAAAAAAf////gAAAAAAAAP///AfAAAAAAAAAAB//////gADAAAAAAAAAAAAAAAAAAAAf///8AAAAAAAAAP///AfAAAAAAAAAAB//////gAAAA' + + 'AAAAAAAAAAAAAAAAAAf///wAAAAAAAAAP///AfAAAAAAAAAAB//////4AAAAAAAAAAAAAAAAAAAAAAf///AAAAAAAAAAP//8AOAAAAAAAAAAB//////4AAAA' + + 'AAAAAAAAAAAAAAAAAAf///AAAAAAAAAAP//4AAAAAAAAAAAAB//////8AAAAAAAAAAAAAAAAAAAAAAf///AAAAAAAAAAH//4AAAAAAAAAAAAA//////8AAAA' + + 'AAAAAAAAAAAAAAAAAA////AAAAAAAAAAD//4AAAAAAAAAAAAA//////8AAAAAAAAAAAAAAAAAAAAAA///+AAAAAAAAAAB//wAAAAAAAAAAAAAf/////8AAAA' + + 'AAAAAAAAAAAAAAAAAA///8AAAAAAAAAAB//gAAAAAAAAAAAAA//////8AAAAAAAAAAAAAAAAAAAAAA///4AAAAAAAAAAA//gAAAAAAAAAAAAAf/////4AAAA' + + 'AAAAAAAAAAAAAAAAAA///wAAAAAAAAAAB/+AAAAAAAAAAAAAAf/w///4AAAAAAAAAAAAAAAAAAAAAA///wAAAAAAAAAAA/8AAAAAAAAAAAAAAf+AP//wAAAA' + + 'AAAAAAAAAAAAAAAAAA///gAAAAAAAAAAA8AAAAAAAAAAAAAAAfIAH//wAADAAAAAAAAAAAAAAAAAAB//4AAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAD//gAABg' + + 'AAAAAAAAAAAAAAAAAB//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/gAABwAAAAAAAAAAAAAAAAAD//4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/AAAA+' + + 'AAAAAAAAAAAAAAAAAD//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/8AAD/AAAAAAAAAAAAAAAAAP//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//gAf/w' + + 'AAAAAAAAAAAAAAAAAP//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//wAf/wAAAAAAAAAAAAAAAAAP//4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//gAP/w' + + 'AAAAAAAAAAAAAAAAAP//4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//gAP/wAAAAAAAAAAAAAAAAAP//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//wAP/w' + + 'AAAAAAAAAAAAAAAAAP//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//wAP/wAAAAAAAAAAAAAAAAAP//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//gAP/g' + + 'AAAAAAAAAAAAAAAAAP//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//AAP/AAAAAAAAAAAAAAAAAAP/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf+AAAfwA' + + 'AAAAAAAAAAAAAAAAAP/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfwAAB+AAAAAAAAAAAAAAAAAAP/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAAAP4A' + + 'AAAAAAAAAAAAAAAAAP+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAAAfwAAAAAAAAAAAAAAAAAAf/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAAAfwAA' + + 'AAAAAAAAAAAAAAAAf/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAfwAAAAAAAAAAAAAAAAAAf/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAf4AA' + + 'AAAAAAAAAAAAAAAAf/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAf+AAAAAAAAAAAAAAAAAAf/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAP+AA' + + 'AAAAAAAAAAAAAAAAf/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAABP+AAAAAAAAAAAAAAAAAAf/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAfP+AA' + + 'AAAAAAAAAAAAAAAAf+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAB8f+AAAAAAAAAAAAAAAAAAf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAB8f/AAA' + + 'AAAAAAAAAAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAB8f/AAAAAAAAAAAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAA8f/AA' + + 'AAAAAAAAAAAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAA8f/AAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAA4f/AA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAA4f/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAMf/AA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAIf/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wAP//AA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHP8AD//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE//gAAf/4' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'; + +let decodedLandmask: Uint8Array | null = null; + +function getLandmask(): Uint8Array { + if (decodedLandmask) return decodedLandmask; + const binaryString = atob(EARTH_LANDMASK_B64); + const len = binaryString.length; + const bytes = new Uint8Array(len); + for (let i = 0; i < len; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + decodedLandmask = bytes; + return decodedLandmask; +} + +function isLand(lat: number, lon: number): boolean { + while (lon < -180) lon += 360; + while (lon >= 180) lon -= 360; + + const x = Math.min(359, Math.max(0, Math.floor(lon + 180))); + const y = Math.min(179, Math.max(0, Math.floor(90 - lat))); + + const bitIndex = y * 360 + x; + const byteIndex = bitIndex >> 3; + const bitOffset = 7 - (bitIndex & 7); + + const mask = getLandmask(); + return (mask[byteIndex] & (1 << bitOffset)) !== 0; +} + +function getEarthFeature(lat: number, lon: number): { type: string; color: number } { + while (lon > 180) lon -= 360; + while (lon < -180) lon += 360; + + // Polar Ice Caps (Antarctica, Greenland interior, Arctic pack ice) + if (lat <= -60) { + return { type: 'ice', color: VOXEL_COLORS.white }; // Antarctica + } + if (lat >= 60 && lat <= 84 && lon >= -55 && lon <= -20) { + return { type: 'ice', color: VOXEL_COLORS.white }; // Greenland + } + if (lat >= 75) { + return { type: 'ice', color: VOXEL_COLORS.white }; // Arctic Ice + } + + // Check authentic Natural Earth landmask + const land = isLand(lat, lon); + + if (land) { + // 1. Snowy Mountain Peaks (Himalayas & Tibetan Plateau) + if (lat >= 27 && lat <= 36 && lon >= 75 && lon <= 98) { + return { type: 'mountain_snow', color: VOXEL_COLORS.white }; + } + + // 2. High Alpine & Mountain Ranges (Andes, Rockies, Alps) + if (lat >= -45 && lat <= 5 && lon >= -76 && lon <= -68) { + return { type: 'mountain', color: VOXEL_COLORS.darkStoneGrey }; // Andes + } + if (lat >= 35 && lat <= 58 && lon >= -120 && lon <= -108) { + return { type: 'mountain', color: VOXEL_COLORS.darkStoneGrey }; // Rockies + } + if (lat >= 44 && lat <= 48 && lon >= 6 && lon <= 15) { + return { type: 'mountain', color: VOXEL_COLORS.darkStoneGrey }; // Alps + } + + // 3. Major Deserts (Sand Yellow) + // Sahara Desert + if (lat >= 17 && lat <= 29 && lon >= -13 && lon <= 34) { + return { type: 'desert', color: VOXEL_COLORS.brickYellow }; + } + // Arabian Peninsula + if (lat >= 15 && lat <= 30 && lon >= 40 && lon <= 56) { + return { type: 'desert', color: VOXEL_COLORS.brickYellow }; + } + // Australian Outback + if (lat <= -20 && lat >= -30 && lon >= 120 && lon <= 136) { + return { type: 'desert', color: VOXEL_COLORS.brickYellow }; + } + // Gobi Desert + if (lat >= 39 && lat <= 45 && lon >= 88 && lon <= 106) { + return { type: 'desert', color: VOXEL_COLORS.brickYellow }; + } + + // 4. Dense Tropical Rainforests (Amazon, Congo) + if (lat >= -10 && lat <= 4 && lon >= -72 && lon <= -50) { + return { type: 'rainforest', color: VOXEL_COLORS.darkGreen }; // Amazon + } + if (lat >= -4 && lat <= 4 && lon >= 14 && lon <= 26) { + return { type: 'rainforest', color: VOXEL_COLORS.darkGreen }; // Congo + } + + // 5. Default Continents / Grasslands (Emerald Green) + return { type: 'land', color: VOXEL_COLORS.brightGreen }; + } + + // Ocean + // Coastal shallow reefs / Caribbean / Coral Sea / Indo-Pacific + if (Math.abs(lat) < 24 && ((lon > -90 && lon < -62) || (lon > 108 && lon < 154))) { + return { type: 'reef', color: VOXEL_COLORS.mediumBlue }; + } + return { type: 'ocean', color: VOXEL_COLORS.brightBlue }; +} + +const THREE_LOCAL_URL = '/js/three.min.js'; +const THREE_CDN_URL = 'https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js'; + +function injectScript(src: string): Promise { + return new Promise((resolve) => { + const existing = document.querySelector(`script[src="${src}"]`); + if (existing) { + if (typeof THREE !== 'undefined') { + resolve(true); + } else { + existing.addEventListener('load', () => resolve(true), { once: true }); + existing.addEventListener('error', () => resolve(false), { once: true }); + } + return; + } + const script = document.createElement('script'); + script.src = src; + script.async = true; + script.onload = () => resolve(true); + script.onerror = () => resolve(false); + document.head.appendChild(script); + }); +} + +async function loadThreeScript(): Promise { + if (typeof THREE !== 'undefined') return; + + // 1. Try local self-hosted Three.js first (instant, zero third-party CDN failure, works offline) + await injectScript(THREE_LOCAL_URL); + if (typeof THREE !== 'undefined') return; + + // 2. Fallback to Cloudflare CDN if local script fails + await injectScript(THREE_CDN_URL); +} + +/** + * Probes WebGL 3D context availability before loading Three.js. + * + * In headless CI containers (Chromium with `--disable-gpu`) or restricted environments, + * the 2D `` element exists and `window.WebGLRenderingContext` is defined, but + * calling `getContext('webgl')` fails or returns null. Probing avoids downloading the + * 121 KB Three.js bundle and prevents `THREE.WebGLRenderer: Error creating WebGL context` + * console errors. + * + * We probe on a throwaway in-memory canvas rather than `#digitalEarthCanvas` so we don't + * bind default context attributes before Three.js initializes with its custom options + * (`alpha`, `antialias`, and `high-performance`). + */ +function isWebGLAvailable(): boolean { + try { + const testCanvas = document.createElement('canvas'); + return !!( + window.WebGLRenderingContext && + (testCanvas.getContext('webgl') || testCanvas.getContext('experimental-webgl')) + ); + } catch { + return false; + } +} + +export async function initDigitalEarth(): Promise { + const container = document.getElementById('heroEarthBackdrop'); + const canvas = document.getElementById('digitalEarthCanvas') as HTMLCanvasElement | null; + + if (!container || !canvas) { + return; + } + + // Abort if browser or container environment has no WebGL support (e.g. headless CI with --disable-gpu) + if (!isWebGLAvailable()) { + return; + } + + // Load Three.js dynamically on-demand if not already loaded + if (typeof THREE === 'undefined') { + await loadThreeScript(); + } + + if (typeof THREE === 'undefined') { + return; + } + + // Scene setup + const scene = new THREE.Scene(); + + // Camera looking directly at the Earth (responsive distance for mobile vs desktop) + const isMobileInitial = container.clientWidth < 768; + const camera = new THREE.PerspectiveCamera( + 38, + container.clientWidth / container.clientHeight, + 0.1, + 100, + ); + camera.position.set(0, 0, isMobileInitial ? 18.0 : 15.5); + + // WebGL Renderer with filmic tone mapping & color management + let renderer: any; + try { + renderer = new THREE.WebGLRenderer({ + canvas, + alpha: true, + antialias: true, + powerPreference: 'high-performance', + }); + } catch { + // Graceful fallback if WebGL context creation fails + return; + } + if (!renderer) return; + + renderer.setSize(container.clientWidth, container.clientHeight); + renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); + renderer.toneMapping = THREE.ACESFilmicToneMapping; + renderer.toneMappingExposure = 1.05; + + // Master Earth Tilt Group (holds Earth's real 23.4° axial tilt) + const baseTiltZ = THREE.MathUtils.degToRad(-23.4); + const baseTiltX = THREE.MathUtils.degToRad(8); + + const tiltGroup = new THREE.Group(); + tiltGroup.rotation.z = baseTiltZ; + tiltGroup.rotation.x = baseTiltX; + scene.add(tiltGroup); + + // Inner Planet Spin Group (rotates around the tilted polar axis) + // Initial angle of 75° frames North and South America centered on page load + const planetSpinGroup = new THREE.Group(); + let planetBaseSpin = THREE.MathUtils.degToRad(75); + planetSpinGroup.rotation.y = planetBaseSpin; + tiltGroup.add(planetSpinGroup); + + // 3D Cartesian Voxel Sphere Parameters (Adaptive density for smooth 60fps on all devices) + // ONLY CUBES: Zero spheres, zero circular geometry anywhere. + const gridRadius = isMobileInitial ? 13.5 : 19.2; + const voxelSize = isMobileInitial ? 0.38 : 0.267; + + const surfaceVoxels: VoxelPart[] = []; + const gridRadiusSq = gridRadius * gridRadius; + + // Generate the discrete 3D Cartesian Voxel Sphere (Strictly Cubic Parts) + // Highly optimized 6-cardinal surface extraction avoiding redundant square roots + const cr = Math.ceil(gridRadius); + for (let x = -cr; x <= cr; x++) { + const x2 = x * x; + const xp1_2 = (x + 1) * (x + 1); + const xm1_2 = (x - 1) * (x - 1); + + for (let y = -cr; y <= cr; y++) { + const y2 = y * y; + const yp1_2 = (y + 1) * (y + 1); + const ym1_2 = (y - 1) * (y - 1); + const xy2 = x2 + y2; + + for (let z = -cr; z <= cr; z++) { + const z2 = z * z; + const distSq = xy2 + z2; + + if (distSq <= gridRadiusSq) { + const isSurface = + x2 + yp1_2 + z2 > gridRadiusSq || + xp1_2 + y2 + z2 > gridRadiusSq || + xm1_2 + y2 + z2 > gridRadiusSq || + x2 + ym1_2 + z2 > gridRadiusSq || + xy2 + (z + 1) * (z + 1) > gridRadiusSq || + xy2 + (z - 1) * (z - 1) > gridRadiusSq; + + if (isSurface) { + const dist = Math.sqrt(distSq); + const normX = x / dist; + const normY = y / dist; + const normZ = z / dist; + + const lat = Math.asin(normY) * (180 / Math.PI); + const lon = Math.atan2(normX, normZ) * (180 / Math.PI); + + const feature = getEarthFeature(lat, lon); + + surfaceVoxels.push({ + x: x * voxelSize, + y: y * voxelSize, + z: z * voxelSize, + color: feature.color, + }); + } + } + } + } + } + + // Directional Sun Vector in World Space (Illuminates the visible front-left hemisphere) + const sunDirection = new THREE.Vector3(-0.6, 0.7, 1.3).normalize(); + const sunUniform = { value: sunDirection }; + + // Voxel Smooth Surface Material with Celestial Day/Night Shading + // Calibrated roughness (0.35) for crisp cubic facets and glossy sheen + const voxelMaterial = new THREE.MeshStandardMaterial({ + roughness: 0.35, + metalness: 0.0, + envMapIntensity: 0.75, + }); + + voxelMaterial.customProgramCacheKey = () => 'digital-earth-day-night-v1'; + + voxelMaterial.onBeforeCompile = (shader: any) => { + shader.uniforms.uSunDirection = sunUniform; + + // Inject globe normal varying + shader.vertexShader = ` + varying vec3 vGlobeNormal; + ${shader.vertexShader} + `; + + shader.vertexShader = shader.vertexShader.replace( + '#include ', + ` + #include + #ifdef USE_INSTANCING + // Radial direction of the voxel from the planet center in world coordinates + vec4 instanceCenter = vec4(instanceMatrix[3].xyz, 0.0); + vGlobeNormal = normalize((modelMatrix * instanceCenter).xyz); + #else + vGlobeNormal = normalize((modelMatrix * vec4(position, 0.0)).xyz); + #endif + `, + ); + + shader.fragmentShader = ` + uniform vec3 uSunDirection; + varying vec3 vGlobeNormal; + ${shader.fragmentShader} + `; + + shader.fragmentShader = shader.fragmentShader.replace( + '#include ', + ` + #include + + // Celestial illumination angle (dot product with sun direction in world space) + vec3 sunDir = normalize(uSunDirection); + float globeSunDot = dot(vGlobeNormal, sunDir); + + // Day factor: 1.0 on day side, 0.0 on dark night side + // Smooth terminator transition across the twilight band + float dayFactor = smoothstep(-0.25, 0.25, globeSunDot); + + // Cut off direct sunlight on the dark night side + reflectedLight.directDiffuse *= dayFactor; + reflectedLight.directSpecular *= dayFactor; + + // Night side ambient tint: luminous sapphire/slate glow so continents remain visible + vec3 nightAmbientTint = vec3(0.28, 0.40, 0.62); + reflectedLight.indirectDiffuse = mix(reflectedLight.indirectDiffuse * nightAmbientTint * 2.2, reflectedLight.indirectDiffuse, dayFactor); + + // Warm golden atmospheric sunset / sunrise rim along the terminator + float sunsetGlow = smoothstep(-0.20, 0.05, globeSunDot) * (1.0 - smoothstep(0.05, 0.30, globeSunDot)); + vec3 sunsetColor = vec3(1.0, 0.55, 0.20); // Golden-orange sunset/sunrise + reflectedLight.directDiffuse += sunsetColor * (sunsetGlow * 0.55); + `, + ); + }; + + // Pure Discrete Cube Geometry: BoxGeometry ONLY, zero curves/spheres + const blockGeom = new THREE.BoxGeometry(voxelSize, voxelSize, voxelSize); + + // Instanced Mesh for Cubic Voxel Blocks (~3,710 instances) + const voxelMesh = new THREE.InstancedMesh(blockGeom, voxelMaterial, surfaceVoxels.length); + + const matrix = new THREE.Matrix4(); + const color = new THREE.Color(); + const position = new THREE.Vector3(); + + // Populate Cubic Voxel Blocks + for (let i = 0; i < surfaceVoxels.length; i++) { + const v = surfaceVoxels[i]; + position.set(v.x, v.y, v.z); + matrix.setPosition(position); + voxelMesh.setMatrixAt(i, matrix); + + color.setHex(v.color); + voxelMesh.setColorAt(i, color); + } + + voxelMesh.instanceMatrix.needsUpdate = true; + if (voxelMesh.instanceColor) voxelMesh.instanceColor.needsUpdate = true; + + planetSpinGroup.add(voxelMesh); + + // Natural Planetary Lighting: Single dominant Sun + Deep Space Ambient + Dark Rim + const mainSun = new THREE.DirectionalLight(0xfffbe8, 2.2); + mainSun.position.copy(sunDirection).multiplyScalar(28); // Matches the sunDirection vector + scene.add(mainSun); + + // Subtle cosmic rim light from the dark back side for atmospheric limb definition + const cosmicRim = new THREE.DirectionalLight(0x38bdf8, 0.65); + cosmicRim.position.set(20, -10, -12); + scene.add(cosmicRim); + + // Deep space ambient light: provides subtle illumination for the night side + const ambientLight = new THREE.AmbientLight(0x1e293b, 0.85); + scene.add(ambientLight); + + // Interactive Cursor Parallax & Tilt Setup + let mouseTargetX = 0; // Normalized -1 to +1 + let mouseTargetY = 0; // Normalized -1 to +1 + let currentMouseX = 0; + let currentMouseY = 0; + + function onMouseMove(e: MouseEvent): void { + mouseTargetX = (e.clientX / window.innerWidth) * 2 - 1; + mouseTargetY = -(e.clientY / window.innerHeight) * 2 + 1; + } + + function onTouchMove(e: TouchEvent): void { + if (e.touches.length > 0) { + const touch = e.touches[0]; + mouseTargetX = (touch.clientX / window.innerWidth) * 2 - 1; + mouseTargetY = -(touch.clientY / window.innerHeight) * 2 + 1; + } + } + + function onMouseLeave(): void { + mouseTargetX = 0; + mouseTargetY = 0; + } + + window.addEventListener('mousemove', onMouseMove, { passive: true }); + window.addEventListener('touchmove', onTouchMove, { passive: true }); + document.addEventListener('mouseleave', onMouseLeave, { passive: true }); + + // Celestial Elliptic Orbital Flight Mechanics + /** + * Computes the 3D position along an authentic Keplerian/elliptical orbital path. + * progress = -1.0 -> Flying in from the distance, bottom-left corner + * progress = 0.0 -> Perigee (closest approach) centered in the middle of the screen + * progress = +1.0 -> Continues along the elliptical path down, and moves outside the screen + */ + function getEllipticOrbitPosition(progress: number): { x: number; y: number; z: number } { + const aspect = camera.aspect; + const tanHalfFov = Math.tan(THREE.MathUtils.degToRad(camera.fov / 2)); + const camZ = camera.position.z; + + // 1. Perspective Depth (Z-axis): + // Ingress: Starts deep in the cosmic distance (z = -22.0) and zooms forward to perigee (z = 0) + // Egress: Gently recedes as Earth plunges downwards (z = -16.0) + let z: number; + if (progress <= 0) { + const t = -progress; // 1.0 at entry -> 0.0 at perigee + z = -22.0 * Math.pow(t, 1.25); + } else { + const t = Math.min(progress, 1.4); + z = -16.0 * Math.pow(t, 1.35); + } + + // View frustum boundaries at depth z + const distAtZ = camZ - z; + const halfH = distAtZ * tanHalfFov; + const halfW = halfH * aspect; + + // 2. Parametric elliptical trajectory: + // Apex at progress = 0: u = 0, v = 0 with smooth zero vertical derivative (horizontal tangent) + let x: number; + let y: number; + + // Subtle celestial inclination tilt (-5.5°) + const orbitTilt = THREE.MathUtils.degToRad(-5.5); + const cosTilt = Math.cos(orbitTilt); + const sinTilt = Math.sin(orbitTilt); + + if (progress <= 0) { + // Ingress: fly in from the distance, bottom-left corner (-W, -H) to center (0, 0) + const angle = progress * (Math.PI * 0.5); // -PI/2 -> 0 + const sinA = Math.sin(angle); // -1 -> 0 + const cosA = Math.cos(angle); // 0 -> 1 + + const reachW = halfW * 0.9; + const reachH = halfH * 0.86; + + const u = reachW * sinA; + const v = reachH * (cosA - 1.0); // -reachH at p = -1, 0 at p = 0 + + x = u * cosTilt - v * sinTilt; + y = u * sinTilt + v * cosTilt; + } else { + // Egress: when user moves down, Earth continues on its path DOWN and moves outside the screen + const angle = Math.min(progress, 1.4) * (Math.PI * 0.5); // 0 -> PI/2 + const sinA = Math.sin(angle); // 0 -> 1 + const cosA = Math.cos(angle); // 1 -> 0 + + const reachW = halfW * 0.95; + // Earth radius is ~5.13 units. To be fully outside bottom, top of Earth (y + 5.13) < -halfH. + const reachH = halfH + 9.0; + + const u = reachW * sinA; + const v = -reachH * (1.0 - cosA); // 0 at p = 0, -reachH at p = 1 + + x = u * cosTilt - v * sinTilt; + y = u * sinTilt + v * cosTilt; + } + + return { x, y, z }; + } + + // Animation Loop: Elliptic Orbit (Bottom-Left Distance Ingress -> Center Perigee -> Downward Egress) + const clock = new THREE.Clock(); + let isVisible = true; + const introDuration = 2.4; // seconds for arrival fly-in from distance + + // If loaded while already scrolled past hero, skip intro + const initialScrollY = typeof window !== 'undefined' ? window.scrollY : 0; + let introCompleted = initialScrollY > 400; + + let currentScrollY = initialScrollY; + const scrollThreshold = 750.0; + const maxTrackedScroll = scrollThreshold * 1.35; + + const heroSection = document.querySelector('.hero-section') || container; + if ('IntersectionObserver' in window && heroSection) { + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + isVisible = + entry.isIntersecting || (typeof window !== 'undefined' && window.scrollY < 600); + if (!isVisible) { + canvas.style.opacity = '0'; + canvas.style.visibility = 'hidden'; + if (container) container.style.visibility = 'hidden'; + } else { + canvas.style.visibility = 'visible'; + if (container) container.style.visibility = 'visible'; + } + }); + }, + { threshold: 0.02 }, + ); + observer.observe(heroSection); + } + + const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)'); + let motionScale = mediaQuery.matches ? 0.2 : 1.0; + mediaQuery.addEventListener('change', (e) => { + motionScale = e.matches ? 0.2 : 1.0; + }); + + function animate(): void { + requestAnimationFrame(animate); + + if (!isVisible) { + if (canvas.style.visibility !== 'hidden') { + canvas.style.opacity = '0'; + canvas.style.visibility = 'hidden'; + if (container) container.style.visibility = 'hidden'; + } + return; + } + + const delta = clock.getDelta(); + const elapsed = clock.getElapsedTime(); + + // 1. Continuous slow planetary rotation around tilted axis + planetBaseSpin += delta * 0.08 * motionScale; + + // Smooth spring dampening (lerp) toward cursor position + currentMouseX = THREE.MathUtils.lerp(currentMouseX, mouseTargetX, 0.05); + currentMouseY = THREE.MathUtils.lerp(currentMouseY, mouseTargetY, 0.05); + + // 2. Interactive Cursor Parallax & Tilt + const cursorTiltX = -currentMouseY * 0.22 * motionScale; + const cursorTiltY = currentMouseX * 0.32 * motionScale; + const cursorRollZ = currentMouseX * 0.08 * motionScale; + + // 3. Compute Elliptic Flight Path Progress + // Phase 1: Intro fly-in from the distance, bottom-left corner (-1.0) into center (0.0) + let pIntro = 0.0; + let introEase = 1.0; + if (!introCompleted) { + const rawIntro = motionScale < 0.5 ? 1.0 : Math.min(elapsed / introDuration, 1.0); + introEase = 1.0 - Math.pow(1.0 - rawIntro, 3.2); // Smooth cubic ease-out + pIntro = -1.0 + introEase * 1.0; // Sweeps from -1.0 to 0.0 + if (rawIntro >= 1.0) { + introCompleted = true; + } + } + + // Phase 2: Scroll-driven continuation along the ellipse downwards (+1.0) + const clampedTargetScroll = Math.min(window.scrollY, maxTrackedScroll); + currentScrollY = THREE.MathUtils.lerp(currentScrollY, clampedTargetScroll, 0.08); + const scrollT = Math.min(currentScrollY / scrollThreshold, 1.25); + const scrollEase = scrollT * (2.0 - Math.min(scrollT, 1.0)); // Smooth quad ease + const pScroll = scrollEase * 1.0; // Sweeps from 0.0 to +1.0 (and beyond) + + // Combined orbital progress: sweeps continuously from -1.0 (bottom-left) -> 0.0 (center) -> +1.0 (exit down) + const totalProgress = Math.min(Math.max(pIntro + pScroll, -1.0), 1.35); + + // Calculate position along the celestial ellipse + const orbitPos = getEllipticOrbitPosition(totalProgress); + + // Dynamic Orbital Banking: Banks into the curvature of the ellipse + const orbitalBanking = -Math.sin(totalProgress * Math.PI * 0.5) * 0.16 * motionScale; + + tiltGroup.rotation.x = baseTiltX + cursorTiltX; + tiltGroup.rotation.z = baseTiltZ + cursorRollZ + orbitalBanking; + + planetSpinGroup.rotation.y = planetBaseSpin + cursorTiltY; + + // Subtle weightless cosmic float (most active when near center perigee) + const centerFactor = Math.max(0, 1.0 - Math.abs(totalProgress) * 1.5) * introEase; + const floatY = Math.sin(elapsed * 0.75) * 0.12 * centerFactor * motionScale; + const driftX = Math.sin(elapsed * 0.4) * 0.08 * centerFactor * motionScale; + + // Apply unconstrained position beyond container boundaries across full viewport + tiltGroup.position.set(orbitPos.x + driftX, orbitPos.y + floatY, orbitPos.z); + + // Smoothly fade and hide canvas as Earth flies completely off the bottom edge + if (scrollT > 0.7) { + const fadeProgress = (scrollT - 0.7) / 0.3; + const opacity = Math.max(0, 1.0 - fadeProgress); + canvas.style.opacity = opacity.toFixed(3); + if (opacity <= 0.01) { + canvas.style.visibility = 'hidden'; + } else { + canvas.style.visibility = 'visible'; + } + } else { + canvas.style.opacity = '1.0'; + canvas.style.visibility = 'visible'; + } + + renderer.render(scene, camera); + } + + // Start continuous interactive animation + animate(); + canvas.classList.add('loaded'); + + // Responsive resize handler + function handleResize(): void { + if (!container) return; + const width = container.clientWidth; + const height = container.clientHeight; + camera.aspect = width / height; + const isMobileNow = width < 768; + camera.position.z = isMobileNow ? 18.0 : 15.5; + camera.updateProjectionMatrix(); + renderer.setSize(width, height); + renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); + } + + window.addEventListener('resize', handleResize); +} diff --git a/assets/ts/install-tabs.ts b/assets/ts/install-tabs.ts new file mode 100644 index 0000000..3eefc1e --- /dev/null +++ b/assets/ts/install-tabs.ts @@ -0,0 +1,27 @@ +/** + * Hero Install Quick Selector + */ + +export function initInstallTabs(): void { + const tabs = document.querySelectorAll('.install-tab-btn'); + const snippetEl = document.getElementById('installCodeSnippet'); + + const installCommands: Record = { + brew: 'brew install earthbuild/tap/earth', + curl: 'curl -fsSL https://www.earthbuild.dev/install.sh | sh', + windows: 'curl -fsSL https://www.earthbuild.dev/install.sh | sh', + nix: 'nix-shell -p earthbuild', + }; + + tabs.forEach((tab) => { + tab.addEventListener('click', () => { + tabs.forEach((t) => t.classList.remove('active')); + tab.classList.add('active'); + + const type = tab.getAttribute('data-install-type'); + if (snippetEl && type && installCommands[type]) { + snippetEl.innerText = installCommands[type]; + } + }); + }); +} diff --git a/assets/ts/main.ts b/assets/ts/main.ts new file mode 100644 index 0000000..e39d159 --- /dev/null +++ b/assets/ts/main.ts @@ -0,0 +1,77 @@ +/** + * EarthBuild Main Application Entrypoint + * Compiled via Hugo Pipes & ESBuild natively with Strict TypeScript + */ + +import { initNavbar, initSmoothScroll } from './nav'; +import { initClipboard } from './clipboard'; +import { initInstallTabs } from './install-tabs'; +import { TripleExecutionRaceEngine } from './race-runner'; +import { initCodeExplorer } from './accordion'; +import { initRoiCalculator } from './roi'; +import { initComparisonFilters, initMigrationTabs } from './comparison'; +import { initDigitalEarth } from './digital-earth'; +import { initHeadingAnchorLinks } from './anchor-links'; + +function scheduleDigitalEarth(): void { + const canvas = document.getElementById('digitalEarthCanvas'); + const backdrop = document.getElementById('heroEarthBackdrop'); + if (!canvas) return; + + // If user opens a deep link with a hash (e.g. #calculator, #features, #comparison), + // disable Earth animation so it never intrudes on deep-linked sections or consumes GPU. + const hasHash = Boolean( + typeof window !== 'undefined' && + window.location.hash && + window.location.hash !== '#' && + window.location.hash !== '#hero', + ); + + let initialized = false; + const startEarth = () => { + if (initialized) return; + initialized = true; + if (backdrop) backdrop.style.display = ''; + canvas.style.display = ''; + initDigitalEarth(); + }; + + if (hasHash) { + if (backdrop) backdrop.style.display = 'none'; + canvas.style.display = 'none'; + + // Only activate if the user explicitly scrolls back up into the hero section + const onScroll = () => { + if (window.scrollY < 200) { + window.removeEventListener('scroll', onScroll); + startEarth(); + } + }; + window.addEventListener('scroll', onScroll, { passive: true }); + return; + } + + startEarth(); +} + +function runInit(): void { + initNavbar(); + initClipboard(); + initHeadingAnchorLinks(); + initInstallTabs(); + scheduleDigitalEarth(); + + const raceEngine = new TripleExecutionRaceEngine(); + initCodeExplorer(raceEngine); + + initRoiCalculator(); + initComparisonFilters(); + initMigrationTabs(); + initSmoothScroll(); +} + +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', runInit); +} else { + runInit(); +} diff --git a/assets/ts/migration-data.ts b/assets/ts/migration-data.ts new file mode 100644 index 0000000..a8bd185 --- /dev/null +++ b/assets/ts/migration-data.ts @@ -0,0 +1,2955 @@ +import { SupportedLanguage } from './types'; + +export interface MigrationFile { + name: string; + code: string; +} + +export interface MigrationToolSample { + legacyTitle: string; + legacyCode: string; + legacyFiles?: MigrationFile[]; + earthfileTitle: string; + earthfileCode: string; +} + +export type MigrationTools = 'dockerfile' | 'bake' | 'makefile' | 'dagger' | 'gha'; + +export type MigrationData = Record>; + +export const migrationData: MigrationData = { + python: { + dockerfile: { + legacyTitle: 'Standard Dockerfile', + legacyCode: `FROM python:3.14-slim AS deps +WORKDIR /app +COPY requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt pyinstaller + +FROM deps AS test +COPY src tests ./ ./ +RUN pytest tests/ + +FROM deps AS builder +COPY src tests ./ ./ +RUN pyinstaller --onefile --distpath dist -n app src/main.py + +FROM python:3.14-slim AS runtime +COPY --from=builder /app/dist/app /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`, + earthfileTitle: 'Earthfile (Direct Host Artifacts & Push Built-In)', + earthfileCode: `VERSION 0.8 +FROM python:3.14-slim +WORKDIR /app + +deps: + COPY requirements.txt ./ + RUN pip install --no-cache-dir -r requirements.txt pyinstaller + +test: + FROM +deps + COPY . ./ + RUN pytest tests/ + +build: + FROM +deps + COPY . ./ + RUN pyinstaller --onefile --distpath bin -n app src/main.py + SAVE ARTIFACT bin/app AS LOCAL bin/app + +docker: + FROM python:3.14-slim + WORKDIR /app + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + bake: { + legacyTitle: 'Docker Buildx Bake', + legacyCode: `group "default" { + targets = ["test", "build", "image"] +} + +target "test" { + dockerfile = "Dockerfile" + target = "test" +} + +target "build" { + dockerfile = "Dockerfile" + target = "builder" + output = ["type=local,dest=bin"] +} + +target "image" { + dockerfile = "Dockerfile" + target = "runtime" + tags = ["docker.io/myorg/myapp:latest"] + push = true +}`, + legacyFiles: [ + { + name: 'docker-bake.hcl', + code: `group "default" { + targets = ["test", "build", "image"] +} + +target "test" { + dockerfile = "Dockerfile" + target = "test" +} + +target "build" { + dockerfile = "Dockerfile" + target = "builder" + output = ["type=local,dest=bin"] +} + +target "image" { + dockerfile = "Dockerfile" + target = "runtime" + tags = ["docker.io/myorg/myapp:latest"] + push = true +}`, + }, + { + name: 'Dockerfile', + code: `FROM python:3.14-slim AS deps +WORKDIR /app +COPY requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt pyinstaller + +FROM deps AS test +COPY src tests ./ ./ +RUN pytest tests/ + +FROM deps AS builder +COPY src tests ./ ./ +RUN pyinstaller --onefile --distpath dist -n app src/main.py + +FROM python:3.14-slim AS runtime +COPY --from=builder /app/dist/app /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`, + }, + ], + earthfileTitle: 'Earthfile (Self-Contained & Deterministic)', + earthfileCode: `VERSION 0.8 +FROM python:3.14-slim +WORKDIR /app + +deps: + COPY requirements.txt ./ + RUN pip install --no-cache-dir -r requirements.txt pyinstaller + +test: + FROM +deps + COPY . ./ + RUN pytest tests/ + +build: + FROM +deps + COPY . ./ + RUN pyinstaller --onefile --distpath bin -n app src/main.py + SAVE ARTIFACT bin/app AS LOCAL bin/app + +docker: + FROM python:3.14-slim + WORKDIR /app + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + makefile: { + legacyTitle: 'Makefile (Python Host Execution)', + legacyCode: `.PHONY: all test build docker + +all: test build docker + +test: + pytest tests/ + +build: + pyinstaller --onefile --distpath dist -n app src/main.py + +docker: build + docker build -t docker.io/myorg/myapp:latest . + docker push docker.io/myorg/myapp:latest`, + legacyFiles: [ + { + name: 'Makefile', + code: `.PHONY: all test build docker + +all: test build docker + +test: + pytest tests/ + +build: + pyinstaller --onefile --distpath dist -n app src/main.py + +docker: build + docker build -t docker.io/myorg/myapp:latest . + docker push docker.io/myorg/myapp:latest`, + }, + { + name: 'Dockerfile', + code: `FROM python:3.14-slim AS deps +WORKDIR /app +COPY requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt pyinstaller + +FROM deps AS test +COPY src tests ./ ./ +RUN pytest tests/ + +FROM deps AS builder +COPY src tests ./ ./ +RUN pyinstaller --onefile --distpath dist -n app src/main.py + +FROM python:3.14-slim AS runtime +COPY --from=builder /app/dist/app /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`, + }, + ], + earthfileTitle: 'Earthfile (Hermetic & Parallel)', + earthfileCode: `VERSION 0.8 +FROM python:3.14-slim +WORKDIR /app + +deps: + COPY requirements.txt ./ + RUN pip install --no-cache-dir -r requirements.txt pyinstaller + +test: + FROM +deps + COPY . ./ + RUN pytest tests/ + +build: + FROM +deps + COPY . ./ + RUN pyinstaller --onefile --distpath bin -n app src/main.py + SAVE ARTIFACT bin/app AS LOCAL bin/app + +docker: + FROM python:3.14-slim + WORKDIR /app + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + dagger: { + legacyTitle: 'Dagger (Python SDK)', + legacyCode: `import dagger +from dagger import dag, function, object_type + +@object_type +class Pipeline: + @function + async def test(self, src: dagger.Directory) -> str: + return await ( + dag.container() + .from_("python:3.14-slim") + .with_directory("/app", src) + .with_workdir("/app") + .with_exec(["pip", "install", "-r", "requirements.txt"]) + .with_exec(["pytest", "tests/"]) + .stdout() + ) + + @function + async def build(self, src: dagger.Directory) -> dagger.File: + bin_file = ( + dag.container() + .from_("python:3.14-slim") + .with_directory("/app", src) + .with_workdir("/app") + .with_exec(["pip", "install", "-r", "requirements.txt", "pyinstaller"]) + .with_exec(["pyinstaller", "--onefile", "-n", "app", "src/main.py"]) + .file("dist/app") + ) + await bin_file.export("bin/app") + return bin_file + + @function + async def publish(self, src: dagger.Directory) -> str: + bin_file = await self.build(src) + return await ( + dag.container() + .from_("python:3.14-slim") + .with_file("/usr/local/bin/app", bin_file) + .with_entrypoint(["/usr/local/bin/app"]) + .publish("docker.io/myorg/myapp:latest") + )`, + earthfileTitle: 'Earthfile', + earthfileCode: `VERSION 0.8 +FROM python:3.14-slim +WORKDIR /app + +deps: + COPY requirements.txt ./ + RUN pip install --no-cache-dir -r requirements.txt pyinstaller + +test: + FROM +deps + COPY . ./ + RUN pytest tests/ + +build: + FROM +deps + COPY . ./ + RUN pyinstaller --onefile --distpath bin -n app src/main.py + SAVE ARTIFACT bin/app AS LOCAL bin/app + +docker: + FROM python:3.14-slim + WORKDIR /app + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + gha: { + legacyTitle: 'Standard GitHub Actions Workflow', + legacyCode: `name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-26.04 + steps: + - uses: actions/checkout@v4 + + - name: Run Tests + run: pytest tests/ + + - name: Build Host Binary / Package + run: pyinstaller --onefile --distpath dist -n app src/main.py + + - uses: actions/upload-artifact@v4 + with: + name: app-artifact + path: dist/app + + - uses: docker/login-action@v3 + with: + username: \${{ secrets.DOCKER_USER }} + password: \${{ secrets.DOCKER_TOKEN }} + + - uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: docker.io/myorg/myapp:latest`, + earthfileTitle: 'Clean GitHub Action with EarthBuild', + earthfileCode: `name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: docker/login-action@v3 + with: + username: \${{ secrets.DOCKER_USER }} + password: \${{ secrets.DOCKER_TOKEN }} + + - name: Setup EarthBuild + uses: earthbuild/actions-setup@v2 + + - name: Test, Build Binary & Push Image + run: earth --ci --push +all`, + }, + }, + js: { + dockerfile: { + legacyTitle: 'Standard Dockerfile', + legacyCode: `FROM node:26-alpine AS deps +WORKDIR /app +COPY package*.json ./ +RUN npm ci + +FROM deps AS test +COPY src ./src package*.json ./ +RUN npm test + +FROM deps AS builder +COPY src ./src package*.json ./ +RUN npm run build + +FROM node:26-alpine AS runtime +COPY --from=builder /app/dist/bundle.js /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`, + earthfileTitle: 'Earthfile (Direct Host Artifacts & Push Built-In)', + earthfileCode: `VERSION 0.8 +FROM node:26-alpine +WORKDIR /app + +deps: + COPY package*.json ./ + RUN npm ci + +test: + FROM +deps + COPY . ./ + RUN npm test + +build: + FROM +deps + COPY . ./ + RUN npm run build + SAVE ARTIFACT dist AS LOCAL dist + +docker: + FROM node:26-alpine + WORKDIR /app + COPY package*.json ./ + RUN npm ci --omit=dev + COPY +build/dist ./dist + CMD ["node", "dist/index.js"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + bake: { + legacyTitle: 'Docker Buildx Bake', + legacyCode: `group "default" { + targets = ["test", "build", "image"] +} + +target "test" { + dockerfile = "Dockerfile" + target = "test" +} + +target "build" { + dockerfile = "Dockerfile" + target = "builder" + output = ["type=local,dest=bin"] +} + +target "image" { + dockerfile = "Dockerfile" + target = "runtime" + tags = ["docker.io/myorg/myapp:latest"] + push = true +}`, + legacyFiles: [ + { + name: 'docker-bake.hcl', + code: `group "default" { + targets = ["test", "build", "image"] +} + +target "test" { + dockerfile = "Dockerfile" + target = "test" +} + +target "build" { + dockerfile = "Dockerfile" + target = "builder" + output = ["type=local,dest=bin"] +} + +target "image" { + dockerfile = "Dockerfile" + target = "runtime" + tags = ["docker.io/myorg/myapp:latest"] + push = true +}`, + }, + { + name: 'Dockerfile', + code: `FROM node:26-alpine AS deps +WORKDIR /app +COPY package*.json ./ +RUN npm ci + +FROM deps AS test +COPY src ./src package*.json ./ +RUN npm test + +FROM deps AS builder +COPY src ./src package*.json ./ +RUN npm run build + +FROM node:26-alpine AS runtime +COPY --from=builder /app/dist/bundle.js /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`, + }, + ], + earthfileTitle: 'Earthfile (Self-Contained & Deterministic)', + earthfileCode: `VERSION 0.8 +FROM node:26-alpine +WORKDIR /app + +deps: + COPY package*.json ./ + RUN npm ci + +test: + FROM +deps + COPY . ./ + RUN npm test + +build: + FROM +deps + COPY . ./ + RUN npm run build + SAVE ARTIFACT dist AS LOCAL dist + +docker: + FROM node:26-alpine + WORKDIR /app + COPY package*.json ./ + RUN npm ci --omit=dev + COPY +build/dist ./dist + CMD ["node", "dist/index.js"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + makefile: { + legacyTitle: 'Makefile (JavaScript Host Execution)', + legacyCode: `.PHONY: all test build docker + +all: test build docker + +test: + npm test + +build: + npm run build + +docker: build + docker build -t docker.io/myorg/myapp:latest . + docker push docker.io/myorg/myapp:latest`, + legacyFiles: [ + { + name: 'Makefile', + code: `.PHONY: all test build docker + +all: test build docker + +test: + npm test + +build: + npm run build + +docker: build + docker build -t docker.io/myorg/myapp:latest . + docker push docker.io/myorg/myapp:latest`, + }, + { + name: 'Dockerfile', + code: `FROM node:26-alpine AS deps +WORKDIR /app +COPY package*.json ./ +RUN npm ci + +FROM deps AS test +COPY src ./src package*.json ./ +RUN npm test + +FROM deps AS builder +COPY src ./src package*.json ./ +RUN npm run build + +FROM node:26-alpine AS runtime +COPY --from=builder /app/dist/bundle.js /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`, + }, + ], + earthfileTitle: 'Earthfile (Hermetic & Parallel)', + earthfileCode: `VERSION 0.8 +FROM node:26-alpine +WORKDIR /app + +deps: + COPY package*.json ./ + RUN npm ci + +test: + FROM +deps + COPY . ./ + RUN npm test + +build: + FROM +deps + COPY . ./ + RUN npm run build + SAVE ARTIFACT dist AS LOCAL dist + +docker: + FROM node:26-alpine + WORKDIR /app + COPY package*.json ./ + RUN npm ci --omit=dev + COPY +build/dist ./dist + CMD ["node", "dist/index.js"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + dagger: { + legacyTitle: 'Dagger (TypeScript SDK)', + legacyCode: `import { dag, Directory, File, object, func } from "@dagger.io/dagger"; + +@object() +export class Pipeline { + @func() + async test(src: Directory): Promise<string> { + return dag.container() + .from("node:26-alpine") + .withDirectory("/app", src) + .withWorkdir("/app") + .withExec(["npm", "ci"]) + .withExec(["npm", "test"]) + .stdout(); + } + + @func() + async build(src: Directory): Promise<File> { + const bundle = dag.container() + .from("node:26-alpine") + .withDirectory("/app", src) + .withWorkdir("/app") + .withExec(["npm", "ci"]) + .withExec(["npm", "run", "build"]) + .file("dist/index.js"); + await bundle.export("dist/index.js"); + return bundle; + } + + @func() + async publish(src: Directory): Promise<string> { + const bundle = await this.build(src); + return dag.container() + .from("node:26-alpine") + .withFile("/app/dist/index.js", bundle) + .withEntrypoint(["node", "/app/dist/index.js"]) + .publish("docker.io/myorg/myapp:latest"); + } +}`, + earthfileTitle: 'Earthfile', + earthfileCode: `VERSION 0.8 +FROM node:26-alpine +WORKDIR /app + +deps: + COPY package*.json ./ + RUN npm ci + +test: + FROM +deps + COPY . ./ + RUN npm test + +build: + FROM +deps + COPY . ./ + RUN npm run build + SAVE ARTIFACT dist AS LOCAL dist + +docker: + FROM node:26-alpine + WORKDIR /app + COPY package*.json ./ + RUN npm ci --omit=dev + COPY +build/dist ./dist + CMD ["node", "dist/index.js"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + gha: { + legacyTitle: 'Standard GitHub Actions Workflow', + legacyCode: `name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-26.04 + steps: + - uses: actions/checkout@v4 + + - name: Run Tests + run: npm test + + - name: Build Host Binary / Package + run: npm run build + + - uses: actions/upload-artifact@v4 + with: + name: app-artifact + path: dist/bundle.js + + - uses: docker/login-action@v3 + with: + username: \${{ secrets.DOCKER_USER }} + password: \${{ secrets.DOCKER_TOKEN }} + + - uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: docker.io/myorg/myapp:latest`, + earthfileTitle: 'Clean GitHub Action with EarthBuild', + earthfileCode: `name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: docker/login-action@v3 + with: + username: \${{ secrets.DOCKER_USER }} + password: \${{ secrets.DOCKER_TOKEN }} + + - name: Setup EarthBuild + uses: earthbuild/actions-setup@v2 + + - name: Test, Build Binary & Push Image + run: earth --ci --push +all`, + }, + }, + typescript: { + dockerfile: { + legacyTitle: 'Standard Dockerfile', + legacyCode: `FROM node:26-alpine AS deps +WORKDIR /app +COPY package*.json tsconfig.json ./ +RUN npm ci + +FROM deps AS test +COPY src ./src tsconfig.json ./ +RUN npm test + +FROM deps AS builder +COPY src ./src tsconfig.json ./ +RUN npx tsc --outDir dist + +FROM node:26-alpine AS runtime +COPY --from=builder /app/dist/index.js /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`, + earthfileTitle: 'Earthfile (Direct Host Artifacts & Push Built-In)', + earthfileCode: `VERSION 0.8 +FROM node:26-alpine +WORKDIR /app + +deps: + COPY package*.json tsconfig.json ./ + RUN npm ci + +test: + FROM +deps + COPY src ./src + RUN npm test + +build: + FROM +deps + COPY src ./src + RUN npx tsc + SAVE ARTIFACT dist AS LOCAL dist + +docker: + FROM node:26-alpine + WORKDIR /app + COPY package*.json ./ + RUN npm ci --omit=dev + COPY +build/dist ./dist + CMD ["node", "dist/index.js"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + bake: { + legacyTitle: 'Docker Buildx Bake', + legacyCode: `group "default" { + targets = ["test", "build", "image"] +} + +target "test" { + dockerfile = "Dockerfile" + target = "test" +} + +target "build" { + dockerfile = "Dockerfile" + target = "builder" + output = ["type=local,dest=bin"] +} + +target "image" { + dockerfile = "Dockerfile" + target = "runtime" + tags = ["docker.io/myorg/myapp:latest"] + push = true +}`, + legacyFiles: [ + { + name: 'docker-bake.hcl', + code: `group "default" { + targets = ["test", "build", "image"] +} + +target "test" { + dockerfile = "Dockerfile" + target = "test" +} + +target "build" { + dockerfile = "Dockerfile" + target = "builder" + output = ["type=local,dest=bin"] +} + +target "image" { + dockerfile = "Dockerfile" + target = "runtime" + tags = ["docker.io/myorg/myapp:latest"] + push = true +}`, + }, + { + name: 'Dockerfile', + code: `FROM node:26-alpine AS deps +WORKDIR /app +COPY package*.json tsconfig.json ./ +RUN npm ci + +FROM deps AS test +COPY src ./src tsconfig.json ./ +RUN npm test + +FROM deps AS builder +COPY src ./src tsconfig.json ./ +RUN npx tsc --outDir dist + +FROM node:26-alpine AS runtime +COPY --from=builder /app/dist/index.js /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`, + }, + ], + earthfileTitle: 'Earthfile (Self-Contained & Deterministic)', + earthfileCode: `VERSION 0.8 +FROM node:26-alpine +WORKDIR /app + +deps: + COPY package*.json tsconfig.json ./ + RUN npm ci + +test: + FROM +deps + COPY src ./src + RUN npm test + +build: + FROM +deps + COPY src ./src + RUN npx tsc + SAVE ARTIFACT dist AS LOCAL dist + +docker: + FROM node:26-alpine + WORKDIR /app + COPY package*.json ./ + RUN npm ci --omit=dev + COPY +build/dist ./dist + CMD ["node", "dist/index.js"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + makefile: { + legacyTitle: 'Makefile (TypeScript Host Execution)', + legacyCode: `.PHONY: all test build docker + +all: test build docker + +test: + npm test + +build: + npx tsc --outDir dist + +docker: build + docker build -t docker.io/myorg/myapp:latest . + docker push docker.io/myorg/myapp:latest`, + legacyFiles: [ + { + name: 'Makefile', + code: `.PHONY: all test build docker + +all: test build docker + +test: + npm test + +build: + npx tsc --outDir dist + +docker: build + docker build -t docker.io/myorg/myapp:latest . + docker push docker.io/myorg/myapp:latest`, + }, + { + name: 'Dockerfile', + code: `FROM node:26-alpine AS deps +WORKDIR /app +COPY package*.json tsconfig.json ./ +RUN npm ci + +FROM deps AS test +COPY src ./src tsconfig.json ./ +RUN npm test + +FROM deps AS builder +COPY src ./src tsconfig.json ./ +RUN npx tsc --outDir dist + +FROM node:26-alpine AS runtime +COPY --from=builder /app/dist/index.js /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`, + }, + ], + earthfileTitle: 'Earthfile (Hermetic & Parallel)', + earthfileCode: `VERSION 0.8 +FROM node:26-alpine +WORKDIR /app + +deps: + COPY package*.json tsconfig.json ./ + RUN npm ci + +test: + FROM +deps + COPY src ./src + RUN npm test + +build: + FROM +deps + COPY src ./src + RUN npx tsc + SAVE ARTIFACT dist AS LOCAL dist + +docker: + FROM node:26-alpine + WORKDIR /app + COPY package*.json ./ + RUN npm ci --omit=dev + COPY +build/dist ./dist + CMD ["node", "dist/index.js"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + dagger: { + legacyTitle: 'Dagger (TypeScript SDK)', + legacyCode: `import { dag, Directory, File, object, func } from "@dagger.io/dagger"; + +@object() +export class Pipeline { + @func() + async test(src: Directory): Promise<string> { + return dag.container() + .from("node:26-alpine") + .withDirectory("/app", src) + .withWorkdir("/app") + .withExec(["npm", "ci"]) + .withExec(["npm", "test"]) + .stdout(); + } + + @func() + async build(src: Directory): Promise<File> { + const bundle = dag.container() + .from("node:26-alpine") + .withDirectory("/app", src) + .withWorkdir("/app") + .withExec(["npm", "ci"]) + .withExec(["npm", "run", "build"]) + .file("dist/index.js"); + await bundle.export("dist/index.js"); + return bundle; + } + + @func() + async publish(src: Directory): Promise<string> { + const bundle = await this.build(src); + return dag.container() + .from("node:26-alpine") + .withFile("/app/dist/index.js", bundle) + .withEntrypoint(["node", "/app/dist/index.js"]) + .publish("docker.io/myorg/myapp:latest"); + } +}`, + earthfileTitle: 'Earthfile', + earthfileCode: `VERSION 0.8 +FROM node:26-alpine +WORKDIR /app + +deps: + COPY package*.json tsconfig.json ./ + RUN npm ci + +test: + FROM +deps + COPY src ./src + RUN npm test + +build: + FROM +deps + COPY src ./src + RUN npx tsc + SAVE ARTIFACT dist AS LOCAL dist + +docker: + FROM node:26-alpine + WORKDIR /app + COPY package*.json ./ + RUN npm ci --omit=dev + COPY +build/dist ./dist + CMD ["node", "dist/index.js"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + gha: { + legacyTitle: 'Standard GitHub Actions Workflow', + legacyCode: `name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-26.04 + steps: + - uses: actions/checkout@v4 + + - name: Run Tests + run: npm test + + - name: Build Host Binary / Package + run: npx tsc --outDir dist + + - uses: actions/upload-artifact@v4 + with: + name: app-artifact + path: dist/index.js + + - uses: docker/login-action@v3 + with: + username: \${{ secrets.DOCKER_USER }} + password: \${{ secrets.DOCKER_TOKEN }} + + - uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: docker.io/myorg/myapp:latest`, + earthfileTitle: 'Clean GitHub Action with EarthBuild', + earthfileCode: `name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: docker/login-action@v3 + with: + username: \${{ secrets.DOCKER_USER }} + password: \${{ secrets.DOCKER_TOKEN }} + + - name: Setup EarthBuild + uses: earthbuild/actions-setup@v2 + + - name: Test, Build Binary & Push Image + run: earth --ci --push +all`, + }, + }, + java: { + dockerfile: { + legacyTitle: 'Standard Dockerfile', + legacyCode: `FROM maven:3.9-eclipse-temurin-25-alpine AS deps +WORKDIR /app +COPY pom.xml ./ +RUN mvn dependency:go-offline + +FROM deps AS test +COPY src ./src pom.xml ./ +RUN mvn test + +FROM deps AS builder +COPY src ./src pom.xml ./ +RUN mvn package -DskipTests + +FROM eclipse-temurin:25-jre-alpine AS runtime +COPY --from=builder /app/target/app.jar /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`, + earthfileTitle: 'Earthfile (Direct Host Artifacts & Push Built-In)', + earthfileCode: `VERSION 0.8 +FROM maven:3.9-eclipse-temurin-25-alpine +WORKDIR /app + +deps: + COPY pom.xml ./ + RUN mvn dependency:go-offline + +test: + FROM +deps + COPY src ./src + RUN mvn test + +build: + FROM +deps + COPY src ./src + RUN mvn package -DskipTests + SAVE ARTIFACT target/*.jar AS LOCAL bin/app.jar + +docker: + FROM eclipse-temurin:25-jre-alpine + COPY +build/bin/app.jar /app/app.jar + ENTRYPOINT ["java", "-jar", "/app/app.jar"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + bake: { + legacyTitle: 'Docker Buildx Bake', + legacyCode: `group "default" { + targets = ["test", "build", "image"] +} + +target "test" { + dockerfile = "Dockerfile" + target = "test" +} + +target "build" { + dockerfile = "Dockerfile" + target = "builder" + output = ["type=local,dest=bin"] +} + +target "image" { + dockerfile = "Dockerfile" + target = "runtime" + tags = ["docker.io/myorg/myapp:latest"] + push = true +}`, + legacyFiles: [ + { + name: 'docker-bake.hcl', + code: `group "default" { + targets = ["test", "build", "image"] +} + +target "test" { + dockerfile = "Dockerfile" + target = "test" +} + +target "build" { + dockerfile = "Dockerfile" + target = "builder" + output = ["type=local,dest=bin"] +} + +target "image" { + dockerfile = "Dockerfile" + target = "runtime" + tags = ["docker.io/myorg/myapp:latest"] + push = true +}`, + }, + { + name: 'Dockerfile', + code: `FROM maven:3.9-eclipse-temurin-25-alpine AS deps +WORKDIR /app +COPY pom.xml ./ +RUN mvn dependency:go-offline + +FROM deps AS test +COPY src ./src pom.xml ./ +RUN mvn test + +FROM deps AS builder +COPY src ./src pom.xml ./ +RUN mvn package -DskipTests + +FROM eclipse-temurin:25-jre-alpine AS runtime +COPY --from=builder /app/target/app.jar /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`, + }, + ], + earthfileTitle: 'Earthfile (Self-Contained & Deterministic)', + earthfileCode: `VERSION 0.8 +FROM maven:3.9-eclipse-temurin-25-alpine +WORKDIR /app + +deps: + COPY pom.xml ./ + RUN mvn dependency:go-offline + +test: + FROM +deps + COPY src ./src + RUN mvn test + +build: + FROM +deps + COPY src ./src + RUN mvn package -DskipTests + SAVE ARTIFACT target/*.jar AS LOCAL bin/app.jar + +docker: + FROM eclipse-temurin:25-jre-alpine + COPY +build/bin/app.jar /app/app.jar + ENTRYPOINT ["java", "-jar", "/app/app.jar"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + makefile: { + legacyTitle: 'Makefile (Java Host Execution)', + legacyCode: `.PHONY: all test build docker + +all: test build docker + +test: + mvn test + +build: + mvn package -DskipTests + +docker: build + docker build -t docker.io/myorg/myapp:latest . + docker push docker.io/myorg/myapp:latest`, + legacyFiles: [ + { + name: 'Makefile', + code: `.PHONY: all test build docker + +all: test build docker + +test: + mvn test + +build: + mvn package -DskipTests + +docker: build + docker build -t docker.io/myorg/myapp:latest . + docker push docker.io/myorg/myapp:latest`, + }, + { + name: 'Dockerfile', + code: `FROM maven:3.9-eclipse-temurin-25-alpine AS deps +WORKDIR /app +COPY pom.xml ./ +RUN mvn dependency:go-offline + +FROM deps AS test +COPY src ./src pom.xml ./ +RUN mvn test + +FROM deps AS builder +COPY src ./src pom.xml ./ +RUN mvn package -DskipTests + +FROM eclipse-temurin:25-jre-alpine AS runtime +COPY --from=builder /app/target/app.jar /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`, + }, + ], + earthfileTitle: 'Earthfile (Hermetic & Parallel)', + earthfileCode: `VERSION 0.8 +FROM maven:3.9-eclipse-temurin-25-alpine +WORKDIR /app + +deps: + COPY pom.xml ./ + RUN mvn dependency:go-offline + +test: + FROM +deps + COPY src ./src + RUN mvn test + +build: + FROM +deps + COPY src ./src + RUN mvn package -DskipTests + SAVE ARTIFACT target/*.jar AS LOCAL bin/app.jar + +docker: + FROM eclipse-temurin:25-jre-alpine + COPY +build/bin/app.jar /app/app.jar + ENTRYPOINT ["java", "-jar", "/app/app.jar"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + dagger: { + legacyTitle: 'Dagger (Java SDK)', + legacyCode: `package io.dagger.modules.pipeline; + +import static io.dagger.client.Dagger.dag; +import io.dagger.client.Container; +import io.dagger.client.Directory; +import io.dagger.client.File; +import io.dagger.module.annotation.Function; +import io.dagger.module.annotation.Object; +import java.util.List; + +@Object +public class Pipeline { + + private Container base(Directory src) { + return dag().container() + .from("maven:3.9-eclipse-temurin-25-alpine") + .withWorkdir("/app") + .withMountedDirectory("/app", src) + .withExec(List.of("mvn", "dependency:go-offline")); + } + + @Function + public String test(Directory src) throws Exception { + return base(src) + .withExec(List.of("mvn", "test")) + .stdout(); + } + + @Function + public File build(Directory src) throws Exception { + File jar = base(src) + .withExec(List.of("mvn", "package", "-DskipTests")) + .file("target/app.jar"); + jar.export("bin/app.jar"); + return jar; + } + + @Function + public String publish(Directory src) throws Exception { + File jar = build(src); + return dag().container() + .from("eclipse-temurin:25-jre-alpine") + .withFile("/app/app.jar", jar) + .withEntrypoint(List.of("java", "-jar", "/app/app.jar")) + .publish("docker.io/myorg/myapp:latest"); + } +}`, + earthfileTitle: 'Earthfile', + earthfileCode: `VERSION 0.8 +FROM maven:3.9-eclipse-temurin-25-alpine +WORKDIR /app + +deps: + COPY pom.xml ./ + RUN mvn dependency:go-offline + +test: + FROM +deps + COPY src ./src + RUN mvn test + +build: + FROM +deps + COPY src ./src + RUN mvn package -DskipTests + SAVE ARTIFACT target/*.jar AS LOCAL bin/app.jar + +docker: + FROM eclipse-temurin:25-jre-alpine + COPY +build/bin/app.jar /app/app.jar + ENTRYPOINT ["java", "-jar", "/app/app.jar"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + gha: { + legacyTitle: 'Standard GitHub Actions Workflow', + legacyCode: `name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-26.04 + steps: + - uses: actions/checkout@v4 + + - name: Run Tests + run: mvn test + + - name: Build Host Binary / Package + run: mvn package -DskipTests + + - uses: actions/upload-artifact@v4 + with: + name: app-artifact + path: target/app.jar + + - uses: docker/login-action@v3 + with: + username: \${{ secrets.DOCKER_USER }} + password: \${{ secrets.DOCKER_TOKEN }} + + - uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: docker.io/myorg/myapp:latest`, + earthfileTitle: 'Clean GitHub Action with EarthBuild', + earthfileCode: `name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: docker/login-action@v3 + with: + username: \${{ secrets.DOCKER_USER }} + password: \${{ secrets.DOCKER_TOKEN }} + + - name: Setup EarthBuild + uses: earthbuild/actions-setup@v2 + + - name: Test, Build Binary & Push Image + run: earth --ci --push +all`, + }, + }, + cpp: { + dockerfile: { + legacyTitle: 'Standard Dockerfile', + legacyCode: `FROM alpine:3.24 AS deps +WORKDIR /app +COPY CMakeLists.txt ./ +RUN cmake -B build -G Ninja + +FROM deps AS test +COPY src ./src CMakeLists.txt ./ +RUN ctest --test-dir build --output-on-failure + +FROM deps AS builder +COPY src ./src CMakeLists.txt ./ +RUN cmake --build build --config Release + +FROM alpine:3.24 AS runtime +COPY --from=builder /app/build/bin/app /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`, + earthfileTitle: 'Earthfile (Direct Host Artifacts & Push Built-In)', + earthfileCode: `VERSION 0.8 +FROM alpine:3.24 +RUN apk add --no-cache build-base cmake ninja +WORKDIR /app + +deps: + COPY CMakeLists.txt ./ + RUN cmake -B build -G Ninja + +test: + FROM +deps + COPY src ./src + RUN cmake --build build --target test + +build: + FROM +deps + COPY src ./src + RUN cmake --build build --config Release + SAVE ARTIFACT build/bin/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + RUN apk add --no-cache libstdc++ + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + bake: { + legacyTitle: 'Docker Buildx Bake', + legacyCode: `group "default" { + targets = ["test", "build", "image"] +} + +target "test" { + dockerfile = "Dockerfile" + target = "test" +} + +target "build" { + dockerfile = "Dockerfile" + target = "builder" + output = ["type=local,dest=bin"] +} + +target "image" { + dockerfile = "Dockerfile" + target = "runtime" + tags = ["docker.io/myorg/myapp:latest"] + push = true +}`, + legacyFiles: [ + { + name: 'docker-bake.hcl', + code: `group "default" { + targets = ["test", "build", "image"] +} + +target "test" { + dockerfile = "Dockerfile" + target = "test" +} + +target "build" { + dockerfile = "Dockerfile" + target = "builder" + output = ["type=local,dest=bin"] +} + +target "image" { + dockerfile = "Dockerfile" + target = "runtime" + tags = ["docker.io/myorg/myapp:latest"] + push = true +}`, + }, + { + name: 'Dockerfile', + code: `FROM alpine:3.24 AS deps +WORKDIR /app +COPY CMakeLists.txt ./ +RUN cmake -B build -G Ninja + +FROM deps AS test +COPY src ./src CMakeLists.txt ./ +RUN ctest --test-dir build --output-on-failure + +FROM deps AS builder +COPY src ./src CMakeLists.txt ./ +RUN cmake --build build --config Release + +FROM alpine:3.24 AS runtime +COPY --from=builder /app/build/bin/app /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`, + }, + ], + earthfileTitle: 'Earthfile (Self-Contained & Deterministic)', + earthfileCode: `VERSION 0.8 +FROM alpine:3.24 +RUN apk add --no-cache build-base cmake ninja +WORKDIR /app + +deps: + COPY CMakeLists.txt ./ + RUN cmake -B build -G Ninja + +test: + FROM +deps + COPY src ./src + RUN cmake --build build --target test + +build: + FROM +deps + COPY src ./src + RUN cmake --build build --config Release + SAVE ARTIFACT build/bin/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + RUN apk add --no-cache libstdc++ + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + makefile: { + legacyTitle: 'Makefile (C++ Host Execution)', + legacyCode: `.PHONY: all test build docker + +all: test build docker + +test: + ctest --test-dir build --output-on-failure + +build: + cmake --build build --config Release + +docker: build + docker build -t docker.io/myorg/myapp:latest . + docker push docker.io/myorg/myapp:latest`, + legacyFiles: [ + { + name: 'Makefile', + code: `.PHONY: all test build docker + +all: test build docker + +test: + ctest --test-dir build --output-on-failure + +build: + cmake --build build --config Release + +docker: build + docker build -t docker.io/myorg/myapp:latest . + docker push docker.io/myorg/myapp:latest`, + }, + { + name: 'Dockerfile', + code: `FROM alpine:3.24 AS deps +WORKDIR /app +COPY CMakeLists.txt ./ +RUN cmake -B build -G Ninja + +FROM deps AS test +COPY src ./src CMakeLists.txt ./ +RUN ctest --test-dir build --output-on-failure + +FROM deps AS builder +COPY src ./src CMakeLists.txt ./ +RUN cmake --build build --config Release + +FROM alpine:3.24 AS runtime +COPY --from=builder /app/build/bin/app /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`, + }, + ], + earthfileTitle: 'Earthfile (Hermetic & Parallel)', + earthfileCode: `VERSION 0.8 +FROM alpine:3.24 +RUN apk add --no-cache build-base cmake ninja +WORKDIR /app + +deps: + COPY CMakeLists.txt ./ + RUN cmake -B build -G Ninja + +test: + FROM +deps + COPY src ./src + RUN cmake --build build --target test + +build: + FROM +deps + COPY src ./src + RUN cmake --build build --config Release + SAVE ARTIFACT build/bin/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + RUN apk add --no-cache libstdc++ + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + dagger: { + legacyTitle: 'Dagger (Go SDK)', + legacyCode: `package main + +import ( + "context" + "dagger/pipeline/internal/dagger" +) + +type Pipeline struct{} + +func (m *Pipeline) base(src *dagger.Directory) *dagger.Container { + return dag.Container(). + From("alpine:3.24"). + WithWorkdir("/app"). + WithDirectory(".", src). + WithExec([]string{"sh", "-c", "cmake -B build -G Ninja"}) +} + +func (m *Pipeline) Test(ctx context.Context, src *dagger.Directory) (string, error) { + return m.base(src).WithExec([]string{"sh", "-c", "ctest --test-dir build --output-on-failure"}).Stdout(ctx) +} + +func (m *Pipeline) Build(ctx context.Context, src *dagger.Directory) (*dagger.File, error) { + bin := m.base(src). + WithExec([]string{"sh", "-c", "cmake --build build --config Release"}). + File("build/bin/app") + + if _, err := bin.Export(ctx, "bin/app"); err != nil { + return nil, err + } + return bin, nil +} + +func (m *Pipeline) Publish(ctx context.Context, src *dagger.Directory) (string, error) { + bin, err := m.Build(ctx, src) + if err != nil { + return "", err + } + return dag.Container(). + From("alpine:3.24"). + WithFile("/usr/local/bin/app", bin). + WithEntrypoint([]string{"/usr/local/bin/app"}). + Publish(ctx, "docker.io/myorg/myapp:latest") +}`, + earthfileTitle: 'Earthfile', + earthfileCode: `VERSION 0.8 +FROM alpine:3.24 +RUN apk add --no-cache build-base cmake ninja +WORKDIR /app + +deps: + COPY CMakeLists.txt ./ + RUN cmake -B build -G Ninja + +test: + FROM +deps + COPY src ./src + RUN cmake --build build --target test + +build: + FROM +deps + COPY src ./src + RUN cmake --build build --config Release + SAVE ARTIFACT build/bin/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + RUN apk add --no-cache libstdc++ + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + gha: { + legacyTitle: 'Standard GitHub Actions Workflow', + legacyCode: `name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-26.04 + steps: + - uses: actions/checkout@v4 + + - name: Run Tests + run: ctest --test-dir build --output-on-failure + + - name: Build Host Binary / Package + run: cmake --build build --config Release + + - uses: actions/upload-artifact@v4 + with: + name: app-artifact + path: build/bin/app + + - uses: docker/login-action@v3 + with: + username: \${{ secrets.DOCKER_USER }} + password: \${{ secrets.DOCKER_TOKEN }} + + - uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: docker.io/myorg/myapp:latest`, + earthfileTitle: 'Clean GitHub Action with EarthBuild', + earthfileCode: `name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: docker/login-action@v3 + with: + username: \${{ secrets.DOCKER_USER }} + password: \${{ secrets.DOCKER_TOKEN }} + + - name: Setup EarthBuild + uses: earthbuild/actions-setup@v2 + + - name: Test, Build Binary & Push Image + run: earth --ci --push +all`, + }, + }, + go: { + dockerfile: { + legacyTitle: 'Standard Dockerfile', + legacyCode: `FROM golang:1.27-alpine3.24 AS deps +WORKDIR /app +COPY go.mod go.sum ./ +RUN go mod download + +FROM deps AS test +COPY *.go ./ +RUN go test -v ./... + +FROM deps AS builder +COPY *.go ./ +RUN CGO_ENABLED=0 go build -o bin/app . + +FROM alpine:3.24 AS runtime +COPY --from=builder /app/bin/app /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`, + earthfileTitle: 'Earthfile (Direct Host Artifacts & Push Built-In)', + earthfileCode: `VERSION 0.8 +FROM golang:1.27-alpine3.24 +WORKDIR /app + +deps: + COPY go.mod go.sum ./ + RUN go mod download + +test: + FROM +deps + COPY *.go ./ + RUN go test -v ./... + +build: + FROM +deps + COPY *.go ./ + RUN CGO_ENABLED=0 go build -o bin/app . + SAVE ARTIFACT bin/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + bake: { + legacyTitle: 'Docker Buildx Bake', + legacyCode: `group "default" { + targets = ["test", "build", "image"] +} + +target "test" { + dockerfile = "Dockerfile" + target = "test" +} + +target "build" { + dockerfile = "Dockerfile" + target = "builder" + output = ["type=local,dest=bin"] +} + +target "image" { + dockerfile = "Dockerfile" + target = "runtime" + tags = ["docker.io/myorg/myapp:latest"] + push = true +}`, + legacyFiles: [ + { + name: 'docker-bake.hcl', + code: `group "default" { + targets = ["test", "build", "image"] +} + +target "test" { + dockerfile = "Dockerfile" + target = "test" +} + +target "build" { + dockerfile = "Dockerfile" + target = "builder" + output = ["type=local,dest=bin"] +} + +target "image" { + dockerfile = "Dockerfile" + target = "runtime" + tags = ["docker.io/myorg/myapp:latest"] + push = true +}`, + }, + { + name: 'Dockerfile', + code: `FROM golang:1.27-alpine3.24 AS deps +WORKDIR /app +COPY go.mod go.sum ./ +RUN go mod download + +FROM deps AS test +COPY *.go ./ +RUN go test -v ./... + +FROM deps AS builder +COPY *.go ./ +RUN CGO_ENABLED=0 go build -o bin/app . + +FROM alpine:3.24 AS runtime +COPY --from=builder /app/bin/app /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`, + }, + ], + earthfileTitle: 'Earthfile (Self-Contained & Deterministic)', + earthfileCode: `VERSION 0.8 +FROM golang:1.27-alpine3.24 +WORKDIR /app + +deps: + COPY go.mod go.sum ./ + RUN go mod download + +test: + FROM +deps + COPY *.go ./ + RUN go test -v ./... + +build: + FROM +deps + COPY *.go ./ + RUN CGO_ENABLED=0 go build -o bin/app . + SAVE ARTIFACT bin/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + makefile: { + legacyTitle: 'Makefile (Go Host Execution)', + legacyCode: `.PHONY: all test build docker + +all: test build docker + +test: + go test -v ./... + +build: + CGO_ENABLED=0 go build -o bin/app . + +docker: build + docker build -t docker.io/myorg/myapp:latest . + docker push docker.io/myorg/myapp:latest`, + legacyFiles: [ + { + name: 'Makefile', + code: `.PHONY: all test build docker + +all: test build docker + +test: + go test -v ./... + +build: + CGO_ENABLED=0 go build -o bin/app . + +docker: build + docker build -t docker.io/myorg/myapp:latest . + docker push docker.io/myorg/myapp:latest`, + }, + { + name: 'Dockerfile', + code: `FROM golang:1.27-alpine3.24 AS deps +WORKDIR /app +COPY go.mod go.sum ./ +RUN go mod download + +FROM deps AS test +COPY *.go ./ +RUN go test -v ./... + +FROM deps AS builder +COPY *.go ./ +RUN CGO_ENABLED=0 go build -o bin/app . + +FROM alpine:3.24 AS runtime +COPY --from=builder /app/bin/app /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`, + }, + ], + earthfileTitle: 'Earthfile (Hermetic & Parallel)', + earthfileCode: `VERSION 0.8 +FROM golang:1.27-alpine3.24 +WORKDIR /app + +deps: + COPY go.mod go.sum ./ + RUN go mod download + +test: + FROM +deps + COPY *.go ./ + RUN go test -v ./... + +build: + FROM +deps + COPY *.go ./ + RUN CGO_ENABLED=0 go build -o bin/app . + SAVE ARTIFACT bin/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + dagger: { + legacyTitle: 'Dagger (Go SDK)', + legacyCode: `package main + +import ( + "context" + "dagger/pipeline/internal/dagger" +) + +type Pipeline struct{} + +func (m *Pipeline) base(src *dagger.Directory) *dagger.Container { + return dag.Container(). + From("golang:1.27-alpine3.24"). + WithWorkdir("/app"). + WithDirectory(".", src). + WithExec([]string{"sh", "-c", "go mod download"}) +} + +func (m *Pipeline) Test(ctx context.Context, src *dagger.Directory) (string, error) { + return m.base(src).WithExec([]string{"sh", "-c", "go test -v ./..."}).Stdout(ctx) +} + +func (m *Pipeline) Build(ctx context.Context, src *dagger.Directory) (*dagger.File, error) { + bin := m.base(src). + WithExec([]string{"sh", "-c", "CGO_ENABLED=0 go build -o bin/app ."}). + File("bin/app") + + if _, err := bin.Export(ctx, "bin/app"); err != nil { + return nil, err + } + return bin, nil +} + +func (m *Pipeline) Publish(ctx context.Context, src *dagger.Directory) (string, error) { + bin, err := m.Build(ctx, src) + if err != nil { + return "", err + } + return dag.Container(). + From("alpine:3.24"). + WithFile("/usr/local/bin/app", bin). + WithEntrypoint([]string{"/usr/local/bin/app"}). + Publish(ctx, "docker.io/myorg/myapp:latest") +}`, + earthfileTitle: 'Earthfile', + earthfileCode: `VERSION 0.8 +FROM golang:1.27-alpine3.24 +WORKDIR /app + +deps: + COPY go.mod go.sum ./ + RUN go mod download + +test: + FROM +deps + COPY *.go ./ + RUN go test -v ./... + +build: + FROM +deps + COPY *.go ./ + RUN CGO_ENABLED=0 go build -o bin/app . + SAVE ARTIFACT bin/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + gha: { + legacyTitle: 'Standard GitHub Actions Workflow', + legacyCode: `name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-26.04 + steps: + - uses: actions/checkout@v4 + + - name: Run Tests + run: go test -v ./... + + - name: Build Host Binary / Package + run: CGO_ENABLED=0 go build -o bin/app . + + - uses: actions/upload-artifact@v4 + with: + name: app-artifact + path: bin/app + + - uses: docker/login-action@v3 + with: + username: \${{ secrets.DOCKER_USER }} + password: \${{ secrets.DOCKER_TOKEN }} + + - uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: docker.io/myorg/myapp:latest`, + earthfileTitle: 'Clean GitHub Action with EarthBuild', + earthfileCode: `name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: docker/login-action@v3 + with: + username: \${{ secrets.DOCKER_USER }} + password: \${{ secrets.DOCKER_TOKEN }} + + - name: Setup EarthBuild + uses: earthbuild/actions-setup@v2 + + - name: Test, Build Binary & Push Image + run: earth --ci --push +all`, + }, + }, + rust: { + dockerfile: { + legacyTitle: 'Standard Dockerfile', + legacyCode: `FROM rust:1.85-alpine3.24 AS deps +WORKDIR /app +COPY Cargo.toml Cargo.lock ./ +RUN cargo fetch + +FROM deps AS test +COPY src ./src ./ +RUN cargo test + +FROM deps AS builder +COPY src ./src ./ +RUN cargo build --release + +FROM alpine:3.24 AS runtime +COPY --from=builder /app/target/release/app /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`, + earthfileTitle: 'Earthfile (Direct Host Artifacts & Push Built-In)', + earthfileCode: `VERSION 0.8 +FROM rust:1.85-alpine3.24 +RUN apk add --no-cache musl-dev +WORKDIR /app + +deps: + COPY Cargo.toml Cargo.lock ./ + RUN cargo fetch + +test: + FROM +deps + COPY src ./src + RUN cargo test + +build: + FROM +deps + COPY src ./src + RUN cargo build --release + SAVE ARTIFACT target/release/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + bake: { + legacyTitle: 'Docker Buildx Bake', + legacyCode: `group "default" { + targets = ["test", "build", "image"] +} + +target "test" { + dockerfile = "Dockerfile" + target = "test" +} + +target "build" { + dockerfile = "Dockerfile" + target = "builder" + output = ["type=local,dest=bin"] +} + +target "image" { + dockerfile = "Dockerfile" + target = "runtime" + tags = ["docker.io/myorg/myapp:latest"] + push = true +}`, + legacyFiles: [ + { + name: 'docker-bake.hcl', + code: `group "default" { + targets = ["test", "build", "image"] +} + +target "test" { + dockerfile = "Dockerfile" + target = "test" +} + +target "build" { + dockerfile = "Dockerfile" + target = "builder" + output = ["type=local,dest=bin"] +} + +target "image" { + dockerfile = "Dockerfile" + target = "runtime" + tags = ["docker.io/myorg/myapp:latest"] + push = true +}`, + }, + { + name: 'Dockerfile', + code: `FROM rust:1.85-alpine3.24 AS deps +WORKDIR /app +COPY Cargo.toml Cargo.lock ./ +RUN cargo fetch + +FROM deps AS test +COPY src ./src ./ +RUN cargo test + +FROM deps AS builder +COPY src ./src ./ +RUN cargo build --release + +FROM alpine:3.24 AS runtime +COPY --from=builder /app/target/release/app /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`, + }, + ], + earthfileTitle: 'Earthfile (Self-Contained & Deterministic)', + earthfileCode: `VERSION 0.8 +FROM rust:1.85-alpine3.24 +RUN apk add --no-cache musl-dev +WORKDIR /app + +deps: + COPY Cargo.toml Cargo.lock ./ + RUN cargo fetch + +test: + FROM +deps + COPY src ./src + RUN cargo test + +build: + FROM +deps + COPY src ./src + RUN cargo build --release + SAVE ARTIFACT target/release/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + makefile: { + legacyTitle: 'Makefile (Rust Host Execution)', + legacyCode: `.PHONY: all test build docker + +all: test build docker + +test: + cargo test + +build: + cargo build --release + +docker: build + docker build -t docker.io/myorg/myapp:latest . + docker push docker.io/myorg/myapp:latest`, + legacyFiles: [ + { + name: 'Makefile', + code: `.PHONY: all test build docker + +all: test build docker + +test: + cargo test + +build: + cargo build --release + +docker: build + docker build -t docker.io/myorg/myapp:latest . + docker push docker.io/myorg/myapp:latest`, + }, + { + name: 'Dockerfile', + code: `FROM rust:1.85-alpine3.24 AS deps +WORKDIR /app +COPY Cargo.toml Cargo.lock ./ +RUN cargo fetch + +FROM deps AS test +COPY src ./src ./ +RUN cargo test + +FROM deps AS builder +COPY src ./src ./ +RUN cargo build --release + +FROM alpine:3.24 AS runtime +COPY --from=builder /app/target/release/app /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`, + }, + ], + earthfileTitle: 'Earthfile (Hermetic & Parallel)', + earthfileCode: `VERSION 0.8 +FROM rust:1.85-alpine3.24 +RUN apk add --no-cache musl-dev +WORKDIR /app + +deps: + COPY Cargo.toml Cargo.lock ./ + RUN cargo fetch + +test: + FROM +deps + COPY src ./src + RUN cargo test + +build: + FROM +deps + COPY src ./src + RUN cargo build --release + SAVE ARTIFACT target/release/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + dagger: { + legacyTitle: 'Dagger (Go SDK)', + legacyCode: `package main + +import ( + "context" + "dagger/pipeline/internal/dagger" +) + +type Pipeline struct{} + +func (m *Pipeline) base(src *dagger.Directory) *dagger.Container { + return dag.Container(). + From("rust:1.85-alpine3.24"). + WithWorkdir("/app"). + WithDirectory(".", src). + WithExec([]string{"cargo", "fetch"}) +} + +func (m *Pipeline) Test(ctx context.Context, src *dagger.Directory) (string, error) { + return m.base(src). + WithExec([]string{"cargo", "test"}). + Stdout(ctx) +} + +func (m *Pipeline) Build(ctx context.Context, src *dagger.Directory) (*dagger.File, error) { + bin := m.base(src). + WithExec([]string{"cargo", "build", "--release"}). + File("target/release/app") + + if _, err := bin.Export(ctx, "bin/app"); err != nil { + return nil, err + } + return bin, nil +} + +func (m *Pipeline) Publish(ctx context.Context, src *dagger.Directory) (string, error) { + bin, err := m.Build(ctx, src) + if err != nil { + return "", err + } + return dag.Container(). + From("alpine:3.24"). + WithFile("/usr/local/bin/app", bin). + WithEntrypoint([]string{"/usr/local/bin/app"}). + Publish(ctx, "docker.io/myorg/myapp:latest") +}`, + earthfileTitle: 'Earthfile', + earthfileCode: `VERSION 0.8 +FROM rust:1.85-alpine3.24 +RUN apk add --no-cache musl-dev +WORKDIR /app + +deps: + COPY Cargo.toml Cargo.lock ./ + RUN cargo fetch + +test: + FROM +deps + COPY src ./src + RUN cargo test + +build: + FROM +deps + COPY src ./src + RUN cargo build --release + SAVE ARTIFACT target/release/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + gha: { + legacyTitle: 'Standard GitHub Actions Workflow', + legacyCode: `name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-26.04 + steps: + - uses: actions/checkout@v4 + + - name: Run Tests + run: cargo test + + - name: Build Host Binary / Package + run: cargo build --release + + - uses: actions/upload-artifact@v4 + with: + name: app-artifact + path: target/release/app + + - uses: docker/login-action@v3 + with: + username: \${{ secrets.DOCKER_USER }} + password: \${{ secrets.DOCKER_TOKEN }} + + - uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: docker.io/myorg/myapp:latest`, + earthfileTitle: 'Clean GitHub Action with EarthBuild', + earthfileCode: `name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: docker/login-action@v3 + with: + username: \${{ secrets.DOCKER_USER }} + password: \${{ secrets.DOCKER_TOKEN }} + + - name: Setup EarthBuild + uses: earthbuild/actions-setup@v2 + + - name: Test, Build Binary & Push Image + run: earth --ci --push +all`, + }, + }, + zig: { + dockerfile: { + legacyTitle: 'Standard Dockerfile', + legacyCode: `FROM alpine:3.24 AS deps +WORKDIR /app +COPY build.zig build.zig.zon ./ +RUN zig build --fetch + +FROM deps AS test +COPY src ./src build.zig build.zig.zon ./ +RUN zig build test + +FROM deps AS builder +COPY src ./src build.zig build.zig.zon ./ +RUN zig build -Doptimize=ReleaseSafe + +FROM alpine:3.24 AS runtime +COPY --from=builder /app/zig-out/bin/app /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`, + earthfileTitle: 'Earthfile (Direct Host Artifacts & Push Built-In)', + earthfileCode: `VERSION 0.8 +FROM alpine:3.24 +RUN apk add --no-cache zig +WORKDIR /app + +deps: + COPY build.zig build.zig.zon ./ + RUN zig build --fetch + +test: + FROM +deps + COPY src ./src + RUN zig build test + +build: + FROM +deps + COPY src ./src + RUN zig build -Doptimize=ReleaseSafe + SAVE ARTIFACT zig-out/bin/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + bake: { + legacyTitle: 'Docker Buildx Bake', + legacyCode: `group "default" { + targets = ["test", "build", "image"] +} + +target "test" { + dockerfile = "Dockerfile" + target = "test" +} + +target "build" { + dockerfile = "Dockerfile" + target = "builder" + output = ["type=local,dest=bin"] +} + +target "image" { + dockerfile = "Dockerfile" + target = "runtime" + tags = ["docker.io/myorg/myapp:latest"] + push = true +}`, + legacyFiles: [ + { + name: 'docker-bake.hcl', + code: `group "default" { + targets = ["test", "build", "image"] +} + +target "test" { + dockerfile = "Dockerfile" + target = "test" +} + +target "build" { + dockerfile = "Dockerfile" + target = "builder" + output = ["type=local,dest=bin"] +} + +target "image" { + dockerfile = "Dockerfile" + target = "runtime" + tags = ["docker.io/myorg/myapp:latest"] + push = true +}`, + }, + { + name: 'Dockerfile', + code: `FROM alpine:3.24 AS deps +WORKDIR /app +COPY build.zig build.zig.zon ./ +RUN zig build --fetch + +FROM deps AS test +COPY src ./src build.zig build.zig.zon ./ +RUN zig build test + +FROM deps AS builder +COPY src ./src build.zig build.zig.zon ./ +RUN zig build -Doptimize=ReleaseSafe + +FROM alpine:3.24 AS runtime +COPY --from=builder /app/zig-out/bin/app /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`, + }, + ], + earthfileTitle: 'Earthfile (Self-Contained & Deterministic)', + earthfileCode: `VERSION 0.8 +FROM alpine:3.24 +RUN apk add --no-cache zig +WORKDIR /app + +deps: + COPY build.zig build.zig.zon ./ + RUN zig build --fetch + +test: + FROM +deps + COPY src ./src + RUN zig build test + +build: + FROM +deps + COPY src ./src + RUN zig build -Doptimize=ReleaseSafe + SAVE ARTIFACT zig-out/bin/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + makefile: { + legacyTitle: 'Makefile (Zig Host Execution)', + legacyCode: `.PHONY: all test build docker + +all: test build docker + +test: + zig build test + +build: + zig build -Doptimize=ReleaseSafe + +docker: build + docker build -t docker.io/myorg/myapp:latest . + docker push docker.io/myorg/myapp:latest`, + legacyFiles: [ + { + name: 'Makefile', + code: `.PHONY: all test build docker + +all: test build docker + +test: + zig build test + +build: + zig build -Doptimize=ReleaseSafe + +docker: build + docker build -t docker.io/myorg/myapp:latest . + docker push docker.io/myorg/myapp:latest`, + }, + { + name: 'Dockerfile', + code: `FROM alpine:3.24 AS deps +WORKDIR /app +COPY build.zig build.zig.zon ./ +RUN zig build --fetch + +FROM deps AS test +COPY src ./src build.zig build.zig.zon ./ +RUN zig build test + +FROM deps AS builder +COPY src ./src build.zig build.zig.zon ./ +RUN zig build -Doptimize=ReleaseSafe + +FROM alpine:3.24 AS runtime +COPY --from=builder /app/zig-out/bin/app /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`, + }, + ], + earthfileTitle: 'Earthfile (Hermetic & Parallel)', + earthfileCode: `VERSION 0.8 +FROM alpine:3.24 +RUN apk add --no-cache zig +WORKDIR /app + +deps: + COPY build.zig build.zig.zon ./ + RUN zig build --fetch + +test: + FROM +deps + COPY src ./src + RUN zig build test + +build: + FROM +deps + COPY src ./src + RUN zig build -Doptimize=ReleaseSafe + SAVE ARTIFACT zig-out/bin/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + dagger: { + legacyTitle: 'Dagger (Go SDK Module — No Native Zig SDK)', + legacyCode: `package main + +import ( + "context" + "dagger/pipeline/internal/dagger" +) + +type Pipeline struct{} + +func (m *Pipeline) base(src *dagger.Directory) *dagger.Container { + return dag.Container(). + From("alpine:3.24"). + WithWorkdir("/app"). + WithDirectory(".", src). + WithExec([]string{"sh", "-c", "zig build --fetch"}) +} + +func (m *Pipeline) Test(ctx context.Context, src *dagger.Directory) (string, error) { + return m.base(src).WithExec([]string{"sh", "-c", "zig build test"}).Stdout(ctx) +} + +func (m *Pipeline) Build(ctx context.Context, src *dagger.Directory) (*dagger.File, error) { + bin := m.base(src). + WithExec([]string{"sh", "-c", "zig build -Doptimize=ReleaseSafe"}). + File("zig-out/bin/app") + + if _, err := bin.Export(ctx, "bin/app"); err != nil { + return nil, err + } + return bin, nil +} + +func (m *Pipeline) Publish(ctx context.Context, src *dagger.Directory) (string, error) { + bin, err := m.Build(ctx, src) + if err != nil { + return "", err + } + return dag.Container(). + From("alpine:3.24"). + WithFile("/usr/local/bin/app", bin). + WithEntrypoint([]string{"/usr/local/bin/app"}). + Publish(ctx, "docker.io/myorg/myapp:latest") +}`, + earthfileTitle: 'Earthfile', + earthfileCode: `VERSION 0.8 +FROM alpine:3.24 +RUN apk add --no-cache zig +WORKDIR /app + +deps: + COPY build.zig build.zig.zon ./ + RUN zig build --fetch + +test: + FROM +deps + COPY src ./src + RUN zig build test + +build: + FROM +deps + COPY src ./src + RUN zig build -Doptimize=ReleaseSafe + SAVE ARTIFACT zig-out/bin/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`, + }, + gha: { + legacyTitle: 'Standard GitHub Actions Workflow', + legacyCode: `name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-26.04 + steps: + - uses: actions/checkout@v4 + + - name: Run Tests + run: zig build test + + - name: Build Host Binary / Package + run: zig build -Doptimize=ReleaseSafe + + - uses: actions/upload-artifact@v4 + with: + name: app-artifact + path: zig-out/bin/app + + - uses: docker/login-action@v3 + with: + username: \${{ secrets.DOCKER_USER }} + password: \${{ secrets.DOCKER_TOKEN }} + + - uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: docker.io/myorg/myapp:latest`, + earthfileTitle: 'Clean GitHub Action with EarthBuild', + earthfileCode: `name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: docker/login-action@v3 + with: + username: \${{ secrets.DOCKER_USER }} + password: \${{ secrets.DOCKER_TOKEN }} + + - name: Setup EarthBuild + uses: earthbuild/actions-setup@v2 + + - name: Test, Build Binary & Push Image + run: earth --ci --push +all`, + }, + }, +}; diff --git a/assets/ts/nav.ts b/assets/ts/nav.ts new file mode 100644 index 0000000..3f6bf97 --- /dev/null +++ b/assets/ts/nav.ts @@ -0,0 +1,70 @@ +/** + * Navigation & Mobile Drawer Interactions + */ + +export function initNavbar(): void { + const toggle = + document.getElementById('mobileMenuToggle') || + document.querySelector('.mobile-menu-toggle') || + document.querySelector('.nav-toggle'); + const drawer = + document.getElementById('mobileNavDrawer') || + document.querySelector('.mobile-nav-drawer') || + document.querySelector('.nav-menu'); + const navbar = + document.querySelector('.navbar') || + document.querySelector('.site-nav'); + + if (toggle && drawer) { + toggle.addEventListener('click', (e) => { + e.stopPropagation(); + const isOpen = drawer.classList.toggle('open'); + drawer.classList.toggle('active', isOpen); + toggle.setAttribute('aria-expanded', isOpen ? 'true' : 'false'); + }); + + // Close on any drawer link click + drawer.querySelectorAll('a').forEach((link) => { + link.addEventListener('click', () => { + drawer.classList.remove('open', 'active'); + toggle.setAttribute('aria-expanded', 'false'); + }); + }); + + // Close on click outside + document.addEventListener('click', (e) => { + const target = e.target as Node | null; + if (target && !drawer.contains(target) && !toggle.contains(target)) { + drawer.classList.remove('open', 'active'); + toggle.setAttribute('aria-expanded', 'false'); + } + }); + + // Close on Escape key + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape') { + drawer.classList.remove('open', 'active'); + toggle.setAttribute('aria-expanded', 'false'); + } + }); + } + + window.addEventListener( + 'scroll', + () => { + if (window.scrollY > 20) { + navbar?.classList.add('scrolled'); + } else { + navbar?.classList.remove('scrolled'); + } + }, + { passive: true }, + ); +} + +/** + * Smooth Scroll for anchor links is managed natively with history & clearance in anchor-links.ts + */ +export function initSmoothScroll(): void { + // Handled uniformly by initHeadingAnchorLinks in anchor-links.ts +} diff --git a/assets/ts/race-runner.ts b/assets/ts/race-runner.ts new file mode 100644 index 0000000..3d5da63 --- /dev/null +++ b/assets/ts/race-runner.ts @@ -0,0 +1,349 @@ +import { SupportedLanguage } from './types'; +import { codeSamples } from './samples-data'; + +export class TripleExecutionRaceEngine { + private deckEl: HTMLElement | null; + + // Cold Terminal Elements + private coldWindow: HTMLElement | null; + private coldLinesEl: HTMLElement | null; + private coldScreenEl: HTMLElement | null; + private coldCursorEl: HTMLElement | null; + + // Edit Terminal Elements + private editWindow: HTMLElement | null; + private editLinesEl: HTMLElement | null; + private editScreenEl: HTMLElement | null; + private editCursorEl: HTMLElement | null; + + // Cached (Zero-Work) Terminal Elements + private cachedWindow: HTMLElement | null; + private cachedLinesEl: HTMLElement | null; + private cachedScreenEl: HTMLElement | null; + private cachedCursorEl: HTMLElement | null; + + // Parallel Comparison Bar Elements + private parallelColdFill: HTMLElement | null; + private parallelColdVal: HTMLElement | null; + + private parallelEditFill: HTMLElement | null; + private parallelEditVal: HTMLElement | null; + private parallelEditTime: HTMLElement | null; + private parallelEditSpeedupBadge: HTMLElement | null; + + private parallelCachedFill: HTMLElement | null; + private parallelCachedTime: HTMLElement | null; + private parallelSpeedupBadge: HTMLElement | null; + + // Summary Metrics Elements + private summarySavedVal: HTMLElement | null; + private summaryCiVal: HTMLElement | null; + private summaryCo2Val: HTMLElement | null; + + private isPlaying: boolean = false; + private timeouts: number[] = []; + private rafId: number | null = null; + + constructor() { + this.deckEl = document.getElementById('stackedWindowsDeck'); + + this.coldWindow = document.getElementById('coldWindow'); + this.coldLinesEl = document.getElementById('coldLines'); + this.coldScreenEl = document.getElementById('coldScreen'); + this.coldCursorEl = document.getElementById('coldCursor'); + + this.editWindow = document.getElementById('editWindow'); + this.editLinesEl = document.getElementById('editLines'); + this.editScreenEl = document.getElementById('editScreen'); + this.editCursorEl = document.getElementById('editCursor'); + + this.cachedWindow = document.getElementById('cachedWindow'); + this.cachedLinesEl = document.getElementById('cachedLines'); + this.cachedScreenEl = document.getElementById('cachedScreen'); + this.cachedCursorEl = document.getElementById('cachedCursor'); + + this.parallelColdFill = document.getElementById('parallelColdFill'); + this.parallelColdVal = document.getElementById('parallelColdVal'); + + this.parallelEditFill = document.getElementById('parallelEditFill'); + this.parallelEditVal = document.getElementById('parallelEditVal'); + this.parallelEditTime = document.getElementById('parallelEditTime'); + this.parallelEditSpeedupBadge = document.getElementById('parallelEditSpeedupBadge'); + + this.parallelCachedFill = document.getElementById('parallelCachedFill'); + this.parallelCachedTime = document.getElementById('parallelCachedTime'); + this.parallelSpeedupBadge = document.getElementById('parallelSpeedupBadge'); + + this.summarySavedVal = document.getElementById('summarySavedVal'); + this.summaryCiVal = document.getElementById('summaryCiVal'); + this.summaryCo2Val = document.getElementById('summaryCo2Val'); + } + + public clear(): void { + this.timeouts.forEach((t) => clearTimeout(t)); + this.timeouts = []; + if (this.rafId !== null) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + + if (this.coldLinesEl) this.coldLinesEl.innerHTML = ''; + if (this.editLinesEl) this.editLinesEl.innerHTML = ''; + if (this.cachedLinesEl) this.cachedLinesEl.innerHTML = ''; + + if (this.parallelColdFill) this.parallelColdFill.style.width = '0%'; + if (this.parallelEditFill) this.parallelEditFill.style.width = '0%'; + if (this.parallelCachedFill) this.parallelCachedFill.style.width = '0%'; + + if (this.coldWindow) this.coldWindow.classList.remove('cold-pulse'); + if (this.editWindow) this.editWindow.classList.remove('edit-pulse'); + if (this.cachedWindow) this.cachedWindow.classList.remove('winner-pulse'); + this.isPlaying = false; + } + + public showStatic(lang: SupportedLanguage): void { + const sample = codeSamples[lang] || codeSamples.python; + this.clear(); + + const coldTimeSec = parseFloat(sample.metrics.coldTime) || 6.2; + const editTimeSec = parseFloat(sample.metrics.editTime) || 1.8; + const cachedTimeSec = parseFloat(sample.metrics.cachedTime) || 1.1; + + const editSpeedupFactor = (coldTimeSec / editTimeSec).toFixed(1); + const cachedSpeedupFactor = (coldTimeSec / cachedTimeSec).toFixed(1); + const computedEditSpeedup = `${editSpeedupFactor}x Faster`; + const computedCachedSpeedup = `${cachedSpeedupFactor}x Faster`; + + const savedSec = Math.max(0, coldTimeSec - cachedTimeSec); + const savedPct = Math.round((savedSec / coldTimeSec) * 100); + const computedSaved = `${savedPct}% Saved (${savedSec.toFixed(1)}s)`; + const computedCiSaved = `${savedPct}% Fewer Runner Mins`; + const computedCo2Saved = `~${(savedSec * 0.25).toFixed(1)}g CO₂e / build`; + + const proportionalEditTargetPct = (editTimeSec / coldTimeSec) * 100; + const proportionalCachedTargetPct = (cachedTimeSec / coldTimeSec) * 100; + + if (this.summarySavedVal) this.summarySavedVal.textContent = computedSaved; + if (this.summaryCiVal) this.summaryCiVal.textContent = computedCiSaved; + if (this.summaryCo2Val) this.summaryCo2Val.textContent = computedCo2Saved; + + if (this.parallelColdVal) this.parallelColdVal.textContent = sample.metrics.coldTime; + if (this.parallelColdFill) this.parallelColdFill.style.width = '100%'; + + if (this.parallelEditTime) this.parallelEditTime.textContent = sample.metrics.editTime; + if (this.parallelEditFill) + this.parallelEditFill.style.width = `${proportionalEditTargetPct.toFixed(2)}%`; + if (this.parallelEditSpeedupBadge) + this.parallelEditSpeedupBadge.textContent = computedEditSpeedup; + + if (this.parallelCachedTime) this.parallelCachedTime.textContent = sample.metrics.cachedTime; + if (this.parallelCachedFill) + this.parallelCachedFill.style.width = `${proportionalCachedTargetPct.toFixed(2)}%`; + if (this.parallelSpeedupBadge) this.parallelSpeedupBadge.textContent = computedCachedSpeedup; + + const coldPill = document.getElementById('coldTimePill'); + const editPill = document.getElementById('editTimePill'); + const cachedPill = document.getElementById('cachedTimePill'); + if (coldPill) coldPill.textContent = sample.metrics.coldTime; + if (editPill) editPill.textContent = sample.metrics.editTime; + if (cachedPill) cachedPill.textContent = sample.metrics.cachedTime; + + const appendLines = ( + linesEl: HTMLElement | null, + screenEl: HTMLElement | null, + lines: Array<{ text: string; class?: string }>, + ) => { + if (!linesEl) return; + const frag = document.createDocumentFragment(); + lines.forEach((item) => { + const line = document.createElement('div'); + line.className = `player-terminal-line ${item.class || ''}`; + line.textContent = item.text; + frag.appendChild(line); + }); + linesEl.appendChild(frag); + if (screenEl) { + screenEl.scrollTop = screenEl.scrollHeight; + } + }; + + appendLines(this.coldLinesEl, this.coldScreenEl, sample.cold); + appendLines(this.editLinesEl, this.editScreenEl, sample.edit); + appendLines(this.cachedLinesEl, this.cachedScreenEl, sample.cached); + + if (this.coldWindow) this.coldWindow.classList.add('cold-pulse'); + if (this.editWindow) this.editWindow.classList.add('edit-pulse'); + if (this.cachedWindow) this.cachedWindow.classList.add('winner-pulse'); + } + + public startRace(lang: SupportedLanguage): void { + const sample = codeSamples[lang] || codeSamples.python; + this.clear(); + + const coldTimeSec = parseFloat(sample.metrics.coldTime) || 6.2; + const editTimeSec = parseFloat(sample.metrics.editTime) || 1.8; + const cachedTimeSec = parseFloat(sample.metrics.cachedTime) || 1.1; + + // Dynamically compute speedups & ROI impact metrics directly from measured execution times + const editSpeedupFactor = (coldTimeSec / editTimeSec).toFixed(1); + const cachedSpeedupFactor = (coldTimeSec / cachedTimeSec).toFixed(1); + const computedEditSpeedup = `${editSpeedupFactor}x Faster`; + const computedCachedSpeedup = `${cachedSpeedupFactor}x Faster`; + + const savedSec = Math.max(0, coldTimeSec - cachedTimeSec); + const savedPct = Math.round((savedSec / coldTimeSec) * 100); + const computedSaved = `${savedPct}% Saved (${savedSec.toFixed(1)}s)`; + const computedCiSaved = `${savedPct}% Fewer Runner Mins`; + const computedCo2Saved = `~${(savedSec * 0.25).toFixed(1)}g CO₂e / build`; + + const coldDurationMs = coldTimeSec * 1000; + const editDurationMs = editTimeSec * 1000; + const cachedDurationMs = cachedTimeSec * 1000; + + const proportionalEditTargetPct = (editTimeSec / coldTimeSec) * 100; + const proportionalCachedTargetPct = (cachedTimeSec / coldTimeSec) * 100; + + // Update Summary Metric Values dynamically computed + if (this.summarySavedVal) this.summarySavedVal.textContent = computedSaved; + if (this.summaryCiVal) this.summaryCiVal.textContent = computedCiSaved; + if (this.summaryCo2Val) this.summaryCo2Val.textContent = computedCo2Saved; + + if (this.parallelColdVal) + this.parallelColdVal.textContent = `0.0s / ${sample.metrics.coldTime}`; + if (this.parallelEditTime) this.parallelEditTime.textContent = '0.0s'; + if (this.parallelEditSpeedupBadge) + this.parallelEditSpeedupBadge.textContent = computedEditSpeedup; + if (this.parallelCachedTime) this.parallelCachedTime.textContent = '0.0s'; + if (this.parallelSpeedupBadge) this.parallelSpeedupBadge.textContent = computedCachedSpeedup; + + // Update Header Time Pills in Horizontal Cards + const coldPill = document.getElementById('coldTimePill'); + const editPill = document.getElementById('editTimePill'); + const cachedPill = document.getElementById('cachedTimePill'); + if (coldPill) coldPill.textContent = sample.metrics.coldTime; + if (editPill) editPill.textContent = sample.metrics.editTime; + if (cachedPill) cachedPill.textContent = sample.metrics.cachedTime; + + this.isPlaying = true; + const startTime = performance.now(); + + // High fidelity real-time frame loop for all 3 parallel progress timelines on shared scale + const updateProgress = () => { + if (!this.isPlaying) return; + const now = performance.now(); + const elapsed = now - startTime; + + // 1. Update Zero-Work Rebuild (Repeat/Cached) Progress + if (elapsed <= cachedDurationMs) { + const cachedFraction = elapsed / cachedDurationMs; + const currentCachedPct = cachedFraction * proportionalCachedTargetPct; + if (this.parallelCachedFill) + this.parallelCachedFill.style.width = `${currentCachedPct.toFixed(2)}%`; + const currentCachedSec = (cachedFraction * cachedTimeSec).toFixed(1); + if (this.parallelCachedTime) this.parallelCachedTime.textContent = `${currentCachedSec}s`; + } else { + if (this.parallelCachedFill) + this.parallelCachedFill.style.width = `${proportionalCachedTargetPct.toFixed(2)}%`; + if (this.parallelCachedTime) + this.parallelCachedTime.textContent = sample.metrics.cachedTime; + if (this.parallelSpeedupBadge) + this.parallelSpeedupBadge.textContent = computedCachedSpeedup; + } + + // 2. Update Incremental Build Progress + if (elapsed <= editDurationMs) { + const editFraction = elapsed / editDurationMs; + const currentEditPct = editFraction * proportionalEditTargetPct; + if (this.parallelEditFill) + this.parallelEditFill.style.width = `${currentEditPct.toFixed(2)}%`; + const currentEditSec = (editFraction * editTimeSec).toFixed(1); + if (this.parallelEditTime) this.parallelEditTime.textContent = `${currentEditSec}s`; + } else { + if (this.parallelEditFill) + this.parallelEditFill.style.width = `${proportionalEditTargetPct.toFixed(2)}%`; + if (this.parallelEditTime) this.parallelEditTime.textContent = sample.metrics.editTime; + if (this.parallelEditSpeedupBadge) + this.parallelEditSpeedupBadge.textContent = computedEditSpeedup; + } + + // 3. Update Clean Build Progress + if (elapsed <= coldDurationMs) { + const coldFraction = elapsed / coldDurationMs; + const coldPct = Math.min(100, coldFraction * 100); + if (this.parallelColdFill) this.parallelColdFill.style.width = `${coldPct.toFixed(2)}%`; + const currentColdSec = (coldFraction * coldTimeSec).toFixed(1); + if (this.parallelColdVal) + this.parallelColdVal.textContent = `${currentColdSec}s / ${sample.metrics.coldTime}`; + this.rafId = requestAnimationFrame(updateProgress); + } else { + this.isPlaying = false; + if (this.parallelColdFill) this.parallelColdFill.style.width = '100%'; + if (this.parallelColdVal) this.parallelColdVal.textContent = sample.metrics.coldTime; + } + }; + this.rafId = requestAnimationFrame(updateProgress); + + // Stream Zero-Work Rebuild Logs in Real Time + sample.cached.forEach((item, index) => { + const delay = item.delay; + const timer = window.setTimeout(() => { + const line = document.createElement('div'); + line.className = `player-terminal-line ${item.class || ''}`; + line.textContent = item.text; + if (this.cachedLinesEl) { + this.cachedLinesEl.appendChild(line); + if (this.cachedScreenEl) this.cachedScreenEl.scrollTop = this.cachedScreenEl.scrollHeight; + } + + // Zero-Work Finishes + if (index === sample.cached.length - 1) { + if (this.cachedWindow) this.cachedWindow.classList.add('winner-pulse'); + } + }, delay); + this.timeouts.push(timer); + }); + + // Stream Incremental Build Logs in Real Time + sample.edit.forEach((item, index) => { + const delay = item.delay; + const timer = window.setTimeout(() => { + const line = document.createElement('div'); + line.className = `player-terminal-line ${item.class || ''}`; + line.textContent = item.text; + if (this.editLinesEl) { + this.editLinesEl.appendChild(line); + if (this.editScreenEl) this.editScreenEl.scrollTop = this.editScreenEl.scrollHeight; + } + + // Incremental Finishes + if (index === sample.edit.length - 1) { + if (this.editWindow) this.editWindow.classList.add('edit-pulse'); + } + }, delay); + this.timeouts.push(timer); + }); + + // Stream Clean Build Logs in Real Time + sample.cold.forEach((item, index) => { + const delay = item.delay; + const timer = window.setTimeout(() => { + const line = document.createElement('div'); + line.className = `player-terminal-line ${item.class || ''}`; + line.textContent = item.text; + if (this.coldLinesEl) { + this.coldLinesEl.appendChild(line); + if (this.coldScreenEl) this.coldScreenEl.scrollTop = this.coldScreenEl.scrollHeight; + } + + // Clean Build Finishes + if (index === sample.cold.length - 1) { + this.isPlaying = false; + if (this.coldWindow) this.coldWindow.classList.add('cold-pulse'); + if (this.parallelColdFill) this.parallelColdFill.style.width = '100%'; + if (this.parallelColdVal) this.parallelColdVal.textContent = sample.metrics.coldTime; + } + }, delay); + this.timeouts.push(timer); + }); + } +} diff --git a/assets/ts/roi.ts b/assets/ts/roi.ts new file mode 100644 index 0000000..84552d3 --- /dev/null +++ b/assets/ts/roi.ts @@ -0,0 +1,193 @@ +/** + * ROI & CI Compute Cost Calculator + * Supports dynamic URL synchronization so customized calculations can be shared and bookmarked. + */ + +import { showCopyToast } from './anchor-links'; +import { copyTextToClipboard } from './clipboard'; + +interface RoiParams { + team?: number; + builds?: number; + mins?: number; +} + +function parseUrlParams(): RoiParams { + const searchParams = new URLSearchParams(window.location.search); + let team = searchParams.get('team') || searchParams.get('teamSize'); + let builds = searchParams.get('builds') || searchParams.get('buildsPerDay'); + let mins = searchParams.get('mins') || searchParams.get('minutesSaved'); + + // Also check query string inside hash if someone copied an in-hash query + if ((!team || !builds || !mins) && window.location.hash.includes('?')) { + const hashQuery = window.location.hash.split('?')[1]; + const hashParams = new URLSearchParams(hashQuery); + if (!team) team = hashParams.get('team') || hashParams.get('teamSize'); + if (!builds) builds = hashParams.get('builds') || hashParams.get('buildsPerDay'); + if (!mins) mins = hashParams.get('mins') || hashParams.get('minutesSaved'); + } + + const parseNum = (v: string | null): number | undefined => { + if (!v) return undefined; + const n = parseInt(v, 10); + return isNaN(n) ? undefined : n; + }; + + return { + team: parseNum(team), + builds: parseNum(builds), + mins: parseNum(mins), + }; +} + +function buildShareUrl(team: number, builds: number, minsSaved: number): string { + const url = new URL(window.location.href); + url.searchParams.set('team', String(team)); + url.searchParams.set('builds', String(builds)); + url.searchParams.set('mins', String(minsSaved)); + url.hash = 'calculator'; + return url.toString(); +} + +export function initRoiCalculator(): void { + const teamSizeInput = document.getElementById('roiTeamSize') as HTMLInputElement | null; + const buildsPerDayInput = document.getElementById('roiBuildsPerDay') as HTMLInputElement | null; + const minutesSavedInput = document.getElementById('roiMinutesSaved') as HTMLInputElement | null; + + const teamSizeVal = document.getElementById('roiTeamSizeVal'); + const buildsPerDayVal = document.getElementById('roiBuildsPerDayVal'); + const minutesSavedVal = document.getElementById('roiMinutesSavedVal'); + + const hoursSavedResult = document.getElementById('roiHoursSaved'); + const hoursPerDevResult = document.getElementById('roiHoursPerDev'); + const runnerMinutesResult = document.getElementById('roiRunnerMinutesSaved'); + const vcpuHoursResult = document.getElementById('roiVcpuHours'); + const co2SavedResult = document.getElementById('roiCo2Saved'); + const co2EquivalentResult = document.getElementById('roiCo2Equivalent'); + + const shareBtn = document.getElementById('roiCopyShareBtn'); + + // 1. Initialize from URL params if provided + const initialParams = parseUrlParams(); + if (teamSizeInput && initialParams.team !== undefined) { + const clampedTeam = Math.max(1, Math.min(100, initialParams.team)); + teamSizeInput.value = String(clampedTeam); + } + if (buildsPerDayInput && initialParams.builds !== undefined) { + const clampedBuilds = Math.max(1, Math.min(25, initialParams.builds)); + buildsPerDayInput.value = String(clampedBuilds); + } + if (minutesSavedInput && initialParams.mins !== undefined) { + const clampedMins = Math.max(2, Math.min(30, initialParams.mins)); + minutesSavedInput.value = String(clampedMins); + } + + let updateUrlTimeout: number | null = null; + + function calculate(syncUrl = true): void { + const team = parseInt(teamSizeInput?.value || '20', 10); + const builds = parseInt(buildsPerDayInput?.value || '8', 10); + const minsSaved = parseInt(minutesSavedInput?.value || '12', 10); + + if (teamSizeVal) teamSizeVal.innerText = `${team} devs`; + if (buildsPerDayVal) buildsPerDayVal.innerText = `${builds} builds/day`; + if (minutesSavedVal) minutesSavedVal.innerText = `${minsSaved} mins`; + + // Monthly math: 21 working days per month + const totalBuildsPerMonth = team * builds * 21; + const totalMinutesSaved = totalBuildsPerMonth * minsSaved; + const totalHoursSaved = Math.round(totalMinutesSaved / 60); + const hoursPerDev = team > 0 ? Math.round(totalHoursSaved / team) : 0; + + // CI Runner Capacity: Standard 2-vCPU runner baseline (e.g. GitHub Actions ubuntu-latest) + const vcpuHours = Math.round((totalMinutesSaved * 2) / 60); + + // Environmental Impact: ~15g CO2e per active cloud runner minute + // Data center grid carbon intensity: ~450-500g CO2e / kWh with cooling PUE + const totalKgCo2 = Math.round((totalMinutesSaved * 15) / 1000); + const totalKwh = Math.round(totalKgCo2 * 2); // ~1 kWh per 500g CO2e + + if (hoursSavedResult) { + hoursSavedResult.innerText = `${totalHoursSaved.toLocaleString()} hrs`; + } + if (hoursPerDevResult) { + hoursPerDevResult.innerText = `≈ ~${hoursPerDev.toLocaleString()} hrs saved / dev / month`; + } + if (runnerMinutesResult) { + runnerMinutesResult.innerText = `${totalMinutesSaved.toLocaleString()} mins`; + } + if (vcpuHoursResult) { + vcpuHoursResult.innerText = `≈ ${vcpuHours.toLocaleString()} vCPU-hrs (2-vCPU runner baseline)`; + } + if (co2SavedResult) { + if (totalKgCo2 >= 1000) { + co2SavedResult.innerText = `${(totalKgCo2 / 1000).toFixed(1)} t CO₂e`; + } else { + co2SavedResult.innerText = `${totalKgCo2.toLocaleString()} kg CO₂e`; + } + } + if (co2EquivalentResult) { + if (totalKwh >= 1000) { + co2EquivalentResult.innerText = `≈ ~${(totalKwh / 1000).toFixed(1)} MWh data center energy spared`; + } else { + co2EquivalentResult.innerText = `≈ ~${totalKwh.toLocaleString()} kWh data center energy spared`; + } + } + + // Debounce URL replacement so dragging sliders is butter-smooth + if (syncUrl) { + if (updateUrlTimeout !== null) { + window.clearTimeout(updateUrlTimeout); + } + updateUrlTimeout = window.setTimeout(() => { + const shareUrl = buildShareUrl(team, builds, minsSaved); + window.history.replaceState(null, '', shareUrl); + }, 100); + } + } + + // 2. Wire up input listeners + [teamSizeInput, buildsPerDayInput, minutesSavedInput].forEach((input) => { + input?.addEventListener('input', () => calculate(true)); + }); + + // 3. Share / Copy Link Button at the end of calculator + if (shareBtn) { + shareBtn.addEventListener('click', async () => { + const team = parseInt(teamSizeInput?.value || '20', 10); + const builds = parseInt(buildsPerDayInput?.value || '8', 10); + const minsSaved = parseInt(minutesSavedInput?.value || '12', 10); + const shareUrl = buildShareUrl(team, builds, minsSaved); + + try { + window.history.replaceState(null, '', shareUrl); + } catch { + // ignore + } + + await copyTextToClipboard(shareUrl); + + // Visual feedback on button + const textSpan = shareBtn.querySelector('.roi-copy-btn-text'); + const linkIcon = shareBtn.querySelector('.roi-btn-link-icon'); + const checkIcon = shareBtn.querySelector('.roi-btn-check-icon'); + + shareBtn.classList.add('copied'); + if (textSpan) textSpan.textContent = 'Link Copied!'; + if (linkIcon) linkIcon.style.display = 'none'; + if (checkIcon) checkIcon.style.display = 'inline-block'; + + showCopyToast(`Calculator link (${team} devs, ${minsSaved}m saved) copied!`); + + setTimeout(() => { + shareBtn.classList.remove('copied'); + if (textSpan) textSpan.textContent = 'Share Calculations'; + if (linkIcon) linkIcon.style.display = 'inline-block'; + if (checkIcon) checkIcon.style.display = 'none'; + }, 2200); + }); + } + + // Run initial calculation without polluting history if no params in URL + calculate(Boolean(initialParams.team || initialParams.builds || initialParams.mins)); +} diff --git a/assets/ts/samples-data.ts b/assets/ts/samples-data.ts new file mode 100644 index 0000000..7cf9ecd --- /dev/null +++ b/assets/ts/samples-data.ts @@ -0,0 +1,1137 @@ +import { CodeSamplesMap } from './types'; + +export const codeSamples: CodeSamplesMap = { + go: { + filename: 'examples/go/Earthfile', + repoUrl: 'https://github.com/earthbuild/earthbuild/tree/main/examples/go', + metrics: { + cachedTime: '1.1s', + editTime: '1.8s', + coldTime: '6.2s', + editSpeedup: '3.4x Faster', + speedup: '5.6x Faster', + saved: '82% Saved (5.1s)', + ciSaved: '82% Fewer Runner Mins', + co2Saved: '~1.4g CO₂e / build', + }, + code: `VERSION 0.8 +FROM golang:1.27-alpine3.24 +WORKDIR /go-example + +# deps caches vendor dependencies independently +deps: + COPY go.mod go.sum ./ + RUN go mod download + SAVE ARTIFACT go.mod AS LOCAL go.mod + SAVE ARTIFACT go.sum AS LOCAL go.sum + +# build compiles standalone Go binary with instant layer cache +build: + FROM +deps + COPY main.go . + RUN go build -o build/go-example main.go + SAVE ARTIFACT build/go-example /go-example AS LOCAL build/go-example`, + cached: [ + { text: '$ earth +build', class: 'term-cmd', delay: 80 }, + { + text: '─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)', + class: 'term-dim', + delay: 180, + }, + { text: '─── Build 🔧', class: 'term-bold', delay: 260 }, + { + text: ' +base | *cached* --> FROM golang:1.27-alpine3.24', + class: 'term-cached', + delay: 360, + }, + { text: ' +base | *cached* --> WORKDIR /go-example', class: 'term-cached', delay: 460 }, + { text: ' +deps | *cached* --> COPY go.mod go.sum ./', class: 'term-cached', delay: 560 }, + { text: ' +deps | *cached* --> RUN go mod download', class: 'term-cached', delay: 660 }, + { text: ' +build | *cached* --> COPY main.go .', class: 'term-cached', delay: 760 }, + { + text: ' +build | *cached* --> RUN go build -o build/go-example main.go', + class: 'term-cached', + delay: 860, + }, + { + text: ' +build | *cached* --> SAVE ARTIFACT build/go-example AS LOCAL build/go-example', + class: 'term-cached', + delay: 960, + }, + { + text: ' output | [----------] 100% exporting outputs', + class: 'terminal-line', + delay: 1020, + }, + { + text: '🎁 Artifact +build/go-example output as build/go-example', + class: 'term-running', + delay: 1060, + }, + { + text: '============= 🌍 Earth Build ✅ SUCCESS [1.1s] =============', + class: 'term-success', + delay: 1100, + }, + ], + edit: [ + { text: '$ earth +build', class: 'term-cmd', delay: 80 }, + { + text: '─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)', + class: 'term-dim', + delay: 180, + }, + { text: '─── Build 🔧', class: 'term-bold', delay: 260 }, + { + text: ' +base | *cached* --> FROM golang:1.27-alpine3.24', + class: 'term-cached', + delay: 360, + }, + { text: ' +base | *cached* --> WORKDIR /go-example', class: 'term-cached', delay: 460 }, + { text: ' +deps | *cached* --> COPY go.mod go.sum ./', class: 'term-cached', delay: 560 }, + { text: ' +deps | *cached* --> RUN go mod download', class: 'term-cached', delay: 660 }, + { text: ' +build | --> COPY main.go .', class: 'terminal-line', delay: 900 }, + { + text: ' +build | --> RUN go build -o build/go-example main.go', + class: 'term-yellow', + delay: 1300, + }, + { + text: ' +build | --> SAVE ARTIFACT build/go-example AS LOCAL build/go-example', + class: 'terminal-line', + delay: 1550, + }, + { + text: ' output | [----------] 100% exporting outputs', + class: 'terminal-line', + delay: 1650, + }, + { + text: '🎁 Artifact +build/go-example output as build/go-example', + class: 'term-running', + delay: 1720, + }, + { + text: '============= 🌍 Earth Build ✅ SUCCESS [1.8s] =============', + class: 'term-success', + delay: 1800, + }, + ], + cold: [ + { text: '$ earth +build', class: 'term-cmd', delay: 80 }, + { + text: '─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)', + class: 'term-dim', + delay: 250, + }, + { text: '─── Build 🔧', class: 'term-bold', delay: 450 }, + { text: ' +base | --> FROM golang:1.27-alpine3.24', class: 'terminal-line', delay: 900 }, + { text: ' +base | --> WORKDIR /go-example', class: 'terminal-line', delay: 1400 }, + { text: ' +deps | --> COPY go.mod go.sum ./', class: 'terminal-line', delay: 1900 }, + { text: ' +deps | --> RUN go mod download', class: 'term-yellow', delay: 2700 }, + { text: ' +build | --> COPY main.go .', class: 'terminal-line', delay: 3800 }, + { + text: ' +build | --> RUN go build -o build/go-example main.go', + class: 'term-yellow', + delay: 4900, + }, + { + text: ' +build | --> SAVE ARTIFACT build/go-example AS LOCAL build/go-example', + class: 'terminal-line', + delay: 5600, + }, + { + text: ' output | [----------] 100% exporting outputs', + class: 'terminal-line', + delay: 5900, + }, + { + text: '🎁 Artifact +build/go-example output as build/go-example', + class: 'term-running', + delay: 6050, + }, + { + text: '============= 🌍 Earth Build ✅ SUCCESS [6.2s] =============', + class: 'term-success', + delay: 6200, + }, + ], + }, + cpp: { + filename: 'examples/cpp/Earthfile', + repoUrl: 'https://github.com/earthbuild/earthbuild/tree/main/examples/cpp', + metrics: { + cachedTime: '0.9s', + editTime: '2.6s', + coldTime: '22.8s', + editSpeedup: '8.8x Faster', + speedup: '25.3x Faster', + saved: '96% Saved (21.9s)', + ciSaved: '96% Fewer Runner Mins', + co2Saved: '~5.5g CO₂e / build', + }, + code: `VERSION 0.8 +FROM ubuntu:26.04 +RUN apt-get update && apt-get install -y build-essential cmake +WORKDIR /code + +# code copies C++ source tree +code: + COPY src src + +# build compiles with CMake & persistent compiler cache mount +build: + FROM +code + COPY CMakeLists.txt . + RUN --mount=type=cache,target=/root/.cache/ccache cmake -B build -S . && cmake --build build + SAVE ARTIFACT build/app AS LOCAL build/app`, + cached: [ + { text: '$ earth +build', class: 'term-cmd', delay: 80 }, + { + text: '─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)', + class: 'term-dim', + delay: 150, + }, + { text: '─── Build 🔧', class: 'term-bold', delay: 220 }, + { text: ' +base | *cached* --> FROM ubuntu:26.04', class: 'term-cached', delay: 300 }, + { + text: ' +base | *cached* --> RUN apt-get update && apt-get install -y build-essential cmake', + class: 'term-cached', + delay: 400, + }, + { text: ' +code | *cached* --> COPY src src', class: 'term-cached', delay: 490 }, + { text: ' +build | *cached* --> COPY CMakeLists.txt .', class: 'term-cached', delay: 580 }, + { + text: ' +build | *cached* --> RUN --mount=type=cache,target=/root/.cache/ccache cmake -B build -S . && cmake --build build', + class: 'term-cached', + delay: 680, + }, + { + text: ' +build | *cached* --> SAVE ARTIFACT build/app AS LOCAL build/app', + class: 'term-cached', + delay: 780, + }, + { + text: ' output | [----------] 100% exporting outputs', + class: 'terminal-line', + delay: 830, + }, + { text: '🎁 Artifact +build/app output as build/app', class: 'term-running', delay: 870 }, + { + text: '============= 🌍 Earth Build ✅ SUCCESS [0.9s] =============', + class: 'term-success', + delay: 900, + }, + ], + edit: [ + { text: '$ earth +build', class: 'term-cmd', delay: 80 }, + { + text: '─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)', + class: 'term-dim', + delay: 180, + }, + { text: '─── Build 🔧', class: 'term-bold', delay: 260 }, + { text: ' +base | *cached* --> FROM ubuntu:26.04', class: 'term-cached', delay: 360 }, + { + text: ' +base | *cached* --> RUN apt-get update && apt-get install -y build-essential cmake', + class: 'term-cached', + delay: 460, + }, + { text: ' +code | --> COPY src src', class: 'term-yellow', delay: 750 }, + { text: ' +build | *cached* --> COPY CMakeLists.txt .', class: 'term-cached', delay: 950 }, + { + text: ' +build | --> RUN --mount=type=cache,target=/root/.cache/ccache cmake -B build -S . && cmake --build build', + class: 'term-yellow', + delay: 1800, + }, + { + text: ' +build | [100%] Linking CXX executable build/app', + class: 'terminal-line', + delay: 2200, + }, + { + text: ' +build | --> SAVE ARTIFACT build/app AS LOCAL build/app', + class: 'terminal-line', + delay: 2400, + }, + { + text: '============= 🌍 Earth Build ✅ SUCCESS [2.6s] =============', + class: 'term-success', + delay: 2600, + }, + ], + cold: [ + { text: '$ earth +build', class: 'term-cmd', delay: 80 }, + { + text: '─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)', + class: 'term-dim', + delay: 300, + }, + { text: '─── Build 🔧', class: 'term-bold', delay: 600 }, + { text: ' +base | --> FROM ubuntu:26.04', class: 'terminal-line', delay: 1500 }, + { + text: ' +base | --> RUN apt-get update && apt-get install -y build-essential cmake', + class: 'term-yellow', + delay: 6500, + }, + { text: ' +code | --> COPY src src', class: 'terminal-line', delay: 10500 }, + { text: ' +build | --> COPY CMakeLists.txt .', class: 'terminal-line', delay: 12000 }, + { + text: ' +build | --> RUN --mount=type=cache,target=/root/.cache/ccache cmake -B build -S . && cmake --build build', + class: 'terminal-line', + delay: 14000, + }, + { + text: ' +build | [ 33%] Building CXX object CMakeFiles/app.dir/main.cpp.o', + class: 'terminal-line', + delay: 17000, + }, + { + text: ' +build | [ 66%] Building CXX object CMakeFiles/app.dir/util.cpp.o', + class: 'terminal-line', + delay: 19000, + }, + { + text: ' +build | [100%] Linking CXX executable build/app', + class: 'terminal-line', + delay: 21000, + }, + { + text: ' +build | --> SAVE ARTIFACT build/app AS LOCAL build/app', + class: 'terminal-line', + delay: 22200, + }, + { + text: '============= 🌍 Earth Build ✅ SUCCESS [22.8s] =============', + class: 'term-success', + delay: 22800, + }, + ], + }, + python: { + filename: 'examples/python/Earthfile', + repoUrl: 'https://github.com/earthbuild/earthbuild/tree/main/examples/python', + metrics: { + cachedTime: '0.8s', + editTime: '1.8s', + coldTime: '14.5s', + editSpeedup: '8.1x Faster', + speedup: '18.1x Faster', + saved: '94% Saved (13.7s)', + ciSaved: '94% Fewer Runner Mins', + co2Saved: '~3.5g CO₂e / build', + }, + code: `VERSION 0.8 +FROM python:3.14-slim +WORKDIR /app + +# deps caches pip dependencies in isolated container layer +deps: + COPY requirements.txt . + RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt + +# test runs unit tests inside isolated container +test: + FROM +deps + COPY src src + RUN pytest src/`, + cached: [ + { text: '$ earth +test', class: 'term-cmd', delay: 80 }, + { + text: '─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)', + class: 'term-dim', + delay: 150, + }, + { text: '─── Build 🔧', class: 'term-bold', delay: 220 }, + { text: ' +deps | *cached* --> FROM python:3.14-slim', class: 'term-cached', delay: 300 }, + { text: ' +deps | *cached* --> WORKDIR /app', class: 'term-cached', delay: 390 }, + { text: ' +deps | *cached* --> COPY requirements.txt .', class: 'term-cached', delay: 480 }, + { + text: ' +deps | *cached* --> RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt', + class: 'term-cached', + delay: 580, + }, + { text: ' +test | *cached* --> COPY src src', class: 'term-cached', delay: 680 }, + { text: ' +test | *cached* --> RUN pytest src/', class: 'term-cached', delay: 750 }, + { + text: '============= 🌍 Earth Build ✅ SUCCESS [0.8s] =============', + class: 'term-success', + delay: 800, + }, + ], + edit: [ + { text: '$ earth +test', class: 'term-cmd', delay: 80 }, + { + text: '─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)', + class: 'term-dim', + delay: 180, + }, + { text: '─── Build 🔧', class: 'term-bold', delay: 260 }, + { text: ' +deps | *cached* --> FROM python:3.14-slim', class: 'term-cached', delay: 360 }, + { text: ' +deps | *cached* --> WORKDIR /app', class: 'term-cached', delay: 460 }, + { text: ' +deps | *cached* --> COPY requirements.txt .', class: 'term-cached', delay: 560 }, + { + text: ' +deps | *cached* --> RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt', + class: 'term-cached', + delay: 660, + }, + { text: ' +test | --> COPY src src', class: 'term-yellow', delay: 850 }, + { text: ' +test | --> RUN pytest src/', class: 'term-yellow', delay: 1400 }, + { + text: ' +test | ================= 14 passed in 0.23s =================', + class: 'terminal-line', + delay: 1650, + }, + { + text: '============= 🌍 Earth Build ✅ SUCCESS [1.8s] =============', + class: 'term-success', + delay: 1800, + }, + ], + cold: [ + { text: '$ earth +test', class: 'term-cmd', delay: 80 }, + { + text: '─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)', + class: 'term-dim', + delay: 300, + }, + { text: '─── Build 🔧', class: 'term-bold', delay: 600 }, + { text: ' +deps | --> FROM python:3.14-slim', class: 'terminal-line', delay: 1800 }, + { text: ' +deps | --> WORKDIR /app', class: 'terminal-line', delay: 3000 }, + { text: ' +deps | --> COPY requirements.txt .', class: 'terminal-line', delay: 4500 }, + { + text: ' +deps | --> RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt', + class: 'term-yellow', + delay: 7500, + }, + { text: ' +test | --> COPY src src', class: 'terminal-line', delay: 11000 }, + { text: ' +test | --> RUN pytest src/', class: 'term-yellow', delay: 13200 }, + { + text: ' +test | ================= 14 passed in 0.45s =================', + class: 'terminal-line', + delay: 14000, + }, + { + text: '============= 🌍 Earth Build ✅ SUCCESS [14.5s] =============', + class: 'term-success', + delay: 14500, + }, + ], + }, + rust: { + filename: 'examples/rust/Earthfile', + repoUrl: 'https://github.com/earthbuild/earthbuild/tree/main/examples/rust', + metrics: { + cachedTime: '1.4s', + editTime: '4.2s', + coldTime: '38.4s', + editSpeedup: '9.1x Faster', + speedup: '27.4x Faster', + saved: '96% Saved (37.0s)', + ciSaved: '96% Fewer Runner Mins', + co2Saved: '~9.2g CO₂e / build', + }, + code: `VERSION 0.8 +FROM rust:1.85-alpine +WORKDIR /rust-app + +# deps pre-warms cargo registry & dependency build cache +deps: + COPY Cargo.toml Cargo.lock ./ + RUN mkdir src && echo "fn main() {}" > src/main.rs + RUN cargo build --release + +# build compiles release binary using persistent sccache mount +build: + FROM +deps + COPY src src + RUN --mount=type=cache,target=/usr/local/cargo/registry cargo build --release + SAVE ARTIFACT target/release/app AS LOCAL bin/app`, + cached: [ + { text: '$ earth +build', class: 'term-cmd', delay: 80 }, + { + text: '─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)', + class: 'term-dim', + delay: 180, + }, + { text: '─── Build 🔧', class: 'term-bold', delay: 260 }, + { text: ' +base | *cached* --> FROM rust:1.85-alpine', class: 'term-cached', delay: 380 }, + { text: ' +base | *cached* --> WORKDIR /rust-app', class: 'term-cached', delay: 460 }, + { + text: ' +deps | *cached* --> COPY Cargo.toml Cargo.lock ./', + class: 'term-cached', + delay: 560, + }, + { + text: ' +deps | *cached* --> RUN cargo build --release', + class: 'term-cached', + delay: 680, + }, + { text: ' +build | *cached* --> COPY src src', class: 'term-cached', delay: 780 }, + { + text: ' +build | *cached* --> RUN --mount=type=cache,target=/usr/local/cargo/registry cargo build --release', + class: 'term-cached', + delay: 900, + }, + { + text: ' +build | *cached* --> SAVE ARTIFACT target/release/app AS LOCAL bin/app', + class: 'term-cached', + delay: 1050, + }, + { + text: ' output | [----------] 100% exporting outputs', + class: 'terminal-line', + delay: 1180, + }, + { text: '🎁 Artifact +build/app output as bin/app', class: 'term-running', delay: 1280 }, + { + text: '============= 🌍 Earth Build ✅ SUCCESS [1.4s] =============', + class: 'term-success', + delay: 1400, + }, + ], + edit: [ + { text: '$ earth +build', class: 'term-cmd', delay: 80 }, + { + text: '─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)', + class: 'term-dim', + delay: 180, + }, + { text: '─── Build 🔧', class: 'term-bold', delay: 260 }, + { text: ' +base | *cached* --> FROM rust:1.85-alpine', class: 'term-cached', delay: 380 }, + { text: ' +base | *cached* --> WORKDIR /rust-app', class: 'term-cached', delay: 460 }, + { + text: ' +deps | *cached* --> COPY Cargo.toml Cargo.lock ./', + class: 'term-cached', + delay: 560, + }, + { + text: ' +deps | *cached* --> RUN cargo build --release', + class: 'term-cached', + delay: 680, + }, + { text: ' +build | --> COPY src src', class: 'term-yellow', delay: 1100 }, + { + text: ' +build | --> RUN --mount=type=cache,target=/usr/local/cargo/registry cargo build --release', + class: 'term-yellow', + delay: 2400, + }, + { + text: ' +build | Compiling app v0.1.0 (/rust-app)', + class: 'terminal-line', + delay: 3500, + }, + { + text: ' +build | --> SAVE ARTIFACT target/release/app AS LOCAL bin/app', + class: 'terminal-line', + delay: 3950, + }, + { + text: '============= 🌍 Earth Build ✅ SUCCESS [4.2s] =============', + class: 'term-success', + delay: 4200, + }, + ], + cold: [ + { text: '$ earth +build', class: 'term-cmd', delay: 80 }, + { + text: '─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)', + class: 'term-dim', + delay: 300, + }, + { text: '─── Build 🔧', class: 'term-bold', delay: 600 }, + { text: ' +base | --> FROM rust:1.85-alpine', class: 'terminal-line', delay: 1500 }, + { text: ' +base | --> WORKDIR /rust-app', class: 'terminal-line', delay: 3000 }, + { text: ' +deps | --> COPY Cargo.toml Cargo.lock ./', class: 'terminal-line', delay: 4500 }, + { text: ' +deps | --> RUN cargo build --release', class: 'term-yellow', delay: 8500 }, + { text: ' +deps | Updating crates.io index', class: 'terminal-line', delay: 14000 }, + { text: ' +deps | Compiling 58 dependencies', class: 'terminal-line', delay: 22000 }, + { text: ' +build | --> COPY src src', class: 'terminal-line', delay: 27000 }, + { + text: ' +build | --> RUN --mount=type=cache,target=/usr/local/cargo/registry cargo build --release', + class: 'term-yellow', + delay: 32000, + }, + { + text: ' +build | Compiling app v0.1.0 (/rust-app)', + class: 'terminal-line', + delay: 35500, + }, + { + text: ' +build | --> SAVE ARTIFACT target/release/app AS LOCAL bin/app', + class: 'terminal-line', + delay: 37500, + }, + { + text: '============= 🌍 Earth Build ✅ SUCCESS [38.4s] =============', + class: 'term-success', + delay: 38400, + }, + ], + }, + zig: { + filename: 'examples/zig/Earthfile', + repoUrl: 'https://github.com/earthbuild/earthbuild/tree/main/examples/zig', + metrics: { + cachedTime: '0.7s', + editTime: '1.6s', + coldTime: '8.6s', + editSpeedup: '5.4x Faster', + speedup: '12.3x Faster', + saved: '92% Saved (7.9s)', + ciSaved: '92% Fewer Runner Mins', + co2Saved: '~2.0g CO₂e / build', + }, + code: `VERSION 0.8 +FROM alpine:3.24 +WORKDIR /zig-app + +# toolchain downloads Zig compiler toolchain +toolchain: + RUN apk add --no-cache curl tar xz + RUN curl -LO https://ziglang.org/download/0.14.0/zig-linux-aarch64-0.14.0.tar.xz && \\ + tar -xf zig-linux-aarch64-0.14.0.tar.xz && mv zig-linux-aarch64-0.14.0 /opt/zig + ENV PATH="/opt/zig:\${PATH}" + +# build compiles optimized ReleaseFast binary +build: + FROM +toolchain + COPY build.zig src ./ + RUN --mount=type=cache,target=/root/.cache/zig zig build -Doptimize=ReleaseFast + SAVE ARTIFACT zig-out/bin/main AS LOCAL bin/zig-app`, + cached: [ + { text: '$ earth +build', class: 'term-cmd', delay: 80 }, + { + text: '─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)', + class: 'term-dim', + delay: 150, + }, + { text: '─── Build 🔧', class: 'term-bold', delay: 220 }, + { text: ' +toolchain | *cached* --> FROM alpine:3.24', class: 'term-cached', delay: 300 }, + { + text: ' +toolchain | *cached* --> RUN apk add --no-cache curl tar xz', + class: 'term-cached', + delay: 380, + }, + { + text: ' +toolchain | *cached* --> RUN curl -LO https://ziglang.org/download/0.14.0/zig-linux-aarch64-0.14.0.tar.xz', + class: 'term-cached', + delay: 460, + }, + { + text: ' +build | *cached* --> COPY build.zig src ./', + class: 'term-cached', + delay: 520, + }, + { + text: ' +build | *cached* --> RUN --mount=type=cache,target=/root/.cache/zig zig build -Doptimize=ReleaseFast', + class: 'term-cached', + delay: 580, + }, + { + text: ' +build | *cached* --> SAVE ARTIFACT zig-out/bin/main AS LOCAL bin/zig-app', + class: 'term-cached', + delay: 640, + }, + { + text: ' output | [----------] 100% exporting outputs', + class: 'terminal-line', + delay: 670, + }, + { text: '🎁 Artifact +build/main output as bin/zig-app', class: 'term-running', delay: 685 }, + { + text: '============= 🌍 Earth Build ✅ SUCCESS [0.7s] =============', + class: 'term-success', + delay: 700, + }, + ], + edit: [ + { text: '$ earth +build', class: 'term-cmd', delay: 80 }, + { + text: '─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)', + class: 'term-dim', + delay: 180, + }, + { text: '─── Build 🔧', class: 'term-bold', delay: 260 }, + { text: ' +toolchain | *cached* --> FROM alpine:3.24', class: 'term-cached', delay: 380 }, + { + text: ' +toolchain | *cached* --> RUN apk add --no-cache curl tar xz', + class: 'term-cached', + delay: 460, + }, + { + text: ' +toolchain | *cached* --> RUN curl -LO https://ziglang.org/download/0.14.0/zig-linux-aarch64-0.14.0.tar.xz', + class: 'term-cached', + delay: 540, + }, + { text: ' +build | --> COPY build.zig src ./', class: 'term-yellow', delay: 700 }, + { + text: ' +build | --> RUN --mount=type=cache,target=/root/.cache/zig zig build -Doptimize=ReleaseFast', + class: 'term-yellow', + delay: 1200, + }, + { + text: ' +build | --> SAVE ARTIFACT zig-out/bin/main AS LOCAL bin/zig-app', + class: 'terminal-line', + delay: 1450, + }, + { + text: '============= 🌍 Earth Build ✅ SUCCESS [1.6s] =============', + class: 'term-success', + delay: 1600, + }, + ], + cold: [ + { text: '$ earth +build', class: 'term-cmd', delay: 80 }, + { + text: '─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)', + class: 'term-dim', + delay: 250, + }, + { text: '─── Build 🔧', class: 'term-bold', delay: 500 }, + { text: ' +toolchain | --> FROM alpine:3.24', class: 'terminal-line', delay: 1200 }, + { + text: ' +toolchain | --> RUN apk add --no-cache curl tar xz', + class: 'terminal-line', + delay: 2500, + }, + { + text: ' +toolchain | --> RUN curl -LO https://ziglang.org/download/0.14.0/zig-linux-aarch64-0.14.0.tar.xz', + class: 'terminal-line', + delay: 4500, + }, + { text: ' +build | --> COPY build.zig src ./', class: 'terminal-line', delay: 5400 }, + { + text: ' +build | --> RUN --mount=type=cache,target=/root/.cache/zig zig build -Doptimize=ReleaseFast', + class: 'term-yellow', + delay: 6900, + }, + { + text: ' +build | [1/4] Compiling src/main.zig', + class: 'terminal-line', + delay: 7600, + }, + { + text: ' +build | [4/4] Linking binary zig-out/bin/main', + class: 'terminal-line', + delay: 8100, + }, + { + text: ' +build | --> SAVE ARTIFACT zig-out/bin/main AS LOCAL bin/zig-app', + class: 'terminal-line', + delay: 8400, + }, + { + text: '============= 🌍 Earth Build ✅ SUCCESS [8.6s] =============', + class: 'term-success', + delay: 8600, + }, + ], + }, + typescript: { + filename: 'examples/typescript-node/Earthfile', + repoUrl: 'https://github.com/earthbuild/earthbuild/tree/main/examples/typescript-node', + metrics: { + cachedTime: '1.2s', + editTime: '2.8s', + coldTime: '16.2s', + editSpeedup: '5.8x Faster', + speedup: '13.5x Faster', + saved: '93% Saved (15.0s)', + ciSaved: '93% Fewer Runner Mins', + co2Saved: '~3.8g CO₂e / build', + }, + code: `VERSION 0.8 +FROM node:26-alpine +WORKDIR /ts-app + +# deps caches npm dependencies +deps: + COPY package.json package-lock.json ./ + RUN --mount=type=cache,target=/root/.npm npm ci + +# build typechecks & builds production bundle +build: + FROM +deps + COPY tsconfig.json vite.config.ts src ./ + RUN npm run build + SAVE ARTIFACT dist AS LOCAL dist`, + cached: [ + { text: '$ earth +build', class: 'term-cmd', delay: 80 }, + { + text: '─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)', + class: 'term-dim', + delay: 180, + }, + { text: '─── Build 🔧', class: 'term-bold', delay: 260 }, + { text: ' +deps | *cached* --> FROM node:26-alpine', class: 'term-cached', delay: 380 }, + { text: ' +deps | *cached* --> WORKDIR /ts-app', class: 'term-cached', delay: 450 }, + { + text: ' +deps | *cached* --> COPY package.json package-lock.json ./', + class: 'term-cached', + delay: 520, + }, + { + text: ' +deps | *cached* --> RUN --mount=type=cache,target=/root/.npm npm ci', + class: 'term-cached', + delay: 600, + }, + { + text: ' +build | *cached* --> COPY tsconfig.json vite.config.ts src ./', + class: 'term-cached', + delay: 680, + }, + { text: ' +build | *cached* --> RUN npm run build', class: 'term-cached', delay: 800 }, + { + text: ' +build | *cached* --> SAVE ARTIFACT dist AS LOCAL dist', + class: 'term-cached', + delay: 950, + }, + { + text: ' output | [----------] 100% exporting outputs', + class: 'terminal-line', + delay: 1050, + }, + { text: '🎁 Artifact +build/dist output as dist', class: 'term-running', delay: 1120 }, + { + text: '============= 🌍 Earth Build ✅ SUCCESS [1.2s] =============', + class: 'term-success', + delay: 1200, + }, + ], + edit: [ + { text: '$ earth +build', class: 'term-cmd', delay: 80 }, + { + text: '─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)', + class: 'term-dim', + delay: 180, + }, + { text: '─── Build 🔧', class: 'term-bold', delay: 260 }, + { text: ' +deps | *cached* --> FROM node:26-alpine', class: 'term-cached', delay: 380 }, + { text: ' +deps | *cached* --> WORKDIR /ts-app', class: 'term-cached', delay: 450 }, + { + text: ' +deps | *cached* --> COPY package.json package-lock.json ./', + class: 'term-cached', + delay: 520, + }, + { + text: ' +deps | *cached* --> RUN --mount=type=cache,target=/root/.npm npm ci', + class: 'term-cached', + delay: 600, + }, + { + text: ' +build | --> COPY tsconfig.json vite.config.ts src ./', + class: 'term-yellow', + delay: 900, + }, + { text: ' +build | --> RUN npm run build', class: 'term-yellow', delay: 2000 }, + { + text: ' +build | --> SAVE ARTIFACT dist AS LOCAL dist', + class: 'terminal-line', + delay: 2600, + }, + { + text: '============= 🌍 Earth Build ✅ SUCCESS [2.8s] =============', + class: 'term-success', + delay: 2800, + }, + ], + cold: [ + { text: '$ earth +build', class: 'term-cmd', delay: 80 }, + { + text: '─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)', + class: 'term-dim', + delay: 300, + }, + { text: '─── Build 🔧', class: 'term-bold', delay: 600 }, + { text: ' +deps | --> FROM node:26-alpine', class: 'terminal-line', delay: 1500 }, + { text: ' +deps | --> WORKDIR /ts-app', class: 'terminal-line', delay: 2500 }, + { + text: ' +deps | --> COPY package.json package-lock.json ./', + class: 'terminal-line', + delay: 3500, + }, + { + text: ' +deps | --> RUN --mount=type=cache,target=/root/.npm npm ci', + class: 'term-yellow', + delay: 6500, + }, + { text: ' +deps | added 214 packages in 3.8s', class: 'terminal-line', delay: 7000 }, + { + text: ' +build | --> COPY tsconfig.json vite.config.ts src ./', + class: 'terminal-line', + delay: 8500, + }, + { text: ' +build | --> RUN npm run build', class: 'term-yellow', delay: 12500 }, + { + text: ' +build | --> SAVE ARTIFACT dist AS LOCAL dist', + class: 'terminal-line', + delay: 15600, + }, + { + text: '============= 🌍 Earth Build ✅ SUCCESS [16.2s] =============', + class: 'term-success', + delay: 16200, + }, + ], + }, + js: { + filename: 'examples/js/Earthfile', + repoUrl: 'https://github.com/earthbuild/earthbuild/tree/main/examples/js', + metrics: { + cachedTime: '0.9s', + editTime: '1.8s', + coldTime: '11.4s', + editSpeedup: '6.3x Faster', + speedup: '12.6x Faster', + saved: '92% Saved (10.5s)', + ciSaved: '92% Fewer Runner Mins', + co2Saved: '~2.6g CO₂e / build', + }, + code: `VERSION 0.8 +FROM node:26-alpine +WORKDIR /js-app + +# deps caches npm modules layer +deps: + COPY package.json package-lock.json ./ + RUN npm install + +# test runs test suite +test: + FROM +deps + COPY src test ./ + RUN npm test`, + cached: [ + { text: '$ earth +test', class: 'term-cmd', delay: 80 }, + { + text: '─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)', + class: 'term-dim', + delay: 160, + }, + { text: '─── Build 🔧', class: 'term-bold', delay: 240 }, + { text: ' +base | *cached* --> FROM node:26-alpine', class: 'term-cached', delay: 340 }, + { text: ' +base | *cached* --> WORKDIR /js-app', class: 'term-cached', delay: 400 }, + { + text: ' +deps | *cached* --> COPY package.json package-lock.json ./', + class: 'term-cached', + delay: 480, + }, + { text: ' +deps | *cached* --> RUN npm install', class: 'term-cached', delay: 580 }, + { text: ' +test | *cached* --> COPY src test ./', class: 'term-cached', delay: 700 }, + { text: ' +test | *cached* --> RUN npm test', class: 'term-cached', delay: 820 }, + { + text: '============= 🌍 Earth Build ✅ SUCCESS [0.9s] =============', + class: 'term-success', + delay: 900, + }, + ], + edit: [ + { text: '$ earth +test', class: 'term-cmd', delay: 80 }, + { + text: '─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)', + class: 'term-dim', + delay: 180, + }, + { text: '─── Build 🔧', class: 'term-bold', delay: 260 }, + { text: ' +base | *cached* --> FROM node:26-alpine', class: 'term-cached', delay: 380 }, + { text: ' +base | *cached* --> WORKDIR /js-app', class: 'term-cached', delay: 440 }, + { + text: ' +deps | *cached* --> COPY package.json package-lock.json ./', + class: 'term-cached', + delay: 500, + }, + { text: ' +deps | *cached* --> RUN npm install', class: 'term-cached', delay: 580 }, + { text: ' +test | --> COPY src test ./', class: 'term-yellow', delay: 850 }, + { text: ' +test | --> RUN npm test', class: 'term-yellow', delay: 1400 }, + { text: ' +test | Tests: 12 passed, 12 total', class: 'terminal-line', delay: 1650 }, + { + text: '============= 🌍 Earth Build ✅ SUCCESS [1.8s] =============', + class: 'term-success', + delay: 1800, + }, + ], + cold: [ + { text: '$ earth +test', class: 'term-cmd', delay: 80 }, + { + text: '─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)', + class: 'term-dim', + delay: 300, + }, + { text: '─── Build 🔧', class: 'term-bold', delay: 600 }, + { text: ' +base | --> FROM node:26-alpine', class: 'terminal-line', delay: 1800 }, + { text: ' +base | --> WORKDIR /js-app', class: 'terminal-line', delay: 3000 }, + { + text: ' +deps | --> COPY package.json package-lock.json ./', + class: 'terminal-line', + delay: 4000, + }, + { text: ' +deps | --> RUN npm install', class: 'term-yellow', delay: 6500 }, + { text: ' +deps | added 142 packages in 4.6s', class: 'terminal-line', delay: 7200 }, + { text: ' +test | --> COPY src test ./', class: 'terminal-line', delay: 8500 }, + { text: ' +test | --> RUN npm test', class: 'term-yellow', delay: 10500 }, + { text: ' +test | Tests: 12 passed, 12 total', class: 'terminal-line', delay: 11000 }, + { + text: '============= 🌍 Earth Build ✅ SUCCESS [11.4s] =============', + class: 'term-success', + delay: 11400, + }, + ], + }, + java: { + filename: 'examples/java/Earthfile', + repoUrl: 'https://github.com/earthbuild/earthbuild/tree/main/examples/java', + metrics: { + cachedTime: '1.5s', + editTime: '3.4s', + coldTime: '21.8s', + editSpeedup: '6.4x Faster', + speedup: '14.5x Faster', + saved: '93% Saved (20.3s)', + ciSaved: '93% Fewer Runner Mins', + co2Saved: '~5.1g CO₂e / build', + }, + code: `VERSION 0.8 +FROM gradle:8.12-jdk23 +WORKDIR /java-app + +# deps caches Gradle dependencies and daemon +deps: + COPY build.gradle settings.gradle ./ + RUN gradle dependencies --no-daemon + +# build compiles & tests multi-module project +build: + FROM +deps + COPY src src + RUN --mount=type=cache,target=/root/.gradle/caches \\ + gradle assemble test --no-daemon + SAVE ARTIFACT build/libs/*.jar AS LOCAL build/libs/app.jar`, + cached: [ + { text: '$ earth +build', class: 'term-cmd', delay: 80 }, + { + text: '─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)', + class: 'term-dim', + delay: 180, + }, + { text: '─── Build 🔧', class: 'term-bold', delay: 260 }, + { text: ' +deps | *cached* --> FROM gradle:8.12-jdk23', class: 'term-cached', delay: 350 }, + { text: ' +deps | *cached* --> WORKDIR /java-app', class: 'term-cached', delay: 420 }, + { + text: ' +deps | *cached* --> COPY build.gradle settings.gradle ./', + class: 'term-cached', + delay: 490, + }, + { + text: ' +deps | *cached* --> RUN gradle dependencies --no-daemon', + class: 'term-cached', + delay: 560, + }, + { text: ' +build | *cached* --> COPY src src', class: 'term-cached', delay: 640 }, + { + text: ' +build | *cached* --> RUN --mount=type=cache,target=/root/.gradle/caches gradle assemble test --no-daemon', + class: 'term-cached', + delay: 850, + }, + { + text: ' +build | *cached* --> SAVE ARTIFACT build/libs/*.jar AS LOCAL build/libs/app.jar', + class: 'term-cached', + delay: 1100, + }, + { + text: ' output | [----------] 100% exporting outputs', + class: 'terminal-line', + delay: 1250, + }, + { + text: '🎁 Artifact +build/libs/app.jar output as build/libs/app.jar', + class: 'term-running', + delay: 1380, + }, + { + text: '============= 🌍 Earth Build ✅ SUCCESS [1.5s] =============', + class: 'term-success', + delay: 1500, + }, + ], + edit: [ + { text: '$ earth +build', class: 'term-cmd', delay: 80 }, + { + text: '─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)', + class: 'term-dim', + delay: 180, + }, + { text: '─── Build 🔧', class: 'term-bold', delay: 260 }, + { text: ' +deps | *cached* --> FROM gradle:8.12-jdk23', class: 'term-cached', delay: 350 }, + { text: ' +deps | *cached* --> WORKDIR /java-app', class: 'term-cached', delay: 420 }, + { + text: ' +deps | *cached* --> COPY build.gradle settings.gradle ./', + class: 'term-cached', + delay: 490, + }, + { + text: ' +deps | *cached* --> RUN gradle dependencies --no-daemon', + class: 'term-cached', + delay: 560, + }, + { text: ' +build | --> COPY src src', class: 'term-yellow', delay: 800 }, + { + text: ' +build | --> RUN --mount=type=cache,target=/root/.gradle/caches gradle assemble test --no-daemon', + class: 'term-yellow', + delay: 1500, + }, + { text: ' +build | > Task :compileJava', class: 'terminal-line', delay: 2200 }, + { text: ' +build | > Task :test', class: 'terminal-line', delay: 2800 }, + { + text: ' +build | --> SAVE ARTIFACT build/libs/*.jar AS LOCAL build/libs/app.jar', + class: 'terminal-line', + delay: 3150, + }, + { + text: '============= 🌍 Earth Build ✅ SUCCESS [3.4s] =============', + class: 'term-success', + delay: 3400, + }, + ], + cold: [ + { text: '$ earth +build', class: 'term-cmd', delay: 80 }, + { + text: '─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)', + class: 'term-dim', + delay: 300, + }, + { text: '─── Build 🔧', class: 'term-bold', delay: 600 }, + { text: ' +deps | --> FROM gradle:8.12-jdk23', class: 'terminal-line', delay: 1500 }, + { text: ' +deps | --> WORKDIR /java-app', class: 'terminal-line', delay: 2500 }, + { + text: ' +deps | --> COPY build.gradle settings.gradle ./', + class: 'terminal-line', + delay: 3500, + }, + { + text: ' +deps | --> RUN gradle dependencies --no-daemon', + class: 'term-yellow', + delay: 6500, + }, + { text: ' +build | --> COPY src src', class: 'terminal-line', delay: 9000 }, + { + text: ' +build | --> RUN --mount=type=cache,target=/root/.gradle/caches gradle assemble test --no-daemon', + class: 'terminal-line', + delay: 12000, + }, + { text: ' +build | > Task :compileJava', class: 'terminal-line', delay: 15000 }, + { text: ' +build | > Task :test', class: 'terminal-line', delay: 17500 }, + { text: ' +build | > Task :assemble', class: 'terminal-line', delay: 19500 }, + { + text: ' +build | --> SAVE ARTIFACT build/libs/*.jar AS LOCAL build/libs/app.jar', + class: 'terminal-line', + delay: 21000, + }, + { + text: '============= 🌍 Earth Build ✅ SUCCESS [21.8s] =============', + class: 'term-success', + delay: 21800, + }, + ], + }, +}; diff --git a/assets/ts/types.ts b/assets/ts/types.ts new file mode 100644 index 0000000..6264577 --- /dev/null +++ b/assets/ts/types.ts @@ -0,0 +1,37 @@ +/** + * EarthBuild Type Definitions + */ + +export type SupportedLanguage = + 'python' | 'js' | 'typescript' | 'java' | 'cpp' | 'go' | 'rust' | 'zig'; + +export type WindowKey = 'cold' | 'edit' | 'repeat'; + +export interface TerminalLine { + text: string; + class?: string; + delay: number; +} + +export interface SampleMetrics { + cachedTime: string; + editTime: string; + coldTime: string; + editSpeedup: string; + speedup: string; + saved: string; + ciSaved: string; + co2Saved: string; +} + +export interface CodeSample { + filename: string; + repoUrl: string; + metrics: SampleMetrics; + code: string; + cached: TerminalLine[]; + edit: TerminalLine[]; + cold: TerminalLine[]; +} + +export type CodeSamplesMap = Record; diff --git a/content/_index.md b/content/_index.md new file mode 100644 index 0000000..b9259dd --- /dev/null +++ b/content/_index.md @@ -0,0 +1,4 @@ +--- +title: "EarthBuild — Like Docker for Builds" +description: "EarthBuild is the open source, container-powered build system. Repeatable, isolated, cached builds that run identically on your laptop and in any CI." +--- diff --git a/content/faq/_index.md b/content/faq/_index.md new file mode 100644 index 0000000..f071742 --- /dev/null +++ b/content/faq/_index.md @@ -0,0 +1,63 @@ +--- +title: "Frequently Asked Questions" +description: "Everything you need to know about EarthBuild, the open-source community fork of Earthly, licensing, architecture, and migration." +--- + +
+
+ What is EarthBuild and what is its relationship with Earthly? +
+

EarthBuild is the official, community-supported and open-source fork of Earthly. It was initiated and endorsed by the Earthly community and creators to ensure that the core container-based build engine remains vibrant, fully open source, and continuously maintained under the Mozilla Public License 2.0 (MPL-2.0).

+

EarthBuild is a 100% drop-in replacement for existing Earthfiles and CLI workflows.

+
+
+ +
+ Is EarthBuild free and open source? +
+

Yes, 100%. EarthBuild is released under the Mozilla Public License Version 2.0 (MPL-2.0). There are no closed-source enterprise tiers, no cloud telemetry lock-ins, and no sudden paywalls. All capabilities—including remote caching, satellite builds, and multi-platform compilation—are completely free for personal, team, and commercial use.

+
+
+ +
+ How is EarthBuild different from Dockerfiles? +
+

Dockerfiles were designed specifically for creating container runtime images. EarthBuild takes Dockerfile's layer caching and container isolation, but expands it into a general-purpose build system:

+
    +
  • Artifact Output: EarthBuild can output local binaries, test reports, web bundles, and files directly to your host (SAVE ARTIFACT ... AS LOCAL).
  • +
  • Multi-Target Dependency Graph: Targets can depend on other targets across folders and git repos.
  • +
  • Parallel Execution: Independent targets (e.g. +lint, +test, +compile) execute concurrently.
  • +
+
+
+ +
+ How does caching work in EarthBuild? +
+

EarthBuild leverages BuildKit under the hood. It tracks the exact cryptographic hash of all declared inputs (files, arguments, dependencies). If an input hasn't changed, EarthBuild re-uses the cached layer in milliseconds.

+

You can also configure remote caching via any standard OCI container registry (GitHub Packages, Docker Hub, AWS ECR, GCP Artifact Registry) so your entire engineering team shares pre-computed build layers.

+
+
+ +
+ Do I need Docker installed to run EarthBuild? +
+

No, Docker is not strictly required. EarthBuild supports Podman, Docker, and Apple container engines out-of-the-box by default.

+

EarthBuild automatically detects your local container runtime without manual daemon configuration. You can also connect to remote BuildKit daemons running in Kubernetes or a remote Linux VM.

+
+
+ +
+ How do I migrate from Earthly to EarthBuild? +
+

Migration is instant because Earthfiles are 100% compatible. Simply install the earthbuild CLI (which provides the earth command) and run your builds as usual. No changes to your Earthfile syntax are required.

+
+
+ +
+ How are secrets handled during builds? +
+

EarthBuild supports secure secret mounts via RUN --secret SECRET_NAME=+secrets/token .... Secrets are mounted only in memory during the execution of that specific command and are never baked into container image layers, build cache metadata, or output artifacts.

+
+
+
diff --git a/content/install/_index.md b/content/install/_index.md new file mode 100644 index 0000000..8169c71 --- /dev/null +++ b/content/install/_index.md @@ -0,0 +1,153 @@ +--- +title: "Install EarthBuild" +description: "Step-by-step instructions for installing EarthBuild CLI via Homebrew, cURL script, manual binary, Windows (WSL 2), Nix, and GitHub Actions." +--- + +Get EarthBuild running on your machine in under a minute. + +--- + +## 📋 Prerequisites + +EarthBuild supports **Podman**, **Docker**, and **Apple container engines** out-of-the-box by default: + +- **macOS:** Apple container engines, [Docker Desktop](https://docs.docker.com/desktop/mac/install/), or [Podman](https://podman.io/) +- **Linux:** [Podman](https://podman.io/), [Docker Engine](https://docs.docker.com/engine/install/), or Rootless Docker +- **Windows:** [Docker Desktop](https://docs.docker.com/desktop/windows/install/) or [Podman Desktop](https://podman-desktop.io/) with WSL 2 + +--- + +## 🍺 Homebrew (macOS & Linux) + +The easiest way to install and keep EarthBuild updated on macOS and Linux: + +```bash +brew install earthbuild/tap/earth +``` + +--- + +## 🐚 Shell Script (Linux, macOS, WSL 2) + +Install the latest release automatically with our official install script: + +```bash +curl -fsSL https://www.earthbuild.dev/install.sh | sh +``` + +--- + +## 📦 Manual Binary Download + +Download and install pre-compiled binaries directly from GitHub Releases: + +```bash +# Set your architecture (e.g., amd64, arm64) +ARCH=amd64 + +# Download the latest Linux release +curl -LO "https://github.com/earthbuild/earthbuild/releases/latest/download/earth-linux-${ARCH}" + +# Make it executable and place in PATH +chmod +x "earth-linux-${ARCH}" +sudo mv "earth-linux-${ARCH}" /usr/local/bin/earth +``` + +For macOS Darwin binaries, replace `earth-linux-${ARCH}` with `earth-darwin-arm64` (Apple Silicon) or `earth-darwin-amd64` (Intel). + +--- + +## 🪟 Windows (WSL 2) + +Inside your WSL 2 Ubuntu / Debian terminal: + +```bash +curl -fsSL https://www.earthbuild.dev/install.sh | sh +``` + +> **Note:** Native Windows support is experimental. We recommend using WSL 2 for container isolation, BuildKit caching, and performance parity with Linux. + +--- + +## ❄️ Nix & NixOS + +EarthBuild is packaged in `nixpkgs`: + +### Temporary Shell: +```bash +nix-shell -p earthbuild +``` + +### NixOS (`configuration.nix`): +```nix +environment.systemPackages = [ pkgs.earthbuild ]; +``` + +### nix-darwin: +```nix +environment.systemPackages = [ pkgs.earthbuild ]; +``` + +### Home Manager (`home.nix`): +```nix +home.packages = [ pkgs.earthbuild ]; +``` + +--- + +## 🐳 Docker Container + +Run EarthBuild directly inside Docker without installing the host binary: + +```bash +docker run --privileged --rm \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v $(pwd):/workspace \ + -w /workspace \ + earthbuild/earthbuild:latest +build +``` + +--- + +## ⚡ GitHub Actions CI/CD + +Integrate EarthBuild into your GitHub Actions pipelines with official caching and setup: + +```yaml +name: CI +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Setup EarthBuild + uses: earthbuild/actions-setup@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Execute Pipeline + run: earth --ci +all +``` + +--- + +## ✅ Verification & Setup + +Verify that the CLI is installed and check the version: +```bash +earth --version +``` + +Initialize the BuildKit daemon container (first-time bootstrap): +```bash +earth bootstrap +``` + +### Next Steps +- [Getting Started Guide](https://docs.earthbuild.dev/basics) +- [Earthfile Reference](https://docs.earthbuild.dev/docs/earthfile) +- [Example Projects](https://docs.earthbuild.dev/docs/examples) diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..834d214 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,51 @@ +import eslint from '@eslint/js'; +import { defineConfig } from 'eslint/config'; +import tseslint from 'typescript-eslint'; +import eslintConfigPrettier from 'eslint-config-prettier'; +import globals from 'globals'; + +export default defineConfig( + { + ignores: [ + 'public/**', + 'resources/**', + 'static/js/**', + 'node_modules/**', + '.lighthouseci/**', + ], + }, + eslint.configs.recommended, + tseslint.configs.recommended, + eslintConfigPrettier, + { + files: ['assets/ts/**/*.ts'], + languageOptions: { + globals: { + ...globals.browser, + THREE: 'readonly', + }, + }, + rules: { + '@typescript-eslint/no-unused-vars': [ + 'warn', + { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, + ], + '@typescript-eslint/no-explicit-any': 'off', + }, + }, + { + files: ['tests/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + ...globals.browser, + }, + }, + rules: { + 'no-unused-vars': [ + 'warn', + { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, + ], + }, + }, +); diff --git a/firebase.json b/firebase.json new file mode 100644 index 0000000..93fb369 --- /dev/null +++ b/firebase.json @@ -0,0 +1,16 @@ +{ + "hosting": { + "public": "public", + "ignore": [ + "firebase.json", + "**/.*", + "**/node_modules/**" + ], + "rewrites": [ + { + "source": "**", + "destination": "/index.html" + } + ] + } +} diff --git a/hugo.toml b/hugo.toml new file mode 100644 index 0000000..a1b1450 --- /dev/null +++ b/hugo.toml @@ -0,0 +1,63 @@ +baseURL = 'https://earthbuild.dev/' +defaultContentLanguage = 'en' +title = 'EarthBuild — Like Docker for Builds' +description = 'EarthBuild is the open source, container-powered build system. Write once, run anywhere with instant caching and automatic parallelism.' + +[languages] + [languages.en] + locale = 'en-US' + label = 'English' + +[params] + tagline = "It's like Docker for builds" + description = "Repeatable, isolated, cached builds that run identically on your laptop and in any CI. The community-supported open-source successor to Earthly." + githubRepo = "https://github.com/earthbuild/earthbuild" + docsUrl = "https://docs.earthbuild.dev" + slackUrl = "https://earthbuild.dev/slack" + dockerHubUrl = "https://hub.docker.com/u/earthbuild" + license = "Mozilla Public License 2.0" + latestVersion = "v0.8.15" + +[menu] + [[menu.main]] + name = "Features" + url = "/#features" + weight = 10 + [[menu.main]] + name = "Compare" + url = "/#comparison" + weight = 20 + [[menu.main]] + name = "Examples" + url = "/#examples" + weight = 30 + [[menu.main]] + name = "Docs" + url = "https://docs.earthbuild.dev" + weight = 40 + [[menu.main]] + name = "FAQ" + url = "/faq/" + weight = 50 + [[menu.main]] + name = "Install" + url = "/install/" + weight = 60 + +[markup] + [markup.goldmark] + [markup.goldmark.renderer] + unsafe = true + [markup.highlight] + noClasses = false + +[module] + [[module.mounts]] + source = "assets" + target = "assets" + [[module.mounts]] + source = "static/css" + target = "assets/css" + [[module.mounts]] + source = "static" + target = "static" diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html new file mode 100644 index 0000000..308aee1 --- /dev/null +++ b/layouts/_default/baseof.html @@ -0,0 +1,83 @@ + + + + + + {{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }} | EarthBuild{{ end }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ $css := resources.Get "css/style.css" | minify }} + + + +
+ + {{ partial "nav.html" . }} + +
+ {{ block "main" . }}{{ end }} +
+ + {{ partial "footer.html" . }} + + + {{ $opts := dict "targetPath" "js/bundle.js" "minify" hugo.IsProduction }} + {{ $js := resources.Get "ts/main.ts" | js.Build $opts }} + {{ if hugo.IsProduction }} + {{ $js = $js | fingerprint }} + {{ end }} + + + diff --git a/layouts/_default/list.html b/layouts/_default/list.html new file mode 100644 index 0000000..7e10b42 --- /dev/null +++ b/layouts/_default/list.html @@ -0,0 +1,17 @@ +{{ define "main" }} +
+
+
+ +

{{ .Title }}

+ {{ with .Description }} +

{{ . }}

+ {{ end }} +
+ +
+ {{ .Content }} +
+
+
+{{ end }} diff --git a/layouts/_default/single.html b/layouts/_default/single.html new file mode 100644 index 0000000..75d839c --- /dev/null +++ b/layouts/_default/single.html @@ -0,0 +1,17 @@ +{{ define "main" }} +
+
+
+ +

{{ .Title }}

+ {{ with .Description }} +

{{ . }}

+ {{ end }} +
+ +
+ {{ .Content }} +
+
+
+{{ end }} diff --git a/layouts/index.html b/layouts/index.html new file mode 100644 index 0000000..ddb9cd6 --- /dev/null +++ b/layouts/index.html @@ -0,0 +1,13 @@ +{{ define "main" }} + {{ partial "hero.html" . }} + {{ partial "social-proof.html" . }} + {{ partial "problem-solution.html" . }} + {{ partial "features-grid.html" . }} + {{ partial "security-ai.html" . }} + {{ partial "interactive-code.html" . }} + {{ partial "comparison-matrix.html" . }} + {{ partial "roi-calculator.html" . }} + {{ partial "migration-guide.html" . }} + {{ partial "testimonials.html" . }} + {{ partial "quickstart-cta.html" . }} +{{ end }} diff --git a/layouts/partials/comparison-matrix.html b/layouts/partials/comparison-matrix.html new file mode 100644 index 0000000..7cacc7a --- /dev/null +++ b/layouts/partials/comparison-matrix.html @@ -0,0 +1,128 @@ +
+
+
+

How EarthBuild Compares to Alternatives

+

+ See why engineering teams choose EarthBuild over Dagger, Bazel, Docker Buildx, Makefiles, and brittle CI YAML. +

+
+ +
+ +
+ + + + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CapabilityEarthBuildDaggerBazelDocker BuildxMake / JustGitHub Actions
Local == CI 100% Parity
Exact same build outputs locally & in CI
✓ Native✓ Native✓ Native◐ Image only✕ Host dependent✕ Cloud only
Automatic Container Sandboxing
Zero toolchain drift across laptops
✓ Yes (Built-in)✓ Yes (Containers)◐ Strict sandbox✓ Yes✕ None◐ VM-level
Familiar Declarative Syntax
Familiar Dockerfile & Make verbs
✓ Declarative DSL◐ Code SDKs (Go/TS/Python)◐ Starlark DSL✓ Dockerfile✓ Makefile◐ CI YAML
Zero-Rewrite Adoption
Wrap existing tools (cargo, go, npm, pip)
✓ Drop-in Wrapper◐ Requires SDK code✕ Total rewrite✓ Drop-in✓ Drop-in✓ Drop-in
Local Artifact Export to Host
Native SAVE ARTIFACT ... AS LOCAL
✓ Native (1-line)✓ Via Directory API✓ Native◐ Requires --output flag✓ Host writes✕ Upload artifact
Automatic DAG Parallelism
Build independent targets simultaneously
✓ Automatic (BuildKit)✓ Automatic✓ Automatic◐ Multi-stage only◐ make -j (Unsandboxed)◐ Matrix jobs
Multi-Tier Layer Caching
Local layer cache + remote registry cache
✓ Instant Layer Cache✓ Layer Cache✓ Content-addressed✓ BuildKit cache✕ Timestamp based◐ actions/cache
Monorepo & Cross-Repo Imports
Reference targets across folders or git URLs
✓ First-class✓ Module imports✓ Monorepo only✕ Single file only◐ include directives✕ Submodules
100% Community Open Source (MPL-2.0)
No proprietary locks, fully self-hostable
✓ 100% Open Source◐ Apache 2.0 + Cloud service✓ Open source◐ Apache 2.0 (Buildx) / Desktop terms✓ Open source✕ Proprietary SaaS
+
+
+
+
diff --git a/layouts/partials/features-grid.html b/layouts/partials/features-grid.html new file mode 100644 index 0000000..bce2c2f --- /dev/null +++ b/layouts/partials/features-grid.html @@ -0,0 +1,83 @@ +
+
+
+

Built for Maximum Engineering Velocity

+

+ Everything you need to orchestrate complex polyglot builds, monorepos, and multi-service systems without cognitive fatigue. +

+
+ +
+ + + + +
+
📦
+

Docker, Podman & Apple Containers

+

+ Supports Podman, Docker, and Apple container engines out-of-the-box by default. Zero host toolchain contamination with complete container isolation. +

+
+ + +
+
💾
+

Universal File & Image Output

+

+ While extracting host files with Docker typically requires dedicated --output type=local stages or docker cp, EarthBuild natively exports compiled binaries, test reports, and container images simultaneously in a single build pass. +

+
+ SAVE ARTIFACT dist/binary AS LOCAL ./bin/binary +
+
+ + +
+
🏘
+

Monorepo & Remote Repo Imports

+

+ Reference and execute targets across nested subdirectories or remote Git repositories with pinpoint caching. Re-use shared proto generators, linters, or base images with one line. +

+
+ BUILD github.com/myorg/toolchain:v2.1+generate +
+
+ + +
+
🌐
+

Native Multi-Platform Builds

+

+ Build for multiple CPU architectures simultaneously (e.g. linux/amd64 and linux/arm64). Test Apple Silicon and AWS Graviton targets locally with zero hassle. +

+
+ + + +
+
+
diff --git a/layouts/partials/footer.html b/layouts/partials/footer.html new file mode 100644 index 0000000..1419bf6 --- /dev/null +++ b/layouts/partials/footer.html @@ -0,0 +1,71 @@ + diff --git a/layouts/partials/hero.html b/layouts/partials/hero.html new file mode 100644 index 0000000..8431eb0 --- /dev/null +++ b/layouts/partials/hero.html @@ -0,0 +1,52 @@ +
+ +
+ +
+ + + + +
+
+
+
+ + Free Forever + • + Mozilla Public License 2.0 +
+
+ +

+ Build Once. Run Anywhere.
+ Seriously. +

+ +

+ Run fast, containerized builds that behave identically everywhere. No more broken pipelines, no more “works on my machine”. +

+ + + +
+
+ + + + +
+
+ brew install earthbuild/tap/earth + +
+
+
+
+
diff --git a/layouts/partials/interactive-code.html b/layouts/partials/interactive-code.html new file mode 100644 index 0000000..781d3ec --- /dev/null +++ b/layouts/partials/interactive-code.html @@ -0,0 +1,295 @@ +
+
+
+

Faster Feedback Loops. Everywhere You Build.

+

+ See how EarthBuild layer caching eliminates redundant compilation on your workstation and in CI. Watch a live 3-way execution race comparing a clean build, an incremental build, and a zero-work rebuild. +

+
+ + +
+ + +
+
+ + + + + + + + +
+
+ + +
+ +
+
+
+ + + +
+
+ Earthfile +
+
+ +
+ +
+
+ + +
+
VERSION 0.8
+FROM python:3.14-slim
+WORKDIR /app
+
+# deps caches pip dependencies in isolated container layer
+deps:
+  COPY requirements.txt .
+  RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt
+
+# test runs unit tests inside isolated container
+test:
+  FROM +deps
+  COPY src src
+  RUN pytest src/
+
+
+
+ + + + + + + + +
+ + +
+ +
+ +
+
+ ❄️ + Clean Build +
+
+
+
+
+
6.2s
+
+
+ + +
+
+ ✏️ + Incremental Build +
+
+
+
+
+
+ 1.8s + 3.4x Faster +
+
+
+ + +
+
+ ⚡ + Zero-Work Rebuild +
+
+
+
+
+
+ 1.1s + 5.6x Faster +
+
+
+
+ + +
+
+ ⚡ Compute Saved + 82% Saved (5.1s) +
+
+ + 🌱 CO₂ Emissions Avoided + + + + 🌱 CO₂ Estimation Formula + + Calculated using the Cloud Carbon Footprint & GSF standard (~200W active CI runner @ 1.15 PUE with ~410g CO₂e/kWh grid average ≈ 0.25g CO₂e / sec of build time eliminated). + + + Source: Cloud Carbon Footprint Methodology + + + + + + ~1.4g CO₂e / build +
+
+
+
+
diff --git a/layouts/partials/migration-guide.html b/layouts/partials/migration-guide.html new file mode 100644 index 0000000..8bcb637 --- /dev/null +++ b/layouts/partials/migration-guide.html @@ -0,0 +1,554 @@ +
+
+
+

Adopt EarthBuild in Under 5 Minutes

+

+ Adopt EarthBuild alongside your existing toolchain without disrupting active pipelines or rewriting your repository. Start with a single target and migrate at your own pace. +

+
+ + +
+
+ + + + + + + + +
+
+ +
+
+ + + + + +
+ + +
+
+
+ + + Standard Dockerfile + + +
+
FROM golang:1.27-alpine3.24 AS deps
+WORKDIR /app
+COPY go.mod go.sum ./
+RUN go mod download
+
+FROM deps AS test
+COPY *.go ./
+RUN go test -v ./...
+
+FROM deps AS builder
+COPY *.go ./
+RUN CGO_ENABLED=0 go build -o bin/app .
+
+FROM alpine:3.24 AS runtime
+COPY --from=builder /app/bin/app /usr/local/bin/app
+ENTRYPOINT ["/usr/local/bin/app"]
+
+ +
+
+ + + Earthfile (Direct Host Artifacts & Push Built-In) + +
+
VERSION 0.8
+FROM golang:1.27-alpine3.24
+WORKDIR /app
+
+deps:
+  COPY go.mod go.sum ./
+  RUN go mod download
+
+test:
+  FROM +deps
+  COPY *.go ./
+  RUN go test -v ./...
+
+build:
+  FROM +deps
+  COPY *.go ./
+  RUN CGO_ENABLED=0 go build -o bin/app .
+  SAVE ARTIFACT bin/app AS LOCAL bin/app
+
+docker:
+  FROM alpine:3.24
+  COPY +build/bin/app /usr/local/bin/app
+  ENTRYPOINT ["/usr/local/bin/app"]
+  SAVE IMAGE --push docker.io/myorg/myapp:latest
+
+all:
+  BUILD +test
+  BUILD +build
+  BUILD +docker
+
+
+ + + + + + + + + + + + +
+
+
diff --git a/layouts/partials/nav.html b/layouts/partials/nav.html new file mode 100644 index 0000000..9c5a868 --- /dev/null +++ b/layouts/partials/nav.html @@ -0,0 +1,33 @@ + diff --git a/layouts/partials/problem-solution.html b/layouts/partials/problem-solution.html new file mode 100644 index 0000000..ba37caa --- /dev/null +++ b/layouts/partials/problem-solution.html @@ -0,0 +1,100 @@ +
+ + + +
+
+

Overcome the CI Feedback Bottleneck

+

+ Software engineers lose hours every week waiting for cloud CI runners and dealing with environment drift. EarthBuild gives you the speed and certainty of local iteration. +

+
+ +
+ +
+

+ + + + + + Traditional CI & Build Friction +

+ +
    +
  • +
    ✕
    +
    +

    "Works on my machine" Syndrome

    +

    Mismatched Node, Python, or Go versions, missing local headers, and OS differences create constant environment drift.

    +
    +
  • +
  • +
    ✕
    +
    +

    Remote CI Guess-and-Check Loops

    +

    You cannot easily reproduce remote CI pipelines locally. Every adjustment requires pushing commits and waiting in runner queues.

    +
    +
  • +
  • +
    ✕
    +
    +

    Untestable Pipeline Scripts in YAML

    +

    Multi-step YAML files with embedded shell scripts that cannot be tested, stepped through, or executed directly on local machines.

    +
    +
  • +
  • +
    ✕
    +
    +

    Redundant Rebuilds & Expensive Cloud Compute

    +

    CI runners re-download dependencies, re-compile static assets, and rerun unchanged test suites from scratch on every push.

    +
    +
  • +
+
+ + +
+

+ + + + + The EarthBuild Paradigm +

+ +
    +
  • +
    ✓
    +
    +

    Deterministic Container Sandboxing

    +

    Every step runs inside an isolated container with declared inputs. If it builds on your workstation, it builds in CI identically.

    +
    +
  • +
  • +
    ✓
    +
    +

    Instant Local CI Parity

    +

    Run earth +test or earth +ci directly in your terminal. Get instant local feedback in seconds without pushing a single commit.

    +
    +
  • +
  • +
    ✓
    +
    +

    Familiar, Declarative Syntax

    +

    Instant readability with Dockerfile verbs (FROM, RUN, COPY) and Makefile targets (+build, +docker). Zero complex DSLs.

    +
    +
  • +
  • +
    ✓
    +
    +

    Automatic Parallelism & Instant Caching

    +

    EarthBuild automatically discovers target dependencies, executes independent steps simultaneously, and caches layers instantly.

    +
    +
  • +
+
+
+
+
diff --git a/layouts/partials/quickstart-cta.html b/layouts/partials/quickstart-cta.html new file mode 100644 index 0000000..5fc0da0 --- /dev/null +++ b/layouts/partials/quickstart-cta.html @@ -0,0 +1,34 @@ +
+
+
+
+ + Free Forever • Mozilla Public License 2.0 +
+ +

Ready for Faster, Repeatable Builds?

+

+ Join the community of engineers building the next generation of container-powered build automation. Install EarthBuild in 10 seconds. +

+ + +
+
+
diff --git a/layouts/partials/roi-calculator.html b/layouts/partials/roi-calculator.html new file mode 100644 index 0000000..9e0cc4e --- /dev/null +++ b/layouts/partials/roi-calculator.html @@ -0,0 +1,73 @@ +
+
+
+

Calculate Your Time, Compute & CO₂ Savings

+

+ See how much developer focus, CI runner capacity, and data center energy your team saves by eliminating redundant rebuilds. +

+
+ +
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+
+
672 hrs
+
⚡ Engineering Wait Time Reclaimed / Month
+
≈ ~34 hrs saved / dev / month
+
+ +
+
40,320 mins
+
🖥️ CI Runner Minutes Spared / Month
+
≈ 1,344 vCPU-hrs (2-vCPU runner baseline)
+
+ +
+
605 kg CO₂e
+
🌱 Cloud Emissions Avoided / Month
+
≈ ~1.2 MWh data center energy spared
+
+ +
+ +
+
+
+
+
diff --git a/layouts/partials/security-ai.html b/layouts/partials/security-ai.html new file mode 100644 index 0000000..fbf4cc7 --- /dev/null +++ b/layouts/partials/security-ai.html @@ -0,0 +1,77 @@ +
+
+
+

Engineered for Zero-Trust Security. Built for AI Agents.

+

+ Isolate untrusted open-source packages, protect enterprise credentials, and give autonomous AI coding assistants complete build freedom without risking your host machine. +

+
+ +
+ +
+
+
+ + + +
+ Supply Chain Defense +
+

Hermetic Host Sandboxing

+

+ Every build step executes inside an unprivileged container namespace. Malicious npm, pip, or cargo post-install scripts can never access host files, SSH keys, cloud credentials, or environment variables. +

+ +
+ + +
+
+
+ + + + + + + +
+ Autonomous AI Ready +
+

The Safe Sandbox for AI Coding Agents

+

+ Give autonomous coding agents (Claude Code, Devin, Cursor, Antigravity CLI) full freedom to install dependencies, compile code, and run test suites. If an agent hallucinates a dangerous command, the blast radius is isolated inside the container sandbox. +

+ +
+ + +
+
+
+ + + + +
+ SecOps Compliance +
+

Zero-Leak Secret Injection

+

+ Secrets mounted via RUN --secret exist exclusively in RAM during target execution over secure gRPC. They are never baked into cache layers, exported images, or build logs. +

+ +
+
+
+
diff --git a/layouts/partials/social-proof.html b/layouts/partials/social-proof.html new file mode 100644 index 0000000..456ed0d --- /dev/null +++ b/layouts/partials/social-proof.html @@ -0,0 +1,41 @@ + diff --git a/layouts/partials/testimonials.html b/layouts/partials/testimonials.html new file mode 100644 index 0000000..7ddf190 --- /dev/null +++ b/layouts/partials/testimonials.html @@ -0,0 +1,54 @@ +
+
+
+

Loved by Engineers & DevOps

+

+ Here is what developers say about replacing brittle CI YAML and complex monorepo tooling with EarthBuild. +

+
+ +
+ +
+
+ "EarthBuild cut our CI pipeline time from 28 minutes to under 4 minutes with layer caching. But the biggest win was eliminating 'works on my machine' once and for all across our 40-engineer team." +
+
+
MS
+
+
Marcus S.
+ Principal Platform Engineer +
+
+
+ + +
+
+ "The Dockerfile + Makefile syntax is pure genius. There was literally zero learning curve for our backend developers. We ported 6 polyglot microservices in an afternoon." +
+
+
AL
+
+
Anna L.
+ Staff Backend Architect +
+
+
+ + +
+
+ "Being able to run the exact same `earth +test` target on my M3 Mac and in GitHub Actions without touching a line of runner YAML is the best developer experience improvement we made this year." +
+
+
DK
+
+
David K.
+ DevOps Lead +
+
+
+
+
+
diff --git a/lighthouserc.json b/lighthouserc.json new file mode 100644 index 0000000..2e7aad9 --- /dev/null +++ b/lighthouserc.json @@ -0,0 +1,89 @@ +{ + "ci": { + "collect": { + "staticDistDir": "./public", + "url": [ + "/", + "/install/", + "/faq/" + ], + "numberOfRuns": 1, + "settings": { + "chromeFlags": "--no-sandbox --headless --disable-gpu --disable-dev-shm-usage" + } + }, + "assert": { + "assertions": { + "categories:performance": [ + "error", + { + "minScore": 0.90 + } + ], + "categories:accessibility": [ + "error", + { + "minScore": 1.0 + } + ], + "color-contrast": [ + "error", + { + "minScore": 1.0 + } + ], + "image-alt": [ + "error", + { + "minScore": 1.0 + } + ], + "link-name": [ + "error", + { + "minScore": 1.0 + } + ], + "button-name": [ + "error", + { + "minScore": 1.0 + } + ], + "document-title": [ + "error", + { + "minScore": 1.0 + } + ], + "html-has-lang": [ + "error", + { + "minScore": 1.0 + } + ], + "meta-viewport": [ + "error", + { + "minScore": 1.0 + } + ], + "categories:best-practices": [ + "error", + { + "minScore": 0.90 + } + ], + "categories:seo": [ + "error", + { + "minScore": 0.90 + } + ] + } + }, + "upload": { + "target": "temporary-public-storage" + } + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9873df5 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1726 @@ +{ + "name": "earthbuild-web", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "earthbuild-web", + "version": "1.0.0", + "devDependencies": { + "@eslint/js": "^10.0.1", + "eslint": "^10.11.0", + "eslint-config-prettier": "^10.1.8", + "globals": "^17.13.0", + "prettier": "^3.9.9", + "puppeteer-core": "^25.12.0", + "typescript-eslint": "^8.71.0" + } + }, + "node_modules/@cacheable/memory": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz", + "integrity": "sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/utils": "^2.5.0", + "@keyv/bigmap": "^1.3.1", + "hookified": "^1.15.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.5.0.tgz", + "integrity": "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.5.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.3.tgz", + "integrity": "sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@keyv/bigmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", + "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.4.0", + "hookified": "^1.15.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "keyv": "^5.6.0" + } + }, + "node_modules/@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@puppeteer/browsers": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-3.2.3.tgz", + "integrity": "sha512-2Bt3m6dDAJqmZehn0wiXSYyQmuhyiHHZ7FjvrVaS1W2RoFGV6eCK71UvKYkNJjkwC1Q9nobjhvTMhLQztFVINA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "modern-tar": "^0.8.4", + "yargs": "^18.0.0" + }, + "bin": { + "browsers": "lib/main-cli.js" + }, + "engines": { + "node": ">=22.12.0" + }, + "peerDependencies": { + "proxy-agent": ">=8.0.1", + "yauzl": "^2.10.0 || ^3.4.0" + }, + "peerDependenciesMeta": { + "proxy-agent": { + "optional": true + }, + "yauzl": { + "optional": true + } + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.71.0.tgz", + "integrity": "sha512-pqcS9c1HxZTHt7End4nXqd0s5lJrrFzrgCkKFJrsbUnaL6M3+6oBFZaslg6Gjsl3argl2DDRFROnXARaZ2e4Nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.71.0", + "@typescript-eslint/type-utils": "8.71.0", + "@typescript-eslint/utils": "8.71.0", + "@typescript-eslint/visitor-keys": "8.71.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.71.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.11.tgz", + "integrity": "sha512-YChdK5txDjwGUvgR7oCJcLGkwi3LDh8Zx8tS7ndYciLEg/oOCL26VUBSUEtgS7EjiybcBqFxh5j0rAdDX7/bbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.71.0.tgz", + "integrity": "sha512-CG4nPk1f2zc8yw4pALqHsFYH2hdo+h1T9daSp21+Hnxi9LOE3GT9hAfTKJCBXVNM2GmYs1eMEP615wPoeOgk3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.71.0", + "@typescript-eslint/types": "8.71.0", + "@typescript-eslint/typescript-estree": "8.71.0", + "@typescript-eslint/visitor-keys": "8.71.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.71.0.tgz", + "integrity": "sha512-aABjw5rjBacYONVPaPiWOCjJu0vEF4a25iQuodlmQYL1trtLZ0X/y+2Vzl3BKI1odM4LnwLE1oUDXYp1wzx1TQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.71.0", + "@typescript-eslint/types": "^8.71.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.71.0.tgz", + "integrity": "sha512-gWF0BhUcnjZxSpLE8ngS/59n2SB0J3YqRxvX1+2aoRJk9hNtHSLOV+TcarFiOr5ipXm3yc1QrI4c9YZc8zyCxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.71.0", + "@typescript-eslint/visitor-keys": "8.71.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.71.0.tgz", + "integrity": "sha512-Z1UlWHADEK2Mlb9NpWfDeSjqoZ5EyrOv4R3eQpbkzqn/EwaIdOpXXupEA1+0ZIOSJSZZDBHG0BrQyN8zUG6Pwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.71.0.tgz", + "integrity": "sha512-i8uO1qbdxeKgRnS5sCRt6On3/nfo2d2DwQe3Yvjx543zLy7r8ySqRuPPiIIXAhS03U0v5NfAFx+rUgxFzKKwNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.71.0", + "@typescript-eslint/typescript-estree": "8.71.0", + "@typescript-eslint/utils": "8.71.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.71.0.tgz", + "integrity": "sha512-cJ4OoxPGWvFnBTnSZyaU+qJzGTqPTGJY+gDchj6cRyLRdmIdt4rcsE4twj+zPfrNiWuVi38wijHzShL++Z9atQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.71.0.tgz", + "integrity": "sha512-PEEF4G5sLLWAS5BpPrUvms4ySZkiBQQZM4z+3ReI46axK5Vqr/vXBQatJQIZZOYdGyPUAKTtsrWzpqKuU+3DEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.71.0", + "@typescript-eslint/tsconfig-utils": "8.71.0", + "@typescript-eslint/types": "8.71.0", + "@typescript-eslint/visitor-keys": "8.71.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.71.0.tgz", + "integrity": "sha512-pKR/tEMVrXZG23UFKUn5BQf3zfmfk7KQceI2cGzywZ5nxM5Eu3hEJU1utjWzydtzBbcJAQhHN8iPCxobHpPcZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.71.0", + "@typescript-eslint/types": "8.71.0", + "@typescript-eslint/typescript-estree": "8.71.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.71.0.tgz", + "integrity": "sha512-8eQ9R218XORK+KLosnf4bu/QsUXvUyVwTbArg7/0NMB1Pu87OJKvj4nhFblkYE8gQV73mW1dx1ptlPCkwRGa7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.71.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/cacheable": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz", + "integrity": "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/memory": "^2.2.0", + "@cacheable/utils": "^2.5.0", + "hookified": "^1.15.0", + "keyv": "^5.6.0", + "qified": "^0.10.1" + } + }, + "node_modules/chromium-bidi": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-17.0.2.tgz", + "integrity": "sha512-5v9GQFhTktFvotn/OFNJBmKLKRAb6n9r0bVCwf7sHgWc3/JryK0bj1nn93L3pHFrfgcsu6Be6EWsDi+1XHTGDg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "engines": { + "node": ">=20.19.0 <22.0.0 || >=22.12.0" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/devtools-protocol": { + "version": "0.0.1687809", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1687809.tgz", + "integrity": "sha512-t7kSb+UKYdyxcmqy98U2P86ne6rCve3DIJ4+lp54i/hhClgFuG35JrLj23rO14Jw3sWDsp7i+22QhNU0+/qk8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.11.0.tgz", + "integrity": "sha512-P7a6UEEqb9G95MYAtqkmsTbVXIYyzIfl6NGOIJk162PaahFxFyeGcrlXYFSiagECg4sEm8IseJdZBKR3rx6MsQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.7.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.3", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "11.1.5 || >11.1.6 <12", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.5.tgz", + "integrity": "sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^6.1.23" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "6.1.23", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.23.tgz", + "integrity": "sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cacheable": "^2.5.0", + "flatted": "^3.4.2", + "hookified": "^1.15.0" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz", + "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.13.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.13.0.tgz", + "integrity": "sha512-RwMTC61u7hrG3ZJMkZQOK4JLJrKS8QtoTii63EmWvFXHqycY9FObBJQCbsLxSO8kjKhWE5kV1GC+Vb1g3RI0Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hashery": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", + "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^1.15.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/hookified": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", + "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" + }, + "node_modules/modern-tar": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.8.5.tgz", + "integrity": "sha512-snEhs+6G5Tjd4I7tLCDOaoln2RgE0bD19RzEKgvgK2hZ5VKy3MpLhLTZ2fWpXSTg4K2cyPwp+VHATFJhxfnOeA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.9.tgz", + "integrity": "sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/puppeteer-core": { + "version": "25.12.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-25.12.0.tgz", + "integrity": "sha512-z6LQUt5SH7jwGtGA+nMtJuEjZIsqPiwQ+EFHCYuVDQrJpvifNmmGuyDvTzAKPyFzihxgHc5f3aMzu2kn8Ps37A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "3.2.3", + "chromium-bidi": "17.0.2", + "devtools-protocol": "0.0.1687809", + "typed-query-selector": "^2.12.2", + "webdriver-bidi-protocol": "0.4.3", + "ws": "^8.21.3" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/qified": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/qified/-/qified-0.10.1.tgz", + "integrity": "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^2.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/qified/node_modules/hookified": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.2.0.tgz", + "integrity": "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.3.0.tgz", + "integrity": "sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typed-query-selector": { + "version": "2.12.3", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.3.tgz", + "integrity": "sha512-/dtuMZyutHLlrxWhhC1uib00ELUr2dZ8C9B8YzZYsJmeE3V56OxRblrSM2UaF142pcMtak7ehZ6+poWGeehNIg==", + "dev": true, + "license": "MIT" + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.71.0.tgz", + "integrity": "sha512-fBdHYiqQ14RW6mOMXD14Svn82ZsCYAoQSzGRzyEjR59S5A2Krh/l7fGTOQ7iCr8gGy/mHVXtEF7s5fgjEdV0Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.71.0", + "@typescript-eslint/parser": "8.71.0", + "@typescript-eslint/typescript-estree": "8.71.0", + "@typescript-eslint/utils": "8.71.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/webdriver-bidi-protocol": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.3.tgz", + "integrity": "sha512-uuN0goWfxP22B7J/uAgBpOYNPttC+XVseYE+rSY5+rQ+YBeVz/VORw8WbmLVcqW78zNg5A4qnjNXYUWR3il2ig==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ws": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz", + "integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.2.0.tgz", + "integrity": "sha512-9OpKOLeaoNFecEp7P6iYbzze/5CqWoH7N3SMs/6y1XF4nMvFMspEGZzJ6uFi9MmQwXhyzqYoSEKO3tvA3Z/o2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^8.2.1", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..477bbf7 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "earthbuild-web", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "test": "node --test tests/**/*.test.mjs", + "lint": "eslint assets/ts tests && prettier --check assets/ts tests", + "format": "prettier --write assets/ts tests" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "eslint": "^10.11.0", + "eslint-config-prettier": "^10.1.8", + "globals": "^17.13.0", + "prettier": "^3.9.9", + "puppeteer-core": "^25.12.0", + "typescript-eslint": "^8.71.0" + } +} diff --git a/static/casts/cpp-cached.cast b/static/casts/cpp-cached.cast new file mode 100644 index 0000000..27630f2 --- /dev/null +++ b/static/casts/cpp-cached.cast @@ -0,0 +1,12 @@ +{"version": 2, "width": 80, "height": 14, "timestamp": 1724580000, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}} +[0.05, "o", "\u001b[1;36m$ earth +build\u001b[0m\r\n"] +[0.15, "o", "\u001b[90m─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)\u001b[0m\r\n"] +[0.25, "o", "\u001b[1m─── Build 🔧\u001b[0m\r\n"] +[0.35, "o", " \u001b[36m+base\u001b[0m | \u001b[32m*cached*\u001b[0m --> FROM alpine:3.19 (gcc, cmake, ninja, ccache)\r\n"] +[0.45, "o", " \u001b[36m+src\u001b[0m | \u001b[32m*cached*\u001b[0m --> COPY CMakeLists.txt src/ ./\r\n"] +[0.55, "o", " \u001b[36m+cmake\u001b[0m | \u001b[32m*cached*\u001b[0m --> RUN cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release\r\n"] +[0.70, "o", " \u001b[36m+build\u001b[0m | \u001b[32m*cached*\u001b[0m --> RUN --mount=type=cache,target=/root/.ccache ninja -C build\r\n"] +[0.85, "o", " \u001b[36m+build\u001b[0m | ccache hit rate: \u001b[32m100.0%\u001b[0m (38/38 targets)\r\n"] +[0.95, "o", " \u001b[36m+build\u001b[0m | \u001b[32m*cached*\u001b[0m --> SAVE ARTIFACT build/my-app AS LOCAL ./bin/my-app\r\n"] +[1.20, "o", "\u001b[1;32m=========================== 🌍 Earth Build ✅ SUCCESS [1.2s] ===========================\u001b[0m\r\n"] +[1.25, "o", "\u001b[90mOutput saved: ./bin/my-app (8.4 MB) • 25.3x Speedup\u001b[0m\r\n"] diff --git a/static/casts/cpp-cold.cast b/static/casts/cpp-cold.cast new file mode 100644 index 0000000..bbc7e4c --- /dev/null +++ b/static/casts/cpp-cold.cast @@ -0,0 +1,12 @@ +{"version": 2, "width": 80, "height": 14, "timestamp": 1724580000, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}} +[0.05, "o", "\u001b[1;36m$ earth +build\u001b[0m\r\n"] +[0.15, "o", "\u001b[90m─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)\u001b[0m\r\n"] +[0.25, "o", "\u001b[1m─── Build 🔧\u001b[0m\r\n"] +[0.80, "o", " \u001b[36m+base\u001b[0m | --> apt-get update && apt-get install -y build-essential cmake\r\n"] +[1.60, "o", " \u001b[36m+code\u001b[0m | --> COPY src src\r\n"] +[2.40, "o", " \u001b[36m+build\u001b[0m | --> RUN cmake src\r\n"] +[3.50, "o", " \u001b[36m+build\u001b[0m | [1/38] Building CXX object src/CMakeFiles/fibonacci.dir/main.cpp.o\r\n"] +[4.80, "o", " \u001b[36m+build\u001b[0m | [19/38] Building CXX object src/CMakeFiles/fibonacci.dir/calc.cpp.o\r\n"] +[6.20, "o", " \u001b[36m+build\u001b[0m | [38/38] Linking CXX executable fibonacci\r\n"] +[7.40, "o", " \u001b[36m+build\u001b[0m | --> SAVE ARTIFACT fibonacci AS LOCAL fibonacci\r\n"] +[8.10, "o", "\u001b[1;32m=========================== 🌍 Earth Build ✅ SUCCESS [30.4s] ===========================\u001b[0m\r\n"] diff --git a/static/casts/go-cached.cast b/static/casts/go-cached.cast new file mode 100644 index 0000000..8b01418 --- /dev/null +++ b/static/casts/go-cached.cast @@ -0,0 +1,13 @@ +{"version": 2, "width": 80, "height": 14, "timestamp": 1724580000, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}} +[0.05, "o", "\u001b[1;36m$ earth +build\u001b[0m\r\n"] +[0.15, "o", "\u001b[90m─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)\u001b[0m\r\n"] +[0.25, "o", "\u001b[1m─── Build 🔧\u001b[0m\r\n"] +[0.35, "o", " \u001b[36m+base\u001b[0m | \u001b[32m*cached*\u001b[0m --> FROM golang:1.27-alpine3.24\r\n"] +[0.45, "o", " \u001b[36m+base\u001b[0m | \u001b[32m*cached*\u001b[0m --> WORKDIR /go-example\r\n"] +[0.55, "o", " \u001b[36m+deps\u001b[0m | \u001b[32m*cached*\u001b[0m --> COPY go.mod go.sum ./\r\n"] +[0.65, "o", " \u001b[36m+deps\u001b[0m | \u001b[32m*cached*\u001b[0m --> RUN go mod download\r\n"] +[0.75, "o", " \u001b[36m+build\u001b[0m | \u001b[32m*cached*\u001b[0m --> COPY main.go .\r\n"] +[0.85, "o", " \u001b[36m+build\u001b[0m | \u001b[32m*cached*\u001b[0m --> RUN go build -o build/go-example main.go\r\n"] +[0.95, "o", " \u001b[36m+build\u001b[0m | \u001b[32m*cached*\u001b[0m --> SAVE ARTIFACT build/go-example AS LOCAL build/go-example\r\n"] +[1.10, "o", "\u001b[1;32m=========================== 🌍 Earth Build ✅ SUCCESS [1.1s] ===========================\u001b[0m\r\n"] +[1.15, "o", "\u001b[90mOutput saved: ./build/go-example (4.2 MB)\u001b[0m\r\n"] diff --git a/static/casts/go-cold.cast b/static/casts/go-cold.cast new file mode 100644 index 0000000..49f1b1f --- /dev/null +++ b/static/casts/go-cold.cast @@ -0,0 +1,14 @@ +{"version": 2, "width": 80, "height": 14, "timestamp": 1724580000, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}} +[0.05, "o", "\u001b[1;36m$ earth +build\u001b[0m\r\n"] +[0.15, "o", "\u001b[90m─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)\u001b[0m\r\n"] +[0.25, "o", "\u001b[1m─── Build 🔧\u001b[0m\r\n"] +[0.50, "o", " \u001b[36m+base\u001b[0m | --> FROM golang:1.27-alpine3.24\r\n"] +[1.00, "o", " \u001b[36m+base\u001b[0m | [internal] pulling image docker.io/library/golang:1.27-alpine3.24\r\n"] +[1.80, "o", " \u001b[36m+base\u001b[0m | --> WORKDIR /go-example\r\n"] +[2.20, "o", " \u001b[36m+deps\u001b[0m | --> COPY go.mod go.sum ./\r\n"] +[2.60, "o", " \u001b[36m+deps\u001b[0m | --> RUN go mod download\r\n"] +[3.50, "o", " \u001b[36m+build\u001b[0m | --> COPY main.go .\r\n"] +[4.20, "o", " \u001b[36m+build\u001b[0m | --> RUN go build -o build/go-example main.go\r\n"] +[5.60, "o", " \u001b[36m+build\u001b[0m | --> SAVE ARTIFACT build/go-example AS LOCAL build/go-example\r\n"] +[6.20, "o", "\u001b[1;32m=========================== 🌍 Earth Build ✅ SUCCESS [6.2s] ===========================\u001b[0m\r\n"] +[6.25, "o", "\u001b[90mOutput saved: ./build/go-example (4.2 MB)\u001b[0m\r\n"] diff --git a/static/casts/java-cached.cast b/static/casts/java-cached.cast new file mode 100644 index 0000000..b0ecd1b --- /dev/null +++ b/static/casts/java-cached.cast @@ -0,0 +1,10 @@ +{"version": 2, "width": 80, "height": 14, "timestamp": 1724580000, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}} +[0.05, "o", "\u001b[1;36m$ earth +docker\u001b[0m\r\n"] +[0.15, "o", "\u001b[90m─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)\u001b[0m\r\n"] +[0.25, "o", "\u001b[1m─── Build 🔧\u001b[0m\r\n"] +[0.35, "o", " \u001b[36m+deps\u001b[0m | \u001b[32m*cached*\u001b[0m --> FROM amazoncorretto:25.0.4 (Gradle 8.7 + OpenJDK 21)\r\n"] +[0.45, "o", " \u001b[36m+build\u001b[0m | \u001b[32m*cached*\u001b[0m --> RUN gradle build && gradle install\r\n"] +[0.55, "o", " \u001b[36m+build\u001b[0m | > Task :compileJava UP-TO-DATE\r\n"] +[0.65, "o", " \u001b[36m+build\u001b[0m | > Task :installDist UP-TO-DATE\r\n"] +[0.75, "o", " \u001b[36m+docker\u001b[0m | \u001b[32m*cached*\u001b[0m --> SAVE IMAGE --push earthbuild/examples:java\r\n"] +[0.90, "o", "\u001b[1;32m=========================== 🌍 Earth Build ✅ SUCCESS [0.9s] ===========================\u001b[0m\r\n"] diff --git a/static/casts/java-cold.cast b/static/casts/java-cold.cast new file mode 100644 index 0000000..15be04e --- /dev/null +++ b/static/casts/java-cold.cast @@ -0,0 +1,11 @@ +{"version": 2, "width": 80, "height": 14, "timestamp": 1724580000, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}} +[0.05, "o", "\u001b[1;36m$ earth +build\u001b[0m\r\n"] +[0.15, "o", "\u001b[90m─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)\u001b[0m\r\n"] +[0.25, "o", "\u001b[1m─── Build 🔧\u001b[0m\r\n"] +[0.60, "o", " \u001b[36m+deps\u001b[0m | --> apk add gradle openjdk21 (844 MiB in 50 packages)\r\n"] +[1.80, "o", " \u001b[36m+build\u001b[0m | --> COPY src src\r\n"] +[3.50, "o", " \u001b[36m+build\u001b[0m | > Task :compileJava\r\n"] +[4.80, "o", " \u001b[36m+build\u001b[0m | > Task :build (BUILD SUCCESSFUL in 3s)\r\n"] +[5.90, "o", " \u001b[36m+build\u001b[0m | > Task :installDist\r\n"] +[6.70, "o", " \u001b[36m+build\u001b[0m | --> SAVE ARTIFACT bin bin, lib lib\r\n"] +[7.40, "o", "\u001b[1;32m=========================== 🌍 Earth Build ✅ SUCCESS [16.8s] ===========================\u001b[0m\r\n"] diff --git a/static/casts/js-cached.cast b/static/casts/js-cached.cast new file mode 100644 index 0000000..866b674 --- /dev/null +++ b/static/casts/js-cached.cast @@ -0,0 +1,11 @@ +{"version": 2, "width": 80, "height": 14, "timestamp": 1724580000, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}} +[0.05, "o", "\u001b[1;36m$ earth +build\u001b[0m\r\n"] +[0.15, "o", "\u001b[90m─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)\u001b[0m\r\n"] +[0.25, "o", "\u001b[1m─── Build 🔧\u001b[0m\r\n"] +[0.35, "o", " \u001b[36m+base\u001b[0m | \u001b[32m*cached*\u001b[0m --> FROM node:20-alpine3.19\r\n"] +[0.45, "o", " \u001b[36m+deps\u001b[0m | \u001b[32m*cached*\u001b[0m --> COPY package.json package-lock.json ./\r\n"] +[0.55, "o", " \u001b[36m+deps\u001b[0m | \u001b[32m*cached*\u001b[0m --> RUN --mount=type=cache,target=/root/.npm npm ci\r\n"] +[0.65, "o", " \u001b[36m+src\u001b[0m | \u001b[32m*cached*\u001b[0m --> COPY src/ webpack.config.js ./\r\n"] +[0.75, "o", " \u001b[36m+build\u001b[0m | \u001b[32m*cached*\u001b[0m --> RUN npm run build (Webpack 5)\r\n"] +[0.85, "o", " \u001b[36m+build\u001b[0m | \u001b[32m*cached*\u001b[0m --> SAVE ARTIFACT dist/ /dist AS LOCAL dist/\r\n"] +[1.00, "o", "\u001b[1;32m=========================== 🌍 Earth Build ✅ SUCCESS [0.8s] ===========================\u001b[0m\r\n"] diff --git a/static/casts/js-cold.cast b/static/casts/js-cold.cast new file mode 100644 index 0000000..df44413 --- /dev/null +++ b/static/casts/js-cold.cast @@ -0,0 +1,11 @@ +{"version": 2, "width": 80, "height": 14, "timestamp": 1724580000, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}} +[0.05, "o", "\u001b[1;36m$ earth +build\u001b[0m\r\n"] +[0.15, "o", "\u001b[90m─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)\u001b[0m\r\n"] +[0.25, "o", "\u001b[1m─── Build 🔧\u001b[0m\r\n"] +[0.60, "o", " \u001b[36m+base\u001b[0m | --> FROM node:26.7.0-alpine3.23\r\n"] +[1.20, "o", " \u001b[36m+deps\u001b[0m | --> COPY package.json npm-shrinkwrap.json ./\r\n"] +[2.20, "o", " \u001b[36m+deps\u001b[0m | added 149 packages in 1s\r\n"] +[3.50, "o", " \u001b[36m+build\u001b[0m | --> COPY src src, dist dist\r\n"] +[4.80, "o", " \u001b[36m+build\u001b[0m | webpack 5.109.2 compiled successfully in 123 ms\r\n"] +[5.80, "o", " \u001b[36m+build\u001b[0m | --> SAVE ARTIFACT dist +build/dist AS LOCAL dist\r\n"] +[6.40, "o", "\u001b[1;32m=========================== 🌍 Earth Build ✅ SUCCESS [4.5s] ===========================\u001b[0m\r\n"] diff --git a/static/casts/monorepo-cached.cast b/static/casts/monorepo-cached.cast new file mode 100644 index 0000000..fe609fc --- /dev/null +++ b/static/casts/monorepo-cached.cast @@ -0,0 +1,9 @@ +{"version": 2, "width": 80, "height": 14, "timestamp": 1724580000, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}} +[0.05, "o", "\u001b[1;36m$ earth +all-unit-test\u001b[0m\r\n"] +[0.15, "o", "\u001b[90m─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)\u001b[0m\r\n"] +[0.25, "o", "\u001b[1m─── Resolving monorepo target dependency graph (+all-unit-test)...\u001b[0m\r\n"] +[0.35, "o", "\u001b[33m─── 🔀 Running 3 targets in PARALLEL across workspace\u001b[0m\r\n"] +[0.55, "o", " \u001b[36m./libs/hello+unit-test\u001b[0m | \u001b[32m*cached*\u001b[0m --> go test ./... [CACHED]\r\n"] +[0.75, "o", " \u001b[36m./services/one+unit-test\u001b[0m | \u001b[32m*cached*\u001b[0m --> go test ./... [CACHED]\r\n"] +[0.95, "o", " \u001b[36m./services/two+unit-test\u001b[0m | \u001b[32m*cached*\u001b[0m --> go test ./... [CACHED]\r\n"] +[1.35, "o", "\u001b[1;32m=========================== 🌍 Earth Build ✅ SUCCESS [1.4s] ===========================\u001b[0m\r\n"] diff --git a/static/casts/monorepo-cold.cast b/static/casts/monorepo-cold.cast new file mode 100644 index 0000000..40b3b8a --- /dev/null +++ b/static/casts/monorepo-cold.cast @@ -0,0 +1,9 @@ +{"version": 2, "width": 80, "height": 14, "timestamp": 1724580000, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}} +[0.05, "o", "\u001b[1;36m$ earth +all-unit-test\u001b[0m\r\n"] +[0.15, "o", "\u001b[90m─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)\u001b[0m\r\n"] +[0.30, "o", "\u001b[1m─── Resolving monorepo target dependency graph (+all-unit-test)...\u001b[0m\r\n"] +[0.60, "o", "\u001b[33m─── 🔀 Executing 3 independent targets concurrently\u001b[0m\r\n"] +[1.20, "o", " \u001b[36m./libs/hello+unit-test\u001b[0m | ok github.com/EarthBuild/examples/go-monorepo/libs/hello 0.12s\r\n"] +[1.80, "o", " \u001b[36m./services/one+unit-test\u001b[0m | ok github.com/EarthBuild/examples/go-monorepo/services/one 0.34s\r\n"] +[2.40, "o", " \u001b[36m./services/two+unit-test\u001b[0m | ok github.com/EarthBuild/examples/go-monorepo/services/two 0.28s\r\n"] +[2.70, "o", "\u001b[1;32m=========================== 🌍 Earth Build ✅ SUCCESS [24.5s] ===========================\u001b[0m\r\n"] diff --git a/static/casts/python-cached.cast b/static/casts/python-cached.cast new file mode 100644 index 0000000..1cdd0ef --- /dev/null +++ b/static/casts/python-cached.cast @@ -0,0 +1,11 @@ +{"version": 2, "width": 80, "height": 14, "timestamp": 1724580000, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}} +[0.05, "o", "\u001b[1;36m$ earth +test\u001b[0m\r\n"] +[0.15, "o", "\u001b[90m─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)\u001b[0m\r\n"] +[0.25, "o", "\u001b[1m─── Build 🔧\u001b[0m\r\n"] +[0.35, "o", " \u001b[36m+base\u001b[0m | \u001b[32m*cached*\u001b[0m --> FROM python:3.12-slim\r\n"] +[0.45, "o", " \u001b[36m+deps\u001b[0m | \u001b[32m*cached*\u001b[0m --> COPY requirements.txt ./\r\n"] +[0.55, "o", " \u001b[36m+deps\u001b[0m | \u001b[32m*cached*\u001b[0m --> RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt\r\n"] +[0.65, "o", " \u001b[36m+src\u001b[0m | \u001b[32m*cached*\u001b[0m --> COPY src/ tests/ ./\r\n"] +[0.75, "o", " \u001b[36m+test\u001b[0m | \u001b[32m*cached*\u001b[0m --> RUN pytest --disable-warnings -v\r\n"] +[0.85, "o", " \u001b[36m+test\u001b[0m | ======================== 42 passed in 0.18s ========================\r\n"] +[1.00, "o", "\u001b[1;32m=========================== 🌍 Earth Build ✅ SUCCESS [0.9s] ===========================\u001b[0m\r\n"] diff --git a/static/casts/python-cold.cast b/static/casts/python-cold.cast new file mode 100644 index 0000000..6a278b9 --- /dev/null +++ b/static/casts/python-cold.cast @@ -0,0 +1,13 @@ +{"version": 2, "width": 80, "height": 14, "timestamp": 1724580000, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}} +[0.05, "o", "\u001b[1;36m$ earth +build\u001b[0m\r\n"] +[0.15, "o", "\u001b[90m─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)\u001b[0m\r\n"] +[0.25, "o", "\u001b[1m─── Build 🔧\u001b[0m\r\n"] +[0.60, "o", " \u001b[36m+base\u001b[0m | --> FROM python:3\r\n"] +[1.00, "o", " \u001b[36m+base\u001b[0m | --> WORKDIR /code\r\n"] +[1.50, "o", " \u001b[36m+deps\u001b[0m | --> RUN pip install wheel\r\n"] +[2.20, "o", " \u001b[36m+deps\u001b[0m | --> COPY requirements.txt ./\r\n"] +[3.00, "o", " \u001b[36m+deps\u001b[0m | --> RUN pip wheel -r requirements.txt --wheel-dir=wheels\r\n"] +[4.20, "o", " \u001b[36m+deps\u001b[0m | Downloading markdown-3.10.3-py3-none-any.whl (110 kB)\r\n"] +[5.00, "o", " \u001b[36m+build\u001b[0m | --> COPY src src\r\n"] +[5.80, "o", " \u001b[36m+build\u001b[0m | --> SAVE ARTIFACT src /src, wheels /wheels\r\n"] +[6.40, "o", "\u001b[1;32m=========================== 🌍 Earth Build ✅ SUCCESS [4.8s] ===========================\u001b[0m\r\n"] diff --git a/static/casts/rust-cached.cast b/static/casts/rust-cached.cast new file mode 100644 index 0000000..8543d52 --- /dev/null +++ b/static/casts/rust-cached.cast @@ -0,0 +1,12 @@ +{"version": 2, "width": 80, "height": 14, "timestamp": 1724580000, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}} +[0.05, "o", "\u001b[1;36m$ earth +build\u001b[0m\r\n"] +[0.15, "o", "\u001b[90m─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)\u001b[0m\r\n"] +[0.25, "o", "\u001b[1m─── Build 🔧\u001b[0m\r\n"] +[0.35, "o", " \u001b[36m+base\u001b[0m | \u001b[32m*cached*\u001b[0m --> FROM rust:1.80-alpine3.20 (sccache)\r\n"] +[0.45, "o", " \u001b[36m+deps\u001b[0m | \u001b[32m*cached*\u001b[0m --> COPY Cargo.toml Cargo.lock ./\r\n"] +[0.55, "o", " \u001b[36m+src\u001b[0m | \u001b[32m*cached*\u001b[0m --> COPY src/ ./\r\n"] +[0.70, "o", " \u001b[36m+build\u001b[0m | \u001b[32m*cached*\u001b[0m --> RUN --mount=type=cache,target=/root/.cache/sccache cargo build --release\r\n"] +[0.85, "o", " \u001b[36m+build\u001b[0m | sccache: \u001b[32m100% cache hits\u001b[0m (142 compiled crates skipped)\r\n"] +[0.95, "o", " \u001b[36m+build\u001b[0m | \u001b[32m*cached*\u001b[0m --> SAVE ARTIFACT target/release/app AS LOCAL ./bin/rust-app\r\n"] +[1.30, "o", "\u001b[1;32m=========================== 🌍 Earth Build ✅ SUCCESS [1.3s] ===========================\u001b[0m\r\n"] +[1.35, "o", "\u001b[90mOutput saved: ./bin/rust-app (6.1 MB) • 32.5x Speedup\u001b[0m\r\n"] diff --git a/static/casts/rust-cold.cast b/static/casts/rust-cold.cast new file mode 100644 index 0000000..55450e9 --- /dev/null +++ b/static/casts/rust-cold.cast @@ -0,0 +1,12 @@ +{"version": 2, "width": 80, "height": 14, "timestamp": 1724580000, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}} +[0.05, "o", "\u001b[1;36m$ earth +build\u001b[0m\r\n"] +[0.15, "o", "\u001b[90m─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)\u001b[0m\r\n"] +[0.25, "o", "\u001b[1m─── Build 🔧\u001b[0m\r\n"] +[0.80, "o", " \u001b[36m+base\u001b[0m | --> FROM rust:slim-bookworm\r\n"] +[1.50, "o", " \u001b[36m+deps\u001b[0m | --> Updating crates.io index\r\n"] +[2.50, "o", " \u001b[36m+deps\u001b[0m | --> Downloading tokio, serde, syn, clap...\r\n"] +[4.00, "o", " \u001b[36m+build\u001b[0m | Compiling syn v2.0.31, serde v1.0.188, clap v4.4.2\r\n"] +[5.50, "o", " \u001b[36m+build\u001b[0m | Compiling example-rust v0.1.0 (/rustexample)\r\n"] +[7.00, "o", " \u001b[36m+build\u001b[0m | Finished release [optimized] target(s) in 13.56s\r\n"] +[8.20, "o", " \u001b[36m+build\u001b[0m | --> SAVE ARTIFACT target/release/example-rust AS LOCAL example-rust\r\n"] +[8.90, "o", "\u001b[1;32m=========================== 🌍 Earth Build ✅ SUCCESS [38.4s] ===========================\u001b[0m\r\n"] diff --git a/static/casts/typescript-cached.cast b/static/casts/typescript-cached.cast new file mode 100644 index 0000000..36c20ac --- /dev/null +++ b/static/casts/typescript-cached.cast @@ -0,0 +1,10 @@ +{"version": 2, "width": 80, "height": 14, "timestamp": 1724580000, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}} +[0.05, "o", "\u001b[1;36m$ earth +all\u001b[0m\r\n"] +[0.15, "o", "\u001b[90m─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)\u001b[0m\r\n"] +[0.25, "o", "\u001b[1m─── Build 🔧\u001b[0m\r\n"] +[0.35, "o", " \u001b[36m+deps\u001b[0m | \u001b[32m*cached*\u001b[0m --> FROM node:26.7.0-alpine3.23 (npm install)\r\n"] +[0.45, "o", " \u001b[36m+build\u001b[0m | \u001b[32m*cached*\u001b[0m --> RUN npx tsc\r\n"] +[0.55, "o", " \u001b[36m+build\u001b[0m | \u001b[32m*cached*\u001b[0m --> SAVE ARTIFACT dist /dist AS LOCAL dist\r\n"] +[0.65, "o", " \u001b[36m+unit-test\u001b[0m | \u001b[32m*cached*\u001b[0m --> RUN npm run test [CACHED]\r\n"] +[0.75, "o", " \u001b[36m+docker\u001b[0m | \u001b[32m*cached*\u001b[0m --> SAVE IMAGE typescript-node-example:latest\r\n"] +[0.90, "o", "\u001b[1;32m=========================== 🌍 Earth Build ✅ SUCCESS [0.9s] ===========================\u001b[0m\r\n"] diff --git a/static/casts/typescript-cold.cast b/static/casts/typescript-cold.cast new file mode 100644 index 0000000..ac0d886 --- /dev/null +++ b/static/casts/typescript-cold.cast @@ -0,0 +1,10 @@ +{"version": 2, "width": 80, "height": 14, "timestamp": 1724580000, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}} +[0.05, "o", "\u001b[1;36m$ earth +build\u001b[0m\r\n"] +[0.15, "o", "\u001b[90m─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)\u001b[0m\r\n"] +[0.25, "o", "\u001b[1m─── Build 🔧\u001b[0m\r\n"] +[0.60, "o", " \u001b[36m+deps\u001b[0m | --> RUN npm install (added 405 packages in 2s)\r\n"] +[1.80, "o", " \u001b[36m+deps\u001b[0m | --> COPY tsconfig.json src src ./\r\n"] +[2.80, "o", " \u001b[36m+build\u001b[0m | --> RUN npx tsc\r\n"] +[4.00, "o", " \u001b[36m+build\u001b[0m | --> SAVE ARTIFACT dist +build/dist AS LOCAL dist\r\n"] +[5.10, "o", " \u001b[36moutput\u001b[0m | [----------] 100% exporting outputs\r\n"] +[5.80, "o", "\u001b[1;32m=========================== 🌍 Earth Build ✅ SUCCESS [9.8s] ===========================\u001b[0m\r\n"] diff --git a/static/casts/zig-cached.cast b/static/casts/zig-cached.cast new file mode 100644 index 0000000..6823b04 --- /dev/null +++ b/static/casts/zig-cached.cast @@ -0,0 +1,11 @@ +{"version": 2, "width": 80, "height": 14, "timestamp": 1724580000, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}} +[0.05, "o", "\u001b[1;36m$ earth +build\u001b[0m\r\n"] +[0.15, "o", "\u001b[90m─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)\u001b[0m\r\n"] +[0.25, "o", "\u001b[1m─── Build 🔧\u001b[0m\r\n"] +[0.35, "o", " \u001b[36m+deps\u001b[0m | \u001b[32m*cached*\u001b[0m --> FROM alpine:3.24.1 (Zig 0.16.0 toolchain)\r\n"] +[0.45, "o", " \u001b[36m+src\u001b[0m | \u001b[32m*cached*\u001b[0m --> COPY build.zig src/ ./\r\n"] +[0.55, "o", " \u001b[36m+build\u001b[0m | \u001b[32m*cached*\u001b[0m --> RUN --mount=type=cache,target=/root/.cache/zig zig build -Doptimize=ReleaseFast\r\n"] +[0.70, "o", " \u001b[36m+build\u001b[0m | zig cache: \u001b[32m100% cache hits\u001b[0m (all AST units up-to-date)\r\n"] +[0.85, "o", " \u001b[36m+build\u001b[0m | \u001b[32m*cached*\u001b[0m --> SAVE ARTIFACT zig-out/bin/fizzbuzz AS LOCAL ./bin/fizzbuzz\r\n"] +[1.05, "o", "\u001b[1;32m=========================== 🌍 Earth Build ✅ SUCCESS [0.8s] ===========================\u001b[0m\r\n"] +[1.10, "o", "\u001b[90mOutput saved: ./bin/fizzbuzz (1.8 MB static binary)\u001b[0m\r\n"] diff --git a/static/casts/zig-cold.cast b/static/casts/zig-cold.cast new file mode 100644 index 0000000..a3cb639 --- /dev/null +++ b/static/casts/zig-cold.cast @@ -0,0 +1,12 @@ +{"version": 2, "width": 80, "height": 14, "timestamp": 1724580000, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}} +[0.05, "o", "\u001b[1;36m$ earth +build\u001b[0m\r\n"] +[0.15, "o", "\u001b[90m─── Init 🚀 Found buildkit daemon as podman container (earth-buildkitd)\u001b[0m\r\n"] +[0.25, "o", "\u001b[1m─── Build 🔧\u001b[0m\r\n"] +[0.60, "o", " \u001b[36m+deps\u001b[0m | --> Downloading Zig 0.16.0 for aarch64-linux\r\n"] +[1.50, "o", " \u001b[36m+deps\u001b[0m | --> Extracting /zig (toolchain ready)\r\n"] +[2.20, "o", " \u001b[36m+src\u001b[0m | --> COPY build.zig src/ ./\r\n"] +[3.10, "o", " \u001b[36m+build\u001b[0m | --> RUN zig build\r\n"] +[4.20, "o", " \u001b[36m+build\u001b[0m | [1/4] Compiling src/main.zig (LLVM backend)\r\n"] +[5.30, "o", " \u001b[36m+build\u001b[0m | [4/4] Linking binary zig-out/bin/fizzbuzz\r\n"] +[6.00, "o", " \u001b[36m+build\u001b[0m | --> SAVE ARTIFACT zig-out/bin/fizzbuzz AS LOCAL fizzbuzz\r\n"] +[6.70, "o", "\u001b[1;32m=========================== 🌍 Earth Build ✅ SUCCESS [5.9s] ===========================\u001b[0m\r\n"] diff --git a/static/css/style.css b/static/css/style.css new file mode 100644 index 0000000..adf3d80 --- /dev/null +++ b/static/css/style.css @@ -0,0 +1,3479 @@ + +/* ========================================================================== + EarthBuild Design System & CSS Custom Properties + -------------------------------------------------------------------------- + Semantic Accents: + - Primary / Emerald (#10B981 / #34D399): Zero-Work Rebuilds, Cache Hits, ROI + - Sky Blue (#06B6D4 / #38BDF8): Incremental Builds, BuildKit, Language Tags + - Amber (#F59E0B): Compiler warnings, compilation steps + - Rose / Red (#EF4444 / #F87171): Clean Builds (baseline cold runs), errors + ========================================================================== */ + +:root { + --bg-primary: #080C14; + --bg-secondary: #0D1322; + --bg-tertiary: #131C31; + --bg-card: rgba(15, 23, 42, 0.7); + --bg-card-hover: rgba(30, 41, 59, 0.8); + --bg-glass: rgba(8, 12, 20, 0.85); + + --border-subtle: rgba(255, 255, 255, 0.08); + --border-hover: rgba(52, 211, 153, 0.35); + --border-focus: #10B981; + + --accent-primary: #10B981; + --accent-primary-light: #34D399; + --accent-primary-dark: #059669; + --accent-primary-glow: rgba(16, 185, 129, 0.25); + + --accent-secondary: #06B6D4; + --accent-secondary-light: #22D3EE; + --accent-amber: #F59E0B; + --accent-rose: #F43F5E; + --accent-purple: #A855F7; + + --text-primary: #F8FAFC; + --text-secondary: #94A3B8; + --text-muted: #94A3B8; + --text-inverted: #080C14; + + --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + --font-mono: 'JetBrains Mono', SFMono-Regular, Menlo, Monaco, Consolas, monospace; + + --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.4); + --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.5); + --shadow-lg: 0 16px 40px rgba(0, 0, 0, 0.6); + --shadow-glow: 0 0 30px rgba(16, 185, 129, 0.2); + --shadow-glow-cyan: 0 0 30px rgba(6, 182, 212, 0.2); + + --radius-sm: 6px; + --radius-md: 10px; + --radius-lg: 16px; + --radius-xl: 24px; + --radius-full: 9999px; + + --container-max: 1200px; + --container-narrow: 880px; + + /* Material Design 3 (M3) Window Size Class Tokens */ + --m3-compact-max: 599px; /* Phone / Compact (< 600px) */ + --m3-medium-min: 600px; /* Tablet / Medium (600px - 839px) */ + --m3-medium-max: 839px; + --m3-expanded-min: 840px; /* Laptop / Expanded (840px - 1199px) */ + --m3-large-min: 1200px; /* Desktop Monitor / Large (>= 1200px) */ +} + +@media (min-width: 1440px) { + :root { + --container-max: 1360px; + } +} + +@media (min-width: 1700px) { + :root { + --container-max: 1440px; + } +} + +/* Reset & Base */ +*, *::before, *::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html { + font-family: var(--font-sans); + background-color: var(--bg-primary); + color: var(--text-primary); + line-height: 1.6; + scroll-behavior: smooth; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + min-height: 100vh; + display: flex; + flex-direction: column; + overflow-x: hidden; + position: relative; + background: radial-gradient(circle at 50% 0%, rgba(16, 185, 129, 0.08) 0%, transparent 60%), + radial-gradient(circle at 10% 40%, rgba(6, 182, 212, 0.04) 0%, transparent 40%), + radial-gradient(circle at 90% 70%, rgba(16, 185, 129, 0.05) 0%, transparent 50%), + var(--bg-primary); +} + +/* Background grid effect */ +.bg-grid-overlay { + position: fixed; + inset: 0; + background-image: + linear-gradient(to right, rgba(255, 255, 255, 0.02) 1px, transparent 1px), + linear-gradient(to bottom, rgba(255, 255, 255, 0.02) 1px, transparent 1px); + background-size: 40px 40px; + pointer-events: none; + z-index: 0; +} + +.container { + width: 100%; + max-width: var(--container-max); + margin-left: auto; + margin-right: auto; + padding-left: 1.5rem; + padding-right: 1.5rem; + position: relative; + z-index: 1; +} + +.container-narrow { + max-width: var(--container-narrow); +} + +/* Typography */ +h1, h2, h3, h4, h5, h6 { + color: var(--text-primary); + font-weight: 700; + line-height: 1.25; + letter-spacing: -0.02em; +} + +h1 { font-size: clamp(2.5rem, 5vw, 4rem); font-weight: 800; } +h2 { font-size: clamp(2rem, 3.5vw, 2.75rem); } +h3 { font-size: clamp(1.25rem, 2vw, 1.6rem); } +h4 { font-size: 1.2rem; } + +p { + color: var(--text-secondary); + font-size: 1.05rem; +} + +a { + color: var(--accent-primary-light); + text-decoration: none; + transition: all 0.2s ease; +} + +a:hover { + color: #6EE7B7; +} + +code, pre { + font-family: var(--font-mono); +} + +pre { + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + tab-size: 2; + overflow-x: auto; +} + +/* Headline & Section Continuous Shimmer Animations (Hero & All Container Headers) */ +.hero-text-shimmer, +.section-text-shimmer, +.title-text-shimmer { + background: linear-gradient( + 110deg, + #FFFFFF 0%, + #F8FAFC 18%, + #BAE6FD 34%, + #6EE7B7 48%, + #E0F2FE 62%, + #F8FAFC 80%, + #FFFFFF 100% + ); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + display: inline-block; + max-width: 100%; + vertical-align: top; +} + +.hero-accent-shimmer, +.section-accent-shimmer, +.text-gradient { + background: linear-gradient( + 110deg, + #34D399 0%, + #22D3EE 24%, + #A7F3D0 48%, + #FFFFFF 60%, + #22D3EE 76%, + #34D399 100% + ); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + display: inline-block; + max-width: 100%; + vertical-align: top; + filter: drop-shadow(0 0 18px rgba(52, 211, 153, 0.4)); +} + +.text-gradient-emerald { + background: linear-gradient(135deg, #34D399 0%, #06B6D4 100%); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; +} + +@media (prefers-reduced-motion: reduce) { + .hero-text-shimmer, + .section-text-shimmer, + .title-text-shimmer, + .hero-accent-shimmer, + .section-accent-shimmer, + .text-gradient { + animation: none; + background-position: 0% 50%; + } +} + +.glow-pill { + display: inline-flex; + align-items: center; + gap: 0.6rem; + padding: 0.45rem 1.15rem; + border-radius: var(--radius-full); + background: rgba(16, 185, 129, 0.1); + border: 1px solid rgba(52, 211, 153, 0.3); + color: var(--accent-primary-light); + font-size: 0.875rem; + font-weight: 600; + box-shadow: 0 0 20px rgba(16, 185, 129, 0.15); + margin-bottom: 2rem; +} + +.glow-pill .pulse-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--accent-primary-light); + box-shadow: 0 0 8px var(--accent-primary); + animation: pulse 2s infinite ease-in-out; +} + +@keyframes pulse { + 0%, 100% { opacity: 1; transform: scale(1); } + 50% { opacity: 0.4; transform: scale(0.8); } +} + +/* Buttons */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + padding: 0.75rem 1.4rem; + font-size: 0.95rem; + font-weight: 600; + border-radius: var(--radius-md); + cursor: pointer; + transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1); + border: 1px solid transparent; + white-space: nowrap; +} + +.btn-primary { + background: linear-gradient(135deg, var(--accent-primary) 0%, var(--accent-primary-dark) 100%); + color: #FFFFFF; + box-shadow: 0 4px 14px rgba(16, 185, 129, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.2); +} + +.btn-primary:hover { + background: linear-gradient(135deg, #18D295 0%, var(--accent-primary) 100%); + box-shadow: 0 6px 20px rgba(16, 185, 129, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.3); + transform: translateY(-1px); + color: #FFFFFF; +} + +.btn-secondary { + background: var(--bg-card); + color: var(--text-primary); + border: 1px solid var(--border-subtle); + backdrop-filter: blur(8px); +} + +.btn-secondary:hover { + background: var(--bg-card-hover); + border-color: var(--border-hover); + color: #FFFFFF; + transform: translateY(-1px); +} + +.btn-outline-glow { + background: rgba(16, 185, 129, 0.05); + color: var(--accent-primary-light); + border: 1px solid rgba(52, 211, 153, 0.3); +} + +.btn-outline-glow:hover { + background: rgba(16, 185, 129, 0.15); + border-color: var(--accent-primary-light); + box-shadow: 0 0 15px rgba(16, 185, 129, 0.25); + color: #FFFFFF; +} + +.btn-lg { + padding: 0.9rem 1.8rem; + font-size: 1.05rem; + border-radius: var(--radius-lg); +} + +/* Navigation */ +.navbar { + position: relative; + z-index: 100; + background: var(--bg-glass); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + border-bottom: 1px solid var(--border-subtle); + transition: all 0.3s ease; +} + +.navbar-container { + display: flex; + align-items: center; + justify-content: space-between; + height: 72px; +} + +.navbar-brand { + display: flex; + align-items: center; + gap: 0.75rem; + font-weight: 800; + font-size: 1.3rem; + color: var(--text-primary); +} + +.navbar-brand img { + height: 38px; + width: auto; +} + +.nav-links { + display: flex; + align-items: center; + gap: 1.75rem; + list-style: none; +} + +.nav-link { + color: var(--text-secondary); + font-size: 0.95rem; + font-weight: 500; + transition: color 0.2s ease; +} + +.nav-link:hover, .nav-link.active { + color: var(--text-primary); +} + +.nav-actions { + display: flex; + align-items: center; + gap: 1rem; +} + +.github-badge-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + background: var(--bg-secondary); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-full); + color: var(--text-secondary); + transition: all 0.2s ease; + flex-shrink: 0; +} + +.github-badge-btn:hover { + border-color: var(--border-hover); + color: #FFFFFF; + background: rgba(255, 255, 255, 0.08); + transform: translateY(-1px); +} + +.mobile-menu-toggle { + display: none; + background: none; + border: none; + color: var(--text-primary); + font-size: 1.5rem; + cursor: pointer; + padding: 0.5rem; +} + +/* Hero Section */ +.hero-section { + padding: 7rem 0 6rem; + position: relative; + text-align: center; + overflow: visible; + z-index: 1; +} + +.hero-section::before { + content: ''; + position: absolute; + inset: 0; + background: radial-gradient(circle at 50% 48%, rgba(7, 11, 20, 0.42) 0%, rgba(7, 11, 20, 0.82) 75%, var(--bg-primary) 100%); + pointer-events: none; + z-index: 0; +} + +/* 3D Voxel Earth Backdrop Canvas - Full Viewport Celestial Orbit */ +.hero-earth-backdrop { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + pointer-events: none; + z-index: 1; + display: flex; + align-items: center; + justify-content: center; + overflow: visible; +} + +/* Hero Earth Contrast Scrim - Targets the central reading zone */ +.hero-earth-scrim { + position: absolute; + inset: 0; + pointer-events: none; + z-index: 2; + background: + radial-gradient( + ellipse 820px 500px at 50% 44%, + rgba(7, 11, 20, 0.60) 0%, + rgba(7, 11, 20, 0.38) 45%, + rgba(7, 11, 20, 0.10) 75%, + transparent 100% + ); +} + +.digital-earth-canvas { + width: 100% !important; + height: 100% !important; + display: block; + opacity: 0; + visibility: hidden; + pointer-events: none; + filter: drop-shadow(0 0 50px rgba(56, 189, 248, 0.15)); + transition: opacity 0.8s ease-out; +} + +.digital-earth-canvas.loaded { + opacity: 0.94; + visibility: visible; +} + +.hero-container-content { + position: relative; + z-index: 3; +} + +.hero-content { + max-width: 940px; + margin: 0 auto; + position: relative; +} + +/* Hero Eyebrow Badge */ +.hero-eyebrow { + margin-bottom: 1.75rem; + display: flex; + justify-content: center; +} + +.hero-badge { + display: inline-flex; + align-items: center; + gap: 0.55rem; + padding: 0.4rem 1rem; + background: rgba(16, 185, 129, 0.08); + border: 1px solid rgba(52, 211, 153, 0.28); + border-radius: var(--radius-full); + font-family: var(--font-sans); + font-size: 0.835rem; + font-weight: 500; + color: var(--accent-primary-light); + box-shadow: 0 0 20px rgba(16, 185, 129, 0.16), 0 2px 10px rgba(0, 0, 0, 0.35); + transition: all 0.25s ease; + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); +} + +.hero-badge:hover { + background: rgba(16, 185, 129, 0.14); + border-color: rgba(52, 211, 153, 0.45); + box-shadow: var(--shadow-glow); + transform: translateY(-1px); +} + +.hero-badge-dot { + width: 7px; + height: 7px; + background-color: var(--accent-primary); + border-radius: 50%; + box-shadow: 0 0 8px var(--accent-primary); + animation: pulseDot 2.2s infinite ease-in-out; + flex-shrink: 0; +} + +.hero-badge-separator { + color: rgba(255, 255, 255, 0.25); + user-select: none; +} + +@keyframes pulseDot { + 0%, 100% { + transform: scale(1); + opacity: 0.9; + } + 50% { + transform: scale(1.35); + opacity: 0.5; + } +} + +.hero-title { + font-size: clamp(2.75rem, 5.5vw, 4.5rem); + line-height: 1.18; + letter-spacing: -0.03em; + margin-bottom: 2rem; + filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.95)) + drop-shadow(0 6px 20px rgba(6, 10, 19, 0.9)); +} + +.hero-subtitle { + font-size: clamp(1.2rem, 2.2vw, 1.4rem); + color: #E2E8F0; + font-weight: 450; + line-height: 1.75; + margin-bottom: 2.75rem; + max-width: 820px; + margin-left: auto; + margin-right: auto; + text-shadow: 0 1px 3px rgba(0, 0, 0, 0.95), 0 2px 12px rgba(6, 10, 19, 0.9); +} + +.hero-highlight-emerald { + color: #6EE7B7; + font-weight: 600; + text-shadow: 0 0 16px rgba(52, 211, 153, 0.45), 0 1px 3px rgba(0, 0, 0, 0.9); +} + +.hero-cta-group { + display: flex; + align-items: center; + justify-content: center; + gap: 1.25rem; + flex-wrap: wrap; + margin-bottom: 3.25rem; +} + +.hero-cta-group .btn-secondary { + background: rgba(15, 23, 42, 0.75); + border: 1px solid rgba(255, 255, 255, 0.16); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.08); + transition: all 0.25s ease; +} + +.hero-cta-group .btn-secondary:hover { + background: rgba(30, 41, 59, 0.9); + border-color: var(--accent-primary-light); + box-shadow: 0 6px 20px rgba(16, 185, 129, 0.25); + transform: translateY(-2px); +} + +/* Copy-Paste Install Widget */ +.install-widget { + background: rgba(11, 17, 32, 0.88); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: var(--radius-lg); + max-width: 680px; + margin: 0 auto; + overflow: hidden; + box-shadow: var(--shadow-md), 0 12px 35px rgba(0, 0, 0, 0.55); +} + +.install-tabs { + display: flex; + background: rgba(0, 0, 0, 0.4); + border-bottom: 1px solid var(--border-subtle); + padding: 0.45rem 0.65rem; + gap: 0.4rem; +} + +.install-tab-btn { + background: transparent; + border: none; + color: var(--text-muted); + font-family: var(--font-sans); + font-size: 0.85rem; + font-weight: 600; + padding: 0.45rem 1rem; + border-radius: var(--radius-sm); + cursor: pointer; + transition: all 0.2s ease; +} + +.install-tab-btn.active { + background: rgba(255, 255, 255, 0.1); + color: var(--text-primary); +} + +.install-code-box { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1.2rem 1.4rem; + font-family: var(--font-mono); + font-size: 0.95rem; + color: var(--accent-primary-light); + gap: 1.25rem; +} + +.install-code-text { + overflow-x: auto; + white-space: nowrap; +} + +.copy-btn { + background: rgba(255, 255, 255, 0.06); + border: 1px solid var(--border-subtle); + color: var(--text-secondary); + padding: 0.45rem 0.85rem; + border-radius: var(--radius-sm); + font-size: 0.825rem; + cursor: pointer; + display: flex; + align-items: center; + gap: 0.4rem; + transition: all 0.2s ease; + flex-shrink: 0; +} + +.copy-btn:hover { + background: rgba(16, 185, 129, 0.2); + color: var(--accent-primary-light); + border-color: var(--accent-primary-light); +} + +/* Micro-Proof Trust Chips */ +.hero-trust-chips { + margin-top: 2.25rem; + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; + gap: 0.85rem; +} + +.trust-chip { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.45rem 0.95rem; + background: rgba(255, 255, 255, 0.03); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-full); + font-size: 0.835rem; + color: var(--text-secondary); + font-weight: 500; + transition: all 0.2s ease; + backdrop-filter: blur(6px); + -webkit-backdrop-filter: blur(6px); +} + +.trust-chip svg { + color: var(--accent-primary-light); + flex-shrink: 0; +} + +.trust-chip:hover { + background: rgba(255, 255, 255, 0.06); + border-color: rgba(255, 255, 255, 0.18); + color: var(--text-primary); + transform: translateY(-1px); +} + +/* Interactive Split Terminal / Code Showcase */ +.hero-showcase { + margin-top: 4.5rem; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 2rem; + text-align: left; +} + +@media (max-width: 839px) { + .hero-showcase { + grid-template-columns: 1fr; + } +} + +.code-window { + background: var(--bg-secondary); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-lg); + overflow: hidden; + box-shadow: var(--shadow-lg); + display: flex; + flex-direction: column; + text-align: left; +} + +.window-header { + background: rgba(0, 0, 0, 0.35); + border-bottom: 1px solid var(--border-subtle); + padding: 0.75rem 1rem; + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 0.5rem; +} + +.window-dots { + display: flex; + gap: 6px; +} + +.window-dot { + width: 11px; + height: 11px; + border-radius: 50%; +} + +.dot-red { background: #EF4444; } +.dot-yellow { background: #F59E0B; } +.dot-green { background: #10B981; } + +.window-title { + font-size: 0.85rem; + color: var(--text-muted); + font-family: var(--font-mono); + font-weight: 500; + display: inline-flex; + align-items: center; + gap: 0.5rem; +} + +.window-title svg { + flex-shrink: 0; +} + +.window-file-tabs { + display: inline-flex; + align-items: center; + gap: 3px; + background: rgba(0, 0, 0, 0.4); + padding: 3px; + border-radius: var(--radius-sm); + border: 1px solid var(--border-subtle); +} + +.window-file-tab-btn { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 0.25rem 0.65rem; + border-radius: 4px; + border: 1px solid transparent; + background: transparent; + color: var(--text-muted); + font-family: var(--font-mono); + font-size: 0.78rem; + font-weight: 500; + line-height: 1.3; + cursor: pointer; + transition: all 0.15s ease; + white-space: nowrap; +} + +.window-file-tab-btn svg { + flex-shrink: 0; + opacity: 0.75; + transition: opacity 0.15s ease, color 0.15s ease; +} + +.window-file-tab-btn:hover { + color: var(--text-primary); + background: rgba(255, 255, 255, 0.06); +} + +.window-file-tab-btn:hover svg { + opacity: 1; +} + +.window-file-tab-btn.active { + background: rgba(255, 255, 255, 0.1); + border-color: rgba(255, 255, 255, 0.22); + color: #FFFFFF; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); +} + +.window-file-tab-btn.active svg { + opacity: 1; + color: var(--accent-primary-light); +} + +.window-file-tab-btn:focus-visible { + outline: 2px solid var(--accent-primary); + outline-offset: 1px; +} + +@media (max-width: 600px) { + .window-header { + flex-direction: column; + align-items: flex-start; + gap: 0.6rem; + } + + .window-file-tabs { + width: 100%; + justify-content: flex-start; + overflow-x: auto; + max-width: 100%; + scrollbar-width: none; + } + + .window-file-tabs::-webkit-scrollbar { + display: none; + } +} + +@media (max-width: 480px) { + .window-file-tab-btn { + padding: 0.2rem 0.45rem; + font-size: 0.72rem; + } +} + +.window-actions { + display: flex; + gap: 0.5rem; +} + +.window-body { + margin: 0; + padding: 1.25rem; + overflow-x: auto; + font-family: var(--font-mono); + font-size: 0.88rem; + line-height: 1.65; + flex-grow: 1; + white-space: pre !important; + display: block; + text-align: left; +} + +.window-body code { + font-family: inherit; + font-size: inherit; + white-space: inherit; + line-height: inherit; + display: block; + text-align: left; +} + +/* Syntax Highlighting */ +.kw-cmd { color: #38BDF8; font-weight: 600; } /* Keywords & Directives: FROM, RUN, COPY, SAVE, BUILD, WORKDIR, ENTRYPOINT, VERSION, package, import, func, return, group, target */ +.kw-target { color: #34D399; font-weight: 600; } /* Targets: +build, +docker, +deps, +test, deps:, build:, test:, docker:, all:, .PHONY: */ +.kw-comment { color: #94A3B8; font-style: italic; } /* Comments: # ..., // ... (WCAG AA compliant contrast) */ +.kw-string { color: #FCD34D; } /* Strings: "...", '...', paths, tags */ +.kw-flag { color: #C084FC; font-weight: 500; } /* Flags & Options: --push, AS LOCAL, --from=builder, --ci, -v, -o, --mount=... */ +.kw-prop { color: #93C5FD; font-weight: 500; } /* Properties / Keys: name:, on:, jobs:, steps:, uses:, with:, run:, env:, targets =, dockerfile =, etc. */ +.kw-type { color: #A78BFA; font-weight: 500; } /* Types: context.Context, *dagger.Directory, string, error, Pipeline */ +.kw-fn { color: #67E8F9; font-weight: 500; } /* Functions & Methods: Test, BuildAndPush, File, Export, Publish, Stdout, WithWorkdir, etc. */ +.kw-var { color: #FB923C; } /* Variables & parameters: ctx, src, m, bin, dag */ +.kw-val { color: #F472B6; font-weight: 500; } /* Literals & booleans: 0.8, true, false, CGO_ENABLED=0 */ +.kw-expr { color: #F472B6; } /* CI Template Expressions: ${{ ... }} */ + +.terminal-line { + margin-bottom: 0.35rem; + color: var(--text-secondary); +} + +.term-cmd { color: #38BDF8; font-weight: 600; } +.term-cached { color: #34D399; font-weight: 600; } +.term-running { color: #F59E0B; } +.term-success { color: #10B981; font-weight: 700; } + +/* Social Proof / Core Architecture Pillars */ +.social-proof-section { + padding: 3.5rem 0; + border-top: 1px solid var(--border-subtle); + border-bottom: 1px solid var(--border-subtle); + background: rgba(11, 17, 32, 0.65); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + position: relative; + z-index: 2; +} + +.proof-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 1.25rem; +} + +.proof-card { + background: rgba(255, 255, 255, 0.02); + border: 1px solid rgba(255, 255, 255, 0.06); + border-radius: var(--radius-lg); + padding: 1.35rem 1.25rem; + display: flex; + flex-direction: column; + transition: all 0.25s ease; + position: relative; +} + +.proof-card:hover { + background: rgba(255, 255, 255, 0.035); + border-color: rgba(52, 211, 153, 0.3); + transform: translateY(-2px); + box-shadow: 0 10px 24px -8px rgba(0, 0, 0, 0.5); +} + +.proof-header { + display: flex; + align-items: center; + justify-content: flex-start; + gap: 0.65rem; + margin-bottom: 0.5rem; +} + +.proof-title { + font-family: var(--font-sans); + font-size: 1.125rem; + font-weight: 700; + color: var(--text-primary); + letter-spacing: -0.01em; + margin: 0; +} + +.proof-pill { + font-family: var(--font-mono); + font-size: 0.725rem; + font-weight: 600; + color: var(--accent-primary-light); + background: rgba(16, 185, 129, 0.1); + border: 1px solid rgba(52, 211, 153, 0.25); + padding: 0.12rem 0.45rem; + border-radius: var(--radius-sm); + line-height: 1.3; +} + +.proof-tagline { + font-size: 0.9rem; + font-weight: 600; + color: var(--accent-primary-light); + margin-bottom: 0.5rem; + line-height: 1.35; +} + +.proof-desc { + font-size: 0.835rem; + color: #94A3B8; + line-height: 1.55; + margin: 0; +} + +.proof-desc code { + font-family: var(--font-mono); + font-size: 0.78rem; + background: rgba(0, 0, 0, 0.45); + border: 1px solid rgba(255, 255, 255, 0.1); + padding: 0.12rem 0.35rem; + border-radius: var(--radius-sm); + color: #BAE6FD; +} + +@media (max-width: 1024px) { + .proof-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (max-width: 600px) { + .proof-grid { + grid-template-columns: 1fr; + gap: 1rem; + } + .proof-card { + padding: 1.25rem 1.1rem; + } +} + +/* Sections Common */ +.section { + padding: 6rem 0; + position: relative; + z-index: 2; + background-color: var(--bg-primary); + background-image: + linear-gradient(to right, rgba(255, 255, 255, 0.02) 1px, transparent 1px), + linear-gradient(to bottom, rgba(255, 255, 255, 0.02) 1px, transparent 1px); + background-size: 40px 40px; +} + +#problem-solution { + position: relative; + z-index: 2; + background-color: transparent; + background-image: + linear-gradient(to right, rgba(255, 255, 255, 0.02) 1px, transparent 1px), + linear-gradient(to bottom, rgba(255, 255, 255, 0.02) 1px, transparent 1px); + background-size: 40px 40px; + overflow: visible; +} + +/* Problem-Solution Contrast Scrim - Optical text shield between Earth and foreground copy, just like in hero */ +.problem-solution-scrim { + position: absolute; + inset: 0; + pointer-events: none; + z-index: 1; + background: + radial-gradient( + ellipse 1000px 650px at 50% 32%, + rgba(7, 11, 20, 0.42) 0%, + rgba(7, 11, 20, 0.78) 60%, + var(--bg-primary) 100% + ); +} + +#problem-solution .container { + position: relative; + z-index: 2; +} + +.section-header { + text-align: center; + max-width: 780px; + margin: 0 auto 4rem; +} + +.section-tag { + color: var(--accent-primary-light); + font-size: 0.85rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.1em; + margin-bottom: 0.75rem; + display: block; +} + +.section-title { + margin-bottom: 1rem; +} + +.section-subtitle { + font-size: 1.15rem; + color: var(--text-secondary); +} + +/* Problem vs Solution Split */ +.problem-solution-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 2rem; +} + +@media (max-width: 839px) { + .problem-solution-grid { + grid-template-columns: 1fr; + } +} + +.comparison-panel { + border-radius: var(--radius-xl); + padding: 2.25rem; + position: relative; + overflow: hidden; + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); +} + +.panel-problem { + background: rgba(15, 23, 42, 0.72); + border: 1px solid rgba(244, 63, 94, 0.25); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.36); +} + +.panel-solution { + background: rgba(15, 23, 42, 0.72); + border: 1px solid rgba(52, 211, 153, 0.35); + box-shadow: 0 0 35px rgba(16, 185, 129, 0.12), 0 8px 32px rgba(0, 0, 0, 0.36); +} + +.panel-badge { + display: inline-flex; + align-items: center; + gap: 0.4rem; + padding: 0.3rem 0.75rem; + border-radius: var(--radius-full); + font-size: 0.8rem; + font-weight: 700; + margin: 0 0 1.5rem 0; + line-height: 1.4; + font-family: inherit; +} + +.badge-bad { + background: rgba(244, 63, 94, 0.15); + color: #FB7185; +} + +.badge-good { + background: rgba(16, 185, 129, 0.2); + color: var(--accent-primary-light); +} + +.comparison-list { + list-style: none; + display: flex; + flex-direction: column; + gap: 1.25rem; +} + +.comparison-item { + display: flex; + align-items: flex-start; + gap: 0.85rem; +} + +.item-icon { + width: 24px; + height: 24px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.85rem; + flex-shrink: 0; + margin-top: 0.2rem; +} + +.icon-bad { + background: rgba(244, 63, 94, 0.2); + color: #FB7185; +} + +.icon-good { + background: rgba(16, 185, 129, 0.2); + color: var(--accent-primary-light); +} + +.item-content h4 { + font-size: 1.05rem; + margin-bottom: 0.25rem; +} + +.item-content p { + font-size: 0.95rem; +} + +/* Features Bento Grid */ +.bento-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1.5rem; +} + +@media (max-width: 839px) { + .bento-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (max-width: 599px) { + .bento-grid { + grid-template-columns: 1fr; + } +} + +.bento-card { + background: var(--bg-card); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-lg); + padding: 2rem; + transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1); + display: flex; + flex-direction: column; + position: relative; + overflow: hidden; +} + +.bento-card:hover { + transform: translateY(-4px); + border-color: var(--border-hover); + box-shadow: var(--shadow-glow); + background: var(--bg-card-hover); +} + +.bento-card-featured { + grid-column: span 2; +} + +@media (max-width: 839px) { + .bento-card-featured { + grid-column: span 2; + } +} + +@media (max-width: 599px) { + .bento-card-featured { + grid-column: span 1; + } +} + +.bento-icon { + width: 48px; + height: 48px; + border-radius: var(--radius-md); + background: rgba(16, 185, 129, 0.1); + border: 1px solid rgba(52, 211, 153, 0.25); + display: flex; + align-items: center; + justify-content: center; + font-size: 1.4rem; + color: var(--accent-primary-light); + margin-bottom: 1.5rem; +} + +.bento-card h3 { + margin-bottom: 0.75rem; +} + +.bento-card p { + font-size: 0.95rem; + color: var(--text-secondary); + line-height: 1.55; +} + +.bento-card-snippet { + margin-top: 1.25rem; + background: rgba(0, 0, 0, 0.4); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-sm); + padding: 0.75rem 1rem; + font-family: var(--font-mono); + font-size: 0.8rem; + color: var(--accent-primary-light); + overflow-x: auto; + white-space: pre; +} + +/* Interactive Code Explorer */ +/* Sticky Language List (Above Window) */ +.race-lang-navbar { + display: flex; + justify-content: center; + align-items: center; + margin-bottom: 1.5rem; + position: sticky; + top: 1rem; + z-index: 50; + padding: 0.4rem 0; + pointer-events: none; +} + +.race-lang-tabs { + display: flex; + gap: 0.35rem; + align-items: center; + background: rgba(7, 11, 20, 0.95); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + padding: 0.42rem 0.65rem; + border-radius: 9999px; + border: 1px solid rgba(255, 255, 255, 0.15); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6), 0 0 20px rgba(0, 229, 255, 0.08); + overflow-x: auto; + scrollbar-width: none; + max-width: 100%; + pointer-events: auto; + transition: border-color var(--transition-fast), box-shadow var(--transition-fast); +} + +.race-lang-navbar.is-stuck .race-lang-tabs { + background: rgba(7, 11, 20, 0.97); + border-color: rgba(52, 211, 153, 0.3); + box-shadow: 0 16px 40px rgba(0, 0, 0, 0.8), 0 0 25px rgba(52, 211, 153, 0.12); +} + +.race-lang-tabs::-webkit-scrollbar { + display: none; +} + +.race-tab-btn { + background: transparent; + border: 1px solid transparent; + color: var(--text-muted); + font-family: var(--font-sans); + font-size: 0.92rem; + font-weight: 700; + padding: 0.42rem 0.9rem; + border-radius: 9999px; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 0.55rem; + transition: all 0.2s ease; + white-space: nowrap; +} + +.race-tab-btn svg { + width: 17px; + height: 17px; + flex-shrink: 0; +} + +.race-tab-btn:hover { + color: var(--text-primary); + background: rgba(255, 255, 255, 0.08); +} + +.race-tab-btn.active { + color: #34D399; + background: rgba(16, 185, 129, 0.16); + border-color: rgba(52, 211, 153, 0.45); + box-shadow: 0 0 16px rgba(16, 185, 129, 0.25); +} + +/* Wide Layout for Interactive Code & Race Demonstration */ +#code-explorer .container { + max-width: 1140px; +} + +@media (min-width: 1440px) { + #code-explorer .container, + .earthfile-window, + .cr-feature-cards-deck { + max-width: 1360px; + } +} + +@media (min-width: 1700px) { + #code-explorer .container, + .earthfile-window, + .cr-feature-cards-deck { + max-width: 1480px; + } +} + +/* 1. Earthfile Window */ +.earthfile-window { + background: #070B14; + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: var(--radius-xl); + box-shadow: 0 16px 40px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.06); + display: flex; + flex-direction: column; + margin-left: auto; + margin-right: auto; + margin-bottom: 1.75rem; + max-width: 1140px; + width: 100%; + overflow: hidden; +} + +/* Window Header with "Earthfile" Title */ +.race-window-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.75rem 1.25rem; + background: rgba(13, 19, 34, 0.95); + border-bottom: 1px solid rgba(255, 255, 255, 0.08); + border-top-left-radius: var(--radius-xl); + border-top-right-radius: var(--radius-xl); +} + +.race-window-header-left { + display: flex; + align-items: center; + gap: 0.9rem; +} + +.race-window-title { + font-family: var(--font-mono); + font-size: 0.84rem; + font-weight: 700; + color: #E2E8F0; + letter-spacing: 0.02em; + display: flex; + align-items: center; + gap: 0.45rem; +} + +.race-window-header-right { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.race-filename-link { + font-family: var(--font-mono); + font-size: 0.76rem; + color: var(--text-muted); + display: flex; + align-items: center; + gap: 0.4rem; +} + +.race-filename-link a { + color: var(--accent-primary-light); + text-decoration: underline; + font-size: 0.74rem; + transition: color 0.15s ease; +} + +.race-filename-link a:hover { + color: #fff; +} + +.earthfile-window-body { + padding: 1.35rem 1.6rem; + background: rgba(5, 8, 17, 0.9); +} + +.race-code-pane { + margin: 0; + padding: 0; + background: transparent; + font-family: var(--font-mono); + font-size: 0.86rem; + line-height: 1.6; + color: #E2E8F0; + overflow-x: auto; + white-space: pre; +} + +.race-code-pane code { + font-family: inherit; + font-size: inherit; + white-space: inherit; + line-height: inherit; + display: block; +} + +/* ========================================================================== + Horizontal 3-Window Execution Race (CodeRabbit-Style Accordion) + -------------------------------------------------------------------------- + Layout Mechanics: + - Active card: flex: 3.6 1 0 (min-width: 500px, expands to full terminal) + - Inactive cards: flex: 1 1 0 (min-width: 155px, peeking mode status) + - Smooth cubic-bezier(0.16, 1, 0.3, 1) flex-grow transitions + - Responsive breakpoint (< 860px): converts to vertical accordion stack + ========================================================================== */ +.cr-feature-cards-deck { + position: relative; + width: 100%; + max-width: 1140px; + height: 490px; + min-height: 490px; + margin-top: 1.5rem; + margin-left: auto; + margin-right: auto; + display: flex; + flex-direction: row; + background: #070B14; + border: 1px solid rgba(255, 255, 255, 0.09); + border-radius: var(--radius-xl); + box-shadow: 0 20px 50px rgba(0, 0, 0, 0.75), inset 0 1px 0 rgba(255, 255, 255, 0.06); + overflow: hidden; + box-sizing: border-box; +} + +/* Individual Feature Card / Window Column */ +.cr-feature-card { + display: flex; + flex-direction: column; + position: relative; + border-left: 1px solid rgba(255, 255, 255, 0.08); + height: 100%; + overflow: hidden; + cursor: pointer; + user-select: none; + flex: 1 1 0%; + min-width: 0; + will-change: flex-grow; + transform: translateZ(0); + transition: flex-grow 0.42s cubic-bezier(0.16, 1, 0.3, 1), + background 0.3s ease; +} + +.cr-feature-card:focus { + outline: none; +} + +.cr-feature-card:focus-visible { + outline: 2px solid var(--accent-primary); + outline-offset: -2px; +} + +.cr-feature-card:first-child { + border-left: none; +} + +/* Inactive Card (Peeking Side Column) */ +.cr-feature-card[data-active="false"] { + flex-grow: 1; + background: rgba(8, 12, 22, 0.72); +} + +.cr-feature-card[data-active="false"]:hover { + background: rgba(14, 21, 38, 0.88); +} + +/* Active Card (Expanded Focus Column) */ +.cr-feature-card[data-active="true"] { + flex-grow: 3.6; + background: rgba(10, 15, 28, 0.98); + cursor: default; +} + +/* Tab Header of each Card */ +.cr-card-tab-header { + padding: 0.85rem 1.25rem; + display: flex; + align-items: center; + justify-content: space-between; + position: relative; + border-bottom: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(10, 15, 28, 0.85); + min-height: 52px; + box-sizing: border-box; + transition: background 0.25s ease; + overflow: hidden; +} + +.cr-feature-card[data-active="true"] .cr-card-tab-header { + background: rgba(14, 21, 38, 0.95); + border-bottom-color: rgba(255, 255, 255, 0.1); +} + +.cr-card-label { + display: flex; + align-items: center; + gap: 0.55rem; + font-family: var(--font-sans); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.cr-card-number { + font-family: var(--font-mono); + font-size: 0.78rem; + font-weight: 600; + color: var(--text-muted); + letter-spacing: 0.05em; + transition: color 0.2s ease; +} + +.cr-feature-card[data-active="true"].cold-card .cr-card-number { color: #F87171; } +.cr-feature-card[data-active="true"].edit-card .cr-card-number { color: #38BDF8; } +.cr-feature-card[data-active="true"].cached-card .cr-card-number { color: var(--accent-primary-light); } + +.cr-card-lead { + font-size: 0.88rem; + font-weight: 700; + color: var(--text-secondary); + letter-spacing: -0.01em; + transition: color 0.2s ease; + white-space: nowrap; + flex-shrink: 0; +} + +.cr-feature-card[data-active="true"] .cr-card-lead { + color: #FFFFFF; +} + +.cr-card-remainder { + font-size: 0.76rem; + color: var(--text-muted); + font-weight: 400; + transition: opacity 0.2s ease; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.cr-feature-card[data-active="false"] .cr-card-remainder { + display: none; +} + +@media (min-width: 1200px) { + .cr-feature-card[data-active="false"] .cr-card-remainder { + display: inline; + color: var(--text-muted); + } +} + +/* Time Pills in Header */ +.cr-card-time-pill { + font-family: var(--font-mono); + font-size: 0.74rem; + font-weight: 600; + padding: 0.15rem 0.5rem; + border-radius: var(--radius-sm); + border: 1px solid transparent; + white-space: nowrap; + flex-shrink: 0; +} + +.cold-pill { + color: #F87171; + background: rgba(248, 113, 113, 0.1); + border-color: rgba(248, 113, 113, 0.2); +} + +.edit-pill { + color: #38BDF8; + background: rgba(56, 189, 248, 0.1); + border-color: rgba(56, 189, 248, 0.2); +} + +.cached-pill { + color: #34D399; + background: rgba(52, 211, 153, 0.12); + border-color: rgba(52, 211, 153, 0.25); +} + +/* Bottom Progress / Active Indicator Underline */ +.cr-card-progress-track { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 2px; + background: transparent; +} + +.cr-card-progress-fill { + display: block; + height: 100%; + width: 100%; + opacity: 0; + transform: scaleX(0.7); + transition: opacity 0.3s ease, transform 0.3s ease; +} + +.cr-feature-card[data-active="true"] .cr-card-progress-fill { + opacity: 1; + transform: scaleX(1); +} + +.cold-card[data-active="true"] .cr-card-progress-fill { + background: #EF4444; + box-shadow: 0 0 12px rgba(239, 68, 68, 0.85); +} + +.edit-card[data-active="true"] .cr-card-progress-fill { + background: #38BDF8; + box-shadow: 0 0 12px rgba(56, 189, 248, 0.85); +} + +.cached-card[data-active="true"] .cr-card-progress-fill { + background: #34D399; + box-shadow: 0 0 12px rgba(52, 211, 153, 0.85); +} + +/* Window Content (Header + Terminal Screen) */ +.cr-card-window-content { + display: flex; + flex-direction: column; + flex: 1; + overflow: hidden; + position: relative; + transition: opacity 0.3s ease, filter 0.3s ease; +} + +.cr-feature-card[data-active="false"] .cr-card-window-content { + opacity: 0.55; + filter: saturate(0.75); + pointer-events: none; +} + +.cr-feature-card[data-active="true"] .cr-card-window-content { + opacity: 1; + visibility: visible; + filter: none; + pointer-events: auto; +} + +/* Inner Window Header Bar (macOS Dots + Command) */ +.cr-window-header { + display: flex; + align-items: center; + justify-content: flex-start; + gap: 0.75rem; + padding: 0.65rem 1.15rem; + background: rgba(8, 12, 22, 0.7); + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + white-space: nowrap; + overflow: hidden; +} + +.cr-window-header .window-dots { + display: flex; + align-items: center; + gap: 6px; + min-width: 46px; + flex-shrink: 0; +} + +.cr-window-header .window-dots .window-dot { + width: 9px; + height: 9px; + border-radius: 50%; + display: block; +} + +.cr-window-header .window-dots .dot-red { background: #EF4444 !important; box-shadow: 0 0 4px rgba(239, 68, 68, 0.4); } +.cr-window-header .window-dots .dot-yellow { background: #F59E0B !important; box-shadow: 0 0 4px rgba(245, 158, 11, 0.4); } +.cr-window-header .window-dots .dot-green { background: #10B981 !important; box-shadow: 0 0 4px rgba(16, 185, 129, 0.4); } + +.cr-window-title { + display: flex; + align-items: center; + gap: 0.45rem; + font-family: var(--font-mono); + font-size: 0.8rem; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.cr-window-title .mode-icon { + font-size: 0.95rem; + flex-shrink: 0; +} + +.cr-window-title .mode-name { + font-weight: 600; + color: var(--text-primary); + white-space: nowrap; + flex-shrink: 0; +} + +.cr-window-title .mode-cmd { + color: var(--text-muted); + font-size: 0.74rem; + margin-left: 0.25rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* Terminal Screen Output */ +.cr-card-window-content .race-screen { + flex: 1; + padding: 1.1rem 1.25rem; + overflow-y: auto; + overflow-x: hidden; + background: #040711; + font-family: var(--font-mono); + font-size: 0.82rem; + line-height: 1.6; + color: #E2E8F0; + box-sizing: border-box; +} + +.cr-feature-card[data-active="false"] .race-screen { + overflow: hidden; +} + +/* Pulse animation on finish */ +.cold-card.cold-pulse { + box-shadow: inset 0 0 25px rgba(239, 68, 68, 0.18); +} + +.edit-card.edit-pulse { + box-shadow: inset 0 0 25px rgba(56, 189, 248, 0.18); +} + +.cached-card.winner-pulse { + box-shadow: inset 0 0 25px rgba(52, 211, 153, 0.18); +} + +/* Responsive Viewports - M3 Window Size Classes (Medium & Compact < 840px) */ +@media (max-width: 839px) { + .cr-feature-cards-deck { + flex-direction: column; + height: auto; + min-height: unset; + } + + .cr-feature-card { + border-left: none; + border-top: 1px solid rgba(255, 255, 255, 0.08); + } + + .cr-feature-card:first-child { + border-top: none; + } + + .cr-feature-card[data-active="false"] { + flex: none; + min-width: 100%; + height: 52px; + } + + .cr-feature-card[data-active="false"] .cr-card-window-content { + display: none; + } + + .cr-feature-card[data-active="true"] { + flex: none; + min-width: 100%; + height: 420px; + } + + .cr-feature-card[data-active="true"] .cr-card-window-content { + display: flex; + } +} + +@media (max-width: 599px) { + .cr-feature-card { + height: 280px; + min-height: 280px; + } +} + +.race-status-badge { + display: inline-flex; + align-items: center; + gap: 5px; + font-size: 0.65rem; + font-weight: 700; + font-family: var(--font-mono); + padding: 2px 8px; + border-radius: 9999px; + letter-spacing: 0.04em; + transition: all var(--transition-fast); +} + +.race-status-badge .status-indicator { + width: 6px; + height: 6px; + border-radius: 50%; +} + +.status-cold { + background: rgba(248, 113, 113, 0.12); + color: #F87171; + border: 1px solid rgba(248, 113, 113, 0.25); +} +.status-cold .status-indicator { + background: #F87171; +} + +.status-edit { + background: rgba(56, 189, 248, 0.15); + color: #38BDF8; + border: 1px solid rgba(56, 189, 248, 0.3); +} +.status-edit .status-indicator { + background: #38BDF8; + box-shadow: 0 0 6px #38BDF8; +} + +.status-cached { + background: rgba(16, 185, 129, 0.15); + color: #34D399; + border: 1px solid rgba(52, 211, 153, 0.3); +} +.status-cached .status-indicator { + background: #10B981; + box-shadow: 0 0 6px #10B981; +} + +.race-status-badge.status-running { + background: rgba(245, 158, 11, 0.15); + color: #FBBF24; + border: 1px solid rgba(245, 158, 11, 0.3); +} +.race-status-badge.status-running .status-indicator { + background: #FBBF24; + box-shadow: 0 0 6px #FBBF24; + animation: pulse 1s infinite; +} + +.race-screen { + flex: 1 1 auto; + min-height: 0; + max-height: none; + overflow-y: auto; + padding: 1.25rem 1.4rem; + font-family: var(--font-mono); + font-size: 0.74rem; + line-height: 1.5; + letter-spacing: -0.015em; + color: #CBD5E1; + position: relative; + scrollbar-width: thin; + scrollbar-color: rgba(255, 255, 255, 0.12) transparent; +} + +.race-screen::-webkit-scrollbar { + width: 4px; +} +.race-screen::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.12); + border-radius: 2px; +} + +.race-lines { + display: flex; + flex-direction: column; + gap: 0; +} + +.player-terminal-line { + display: flex; + align-items: center; + height: 22px; + min-height: 22px; + max-height: 22px; + line-height: 22px; + box-sizing: border-box; + font-family: var(--font-mono); + font-size: 0.78rem; + color: #CBD5E1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + padding: 0 4px; + margin: 0; +} + +.race-cursor { + display: inline-flex; + align-items: center; + height: 22px; + min-height: 22px; + max-height: 22px; + line-height: 22px; + box-sizing: border-box; + color: #34D399; + padding-left: 4px; + animation: termBlink 1s infinite; + font-weight: bold; +} + +/* Parallel Comparable Benchmark Bars */ +.race-summary-bar { + max-width: 1080px; + margin-top: 2rem; + margin-left: auto; + margin-right: auto; + padding: 0; +} + +.race-parallel-bars { + display: flex; + flex-direction: column; + gap: 1.1rem; + margin-bottom: 1.75rem; + padding-bottom: 1.5rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.08); +} + +.parallel-row { + display: grid; + grid-template-columns: 155px 1fr; + align-items: center; + gap: 1.5rem; +} + +@media (max-width: 599px) { + .parallel-row { + grid-template-columns: 1fr; + gap: 0.45rem; + } +} + +.parallel-label { + display: flex; + align-items: center; + gap: 0.6rem; + font-size: 0.95rem; + font-weight: 700; + color: var(--text-primary); + letter-spacing: -0.01em; +} + +.parallel-icon { + font-size: 1.1rem; + line-height: 1; +} + +.parallel-name { + font-size: 0.95rem; + font-weight: 700; + color: #F1F5F9; +} + +.parallel-bar-area { + display: flex; + align-items: center; + gap: 1.25rem; + flex-grow: 1; +} + +.parallel-track { + flex-grow: 1; + height: 14px; + background: rgba(255, 255, 255, 0.08); + box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.4); + border-radius: 9999px; + overflow: hidden; + position: relative; +} + +.parallel-fill { + height: 100%; + border-radius: 9999px; + transition: width 0.05s linear; +} + +.parallel-fill.cold-fill { + background: linear-gradient(90deg, #475569, #94A3B8); +} + +.parallel-fill.edit-fill { + background: linear-gradient(90deg, #0284C7, #38BDF8); + box-shadow: 0 0 14px rgba(56, 189, 248, 0.5); +} + +.parallel-fill.cached-fill { + background: linear-gradient(90deg, #059669, #10B981, #34D399); + box-shadow: 0 0 16px rgba(16, 185, 129, 0.6); +} + +.parallel-metric-val { + font-family: var(--font-mono); + font-size: 1.05rem; + font-weight: 800; + min-width: 195px; + display: flex; + align-items: center; + gap: 0.65rem; +} + +.parallel-metric-val.cold-val { + color: #CBD5E1; +} + +.parallel-metric-val.edit-val { + color: #38BDF8; +} + +.parallel-metric-val.cached-val { + color: #10B981; +} + +.speedup-tag, +.parallel-speedup-badge { + font-size: 0.8rem; + font-weight: 800; + padding: 0.2rem 0.65rem; + border-radius: 9999px; + background: rgba(16, 185, 129, 0.16); + border: 1px solid rgba(16, 185, 129, 0.45); + color: #34D399; + letter-spacing: -0.01em; + white-space: nowrap; + box-shadow: 0 0 12px rgba(16, 185, 129, 0.2); +} + +.edit-val .speedup-tag, +.parallel-speedup-badge.badge-edit { + background: rgba(56, 189, 248, 0.16); + border: 1px solid rgba(56, 189, 248, 0.45); + color: #38BDF8; + box-shadow: 0 0 12px rgba(56, 189, 248, 0.2); +} + +.race-roi-metrics, +.summary-metric-group { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 2rem; + text-align: center; + margin-top: 1.5rem; +} + +@media (max-width: 599px) { + .race-roi-metrics, + .summary-metric-group { + grid-template-columns: 1fr; + gap: 1.25rem; + } +} + +.summary-metric { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.45rem; +} + +.summary-label { + font-size: 0.82rem; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--text-secondary); + font-weight: 700; + display: inline-flex; + align-items: center; + gap: 0.35rem; +} + +.summary-val { + font-family: var(--font-mono); + font-size: 1.55rem; + font-weight: 800; + letter-spacing: -0.02em; + line-height: 1.2; +} + +.saved-val { + color: #10B981; + text-shadow: 0 0 25px rgba(16, 185, 129, 0.35); +} + +.co2-val { + color: #34D399; + text-shadow: 0 0 25px rgba(52, 211, 153, 0.35); +} + +/* Metric Tooltip for Formula Methodology */ +.metric-tooltip-wrapper { + position: relative; + display: inline-flex; + align-items: center; + margin-left: 5px; + vertical-align: middle; +} + +.tooltip-trigger { + background: transparent; + border: none; + padding: 0; + margin: 0; + cursor: pointer; + color: var(--text-muted); + display: inline-flex; + align-items: center; + justify-content: center; + line-height: 1; + transition: color var(--transition-fast), transform var(--transition-fast); +} + +.tooltip-trigger:hover, +.tooltip-trigger:focus-visible, +.metric-tooltip-wrapper:hover .tooltip-trigger { + color: #34D399; + transform: scale(1.15); +} + +.metric-tooltip-box { + visibility: hidden; + opacity: 0; + position: absolute; + bottom: calc(100% + 10px); + right: -10px; + width: 290px; + background: #0A101D; + border: 1px solid rgba(52, 211, 153, 0.3); + backdrop-filter: blur(20px); + box-shadow: 0 12px 36px rgba(0, 0, 0, 0.7), 0 0 20px rgba(16, 185, 129, 0.15); + border-radius: var(--radius-md); + padding: 0.85rem 1rem; + z-index: 999; + pointer-events: none; + transform: translateY(6px); + transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s ease; + display: flex; + flex-direction: column; + gap: 0.45rem; + font-size: 0.74rem; + line-height: 1.45; + color: #CBD5E1; + text-align: left; + text-transform: none; + letter-spacing: normal; +} + +@media (max-width: 599px) { + .metric-tooltip-box { + right: -50px; + width: 260px; + } +} + +.metric-tooltip-box::after { + content: ''; + position: absolute; + top: 100%; + right: 14px; + border-width: 6px; + border-style: solid; + border-color: #0A101D transparent transparent transparent; +} + +.metric-tooltip-wrapper:hover .metric-tooltip-box, +.metric-tooltip-wrapper:focus-within .metric-tooltip-box { + visibility: visible; + opacity: 1; + pointer-events: auto; + transform: translateY(0); +} + +.tooltip-title { + font-weight: 700; + color: #34D399; + font-size: 0.78rem; + display: flex; + align-items: center; + gap: 5px; +} + +.tooltip-desc { + color: #94A3B8; + font-weight: 400; +} + +.tooltip-desc strong { + color: #F1F5F9; + font-weight: 600; +} + +.tooltip-link { + display: inline-flex; + align-items: center; + gap: 4px; + color: #38BDF8; + text-decoration: none; + font-weight: 600; + font-size: 0.72rem; + margin-top: 3px; + padding-top: 4px; + border-top: 1px solid rgba(255, 255, 255, 0.08); + transition: color var(--transition-fast); +} + +.tooltip-link:hover { + color: #7DD3FC; + text-decoration: underline; +} + +/* Terminal ANSI / Highlighting Tokens */ +.player-dots { + display: flex; + gap: 6px; + align-items: center; +} + +.dot { + width: 10px; + height: 10px; + border-radius: 50%; + display: inline-block; +} + +.dot-red { background: #FF5F56; box-shadow: 0 0 4px rgba(255, 95, 86, 0.5); } +.dot-yellow { background: #FFBD2E; box-shadow: 0 0 4px rgba(255, 189, 46, 0.5); } +.dot-green { background: #27C93F; box-shadow: 0 0 4px rgba(39, 201, 63, 0.5); } + +.term-cmd { color: #38BDF8; font-weight: 700; } +.term-cached { color: #34D399; } +.term-running { color: #FBBF24; } +.term-success { + color: #34D399; + font-weight: 700; + background: rgba(16, 185, 129, 0.08); + border: 1px solid rgba(16, 185, 129, 0.25); + border-radius: 3px; + line-height: 20px; +} +.term-bold-cyan { color: #38BDF8; font-weight: 700; } +.term-bold-green { color: #34D399; font-weight: 700; } +.term-bold-yellow { color: #FBBF24; font-weight: 700; } +.term-bold-red { color: #F87171; font-weight: 700; } +.term-bold { font-weight: 700; color: #FFFFFF; } +.term-cyan { color: #38BDF8; } +.term-green { color: #10B981; } +.term-yellow { color: #F59E0B; } +.term-red { color: #EF4444; } +.term-dim { color: #94A3B8; } + +@keyframes termBlink { + 0%, 49% { opacity: 1; } + 50%, 100% { opacity: 0; } +} + +/* Comparison Matrix */ +.matrix-container { + background: var(--bg-card); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-xl); + overflow: hidden; + box-shadow: var(--shadow-lg); +} + +.matrix-filters { + display: flex; + background: rgba(0, 0, 0, 0.35); + padding: 0.75rem 1.25rem; + border-bottom: 1px solid var(--border-subtle); + gap: 0.5rem; + overflow-x: auto; +} + +.matrix-filter-btn { + background: transparent; + border: 1px solid transparent; + color: var(--text-muted); + font-size: 0.85rem; + font-weight: 600; + padding: 0.35rem 0.85rem; + border-radius: var(--radius-full); + cursor: pointer; + transition: all 0.2s ease; +} + +.matrix-filter-btn.active { + background: rgba(16, 185, 129, 0.12); + border-color: rgba(52, 211, 153, 0.3); + color: var(--accent-primary-light); +} + +.table-responsive { + overflow-x: auto; + width: 100%; +} + +.comparison-table { + width: 100%; + border-collapse: collapse; + text-align: left; + font-size: 0.92rem; +} + +.comparison-table th, .comparison-table td { + padding: 1.15rem 1.25rem; + border-bottom: 1px solid var(--border-subtle); +} + +.comparison-table th { + background: rgba(0, 0, 0, 0.4); + color: var(--text-secondary); + font-weight: 600; + font-size: 0.85rem; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.comparison-table th.col-earthbuild, .comparison-table td.col-earthbuild { + background: rgba(16, 185, 129, 0.06); + border-left: 1px solid rgba(52, 211, 153, 0.25); + border-right: 1px solid rgba(52, 211, 153, 0.25); + font-weight: 600; + color: #FFFFFF; +} + +.comparison-table th.col-earthbuild { + color: var(--accent-primary-light); + font-weight: 800; + font-size: 1rem; +} + +.status-yes { + color: var(--accent-primary-light); + font-weight: 700; + display: inline-flex; + align-items: center; + gap: 0.35rem; +} + +.status-no { + color: #64748B; + display: inline-flex; + align-items: center; + gap: 0.35rem; +} + +.status-partial { + color: #F59E0B; + display: inline-flex; + align-items: center; + gap: 0.35rem; +} + +/* ROI Calculator */ +.roi-card { + background: linear-gradient(145deg, rgba(15, 23, 42, 0.8) 0%, rgba(8, 12, 20, 0.95) 100%); + border: 1px solid rgba(52, 211, 153, 0.3); + border-radius: var(--radius-xl); + padding: 3rem; + box-shadow: var(--shadow-glow); + display: grid; + grid-template-columns: 1fr 1fr; + gap: 3rem; + align-items: center; +} + +@media (max-width: 839px) { + .roi-card { + grid-template-columns: 1fr; + padding: 2rem; + gap: 2rem; + } +} + +.slider-group { + margin-bottom: 2rem; +} + +.slider-label { + display: flex; + justify-content: space-between; + font-weight: 600; + margin-bottom: 0.75rem; + font-size: 0.95rem; +} + +.slider-val { + color: var(--accent-primary-light); + font-family: var(--font-mono); + font-weight: 700; +} + +input[type="range"] { + width: 100%; + accent-color: var(--accent-primary); + height: 6px; + background: rgba(255, 255, 255, 0.1); + border-radius: var(--radius-full); + outline: none; + cursor: pointer; +} + +.roi-results-box { + background: rgba(16, 185, 129, 0.06); + border: 1px solid rgba(52, 211, 153, 0.25); + border-radius: var(--radius-lg); + padding: 2rem 2.25rem; + text-align: center; + box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.05); +} + +.roi-metric { + margin-bottom: 1.25rem; +} + +.roi-number { + font-size: 2.35rem; + font-weight: 800; + color: #34D399; + font-family: var(--font-mono); + line-height: 1.15; + text-shadow: 0 0 20px rgba(52, 211, 153, 0.25); +} + +.roi-desc { + color: var(--text-secondary); + font-size: 0.88rem; + font-weight: 600; + margin-top: 0.25rem; +} + +.roi-subtext { + font-size: 0.78rem; + font-family: var(--font-mono); + color: #34D399; + margin-top: 0.35rem; + display: inline-block; + background: rgba(16, 185, 129, 0.12); + border: 1px solid rgba(52, 211, 153, 0.3); + border-radius: 9999px; + padding: 0.2rem 0.7rem; +} + +.roi-subtext-cyan { + color: #38BDF8; + background: rgba(56, 189, 248, 0.12); + border-color: rgba(56, 189, 248, 0.3); +} + +.roi-subtext-amber { + color: #FBBF24; + background: rgba(245, 158, 11, 0.12); + border-color: rgba(251, 191, 36, 0.3); +} + +.roi-actions { + margin-top: 1.5rem; + padding-top: 1.25rem; + border-top: 1px solid rgba(255, 255, 255, 0.1); + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.roi-action-btn { + width: 100%; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + padding: 0.75rem 1.25rem; + font-weight: 600; + font-size: 0.95rem; + border-radius: var(--radius-md); + text-decoration: none; + cursor: pointer; + transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1); +} + +.roi-copy-share-btn { + border: 1px solid rgba(52, 211, 153, 0.3); + color: var(--text-primary); + background: rgba(255, 255, 255, 0.04); + font-family: inherit; +} + +.roi-copy-share-btn:hover { + background: rgba(16, 185, 129, 0.12); + border-color: var(--accent-primary); + color: #FFFFFF; + transform: translateY(-1px); +} + +.roi-copy-share-btn.copied { + background: rgba(16, 185, 129, 0.2); + border-color: var(--accent-primary); + color: var(--accent-primary-light); +} + +.roi-btn-link-icon, +.roi-btn-check-icon { + flex-shrink: 0; + transition: transform 0.2s ease; +} + +.roi-btn-check-icon { + color: var(--accent-primary-light); +} + +/* Migration Guide */ +#migration .container { + max-width: 1440px; +} + +@media (min-width: 1700px) { + #migration .container { + max-width: 1560px; + } +} + +.migration-container { + background: var(--bg-card); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-xl); + padding: 2.5rem; +} + +.migration-tabs { + display: flex; + gap: 0.75rem; + margin-bottom: 2rem; + flex-wrap: wrap; +} + +.migration-tab-btn { + padding: 0.6rem 1.25rem; + border-radius: var(--radius-md); + border: 1px solid var(--border-subtle); + background: var(--bg-secondary); + color: var(--text-secondary); + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + display: inline-flex; + align-items: center; + gap: 0.6rem; +} + +.migration-tab-btn svg { + flex-shrink: 0; + transition: transform 0.2s ease; +} + +.migration-tab-btn:hover svg { + transform: scale(1.1); +} + +.migration-tab-btn.active { + background: rgba(16, 185, 129, 0.15); + border-color: var(--accent-primary-light); + color: #FFFFFF; +} + +.migration-diff-view { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1.5rem; +} + +@media (max-width: 839px) { + .migration-diff-view { + grid-template-columns: 1fr; + } +} + +/* Testimonials */ +.testimonials-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); + gap: 1.5rem; +} + +.testimonial-card { + background: var(--bg-card); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-lg); + padding: 2rem; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.testimonial-quote { + font-size: 1rem; + color: var(--text-secondary); + line-height: 1.6; + margin-bottom: 1.5rem; + font-style: italic; +} + +.testimonial-author { + display: flex; + align-items: center; + gap: 0.85rem; +} + +.author-avatar { + width: 44px; + height: 44px; + border-radius: 50%; + background: linear-gradient(135deg, var(--accent-primary) 0%, var(--accent-secondary) 100%); + display: flex; + align-items: center; + justify-content: center; + font-weight: 700; + color: #FFFFFF; + font-size: 1rem; +} + +.author-info h4, +.author-info .author-name { + font-size: 0.95rem; + margin-bottom: 0.15rem; + color: var(--text-primary); + font-weight: 600; +} + +.author-info span { + font-size: 0.8rem; + color: var(--text-muted); +} + +/* Quickstart CTA Section */ +.cta-banner { + background: radial-gradient(circle at 50% 50%, rgba(16, 185, 129, 0.15) 0%, rgba(8, 12, 20, 0.8) 100%), var(--bg-secondary); + border: 1px solid rgba(52, 211, 153, 0.35); + border-radius: var(--radius-xl); + padding: 4.5rem 2rem; + text-align: center; + box-shadow: var(--shadow-glow); + position: relative; + overflow: hidden; +} + +.cta-banner h2 { + font-size: clamp(2rem, 4vw, 3rem); + margin-bottom: 1.25rem; +} + +.cta-banner p { + max-width: 620px; + margin: 0 auto 2.5rem; + font-size: 1.15rem; +} + +/* Footer */ +.footer { + background: #05080E; + border-top: 1px solid var(--border-subtle); + padding: 5rem 0 2.5rem; + margin-top: auto; + font-size: 0.9rem; + position: relative; + z-index: 2; +} + +.footer-grid { + display: grid; + grid-template-columns: 2fr 1fr 1fr 1fr; + gap: 3rem; + margin-bottom: 4rem; +} + +@media (max-width: 839px) { + .footer-grid { + grid-template-columns: 1fr 1fr; + } +} + +@media (max-width: 599px) { + .footer-grid { + grid-template-columns: 1fr; + } +} + +.footer-brand p { + font-size: 0.9rem; + margin-top: 0.75rem; + max-width: 320px; +} + +.footer-col h4, +.footer-col .footer-col-title { + font-size: 0.95rem; + margin-bottom: 1.25rem; + color: var(--text-primary); + text-transform: uppercase; + letter-spacing: 0.05em; + font-weight: 700; +} + +.footer-links { + list-style: none; + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.footer-links a { + color: var(--text-muted); +} + +.footer-links a:hover { + color: var(--accent-primary-light); +} + +.footer-bottom { + display: flex; + align-items: center; + justify-content: space-between; + padding-top: 2rem; + border-top: 1px solid var(--border-subtle); + color: var(--text-muted); + font-size: 0.85rem; +} + +.footer-bottom a { + color: var(--accent-primary-light); + text-decoration: underline; + text-underline-offset: 3px; +} + +@media (max-width: 599px) { + .footer-bottom { + flex-direction: column; + gap: 1rem; + text-align: center; + } +} + +/* Documentation & Sub-pages Styling */ +.doc-page { + padding: 4rem 0 6rem; +} + +.doc-header { + margin-bottom: 3.5rem; + text-align: center; +} + +.doc-content { + background: var(--bg-card); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-xl); + padding: 3rem; + box-shadow: var(--shadow-md); +} + +@media (max-width: 839px) { + .doc-content { + padding: 1.5rem; + } +} + +.doc-content h2 { + margin-top: 2.5rem; + margin-bottom: 1rem; + padding-bottom: 0.5rem; + border-bottom: 1px solid var(--border-subtle); +} + +.doc-content h3 { + margin-top: 1.75rem; + margin-bottom: 0.75rem; +} + +.doc-content ul, .doc-content ol { + margin-left: 1.5rem; + margin-bottom: 1.25rem; + color: var(--text-secondary); +} + +.doc-content li { + margin-bottom: 0.4rem; +} + +.doc-content .highlight { + background: var(--bg-secondary); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-md); + margin: 1.5rem 0; + overflow-x: auto; +} + +.doc-content pre { + background: var(--bg-secondary); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-md); + padding: 1.25rem; + margin: 1.5rem 0; + overflow-x: auto; + font-size: 0.9rem; + line-height: 1.6; + white-space: pre; +} + +.doc-content .highlight pre { + background: transparent; + border: none; + margin: 0; + padding: 1.25rem; +} + +.doc-content pre code { + background: transparent; + border: none; + padding: 0; + white-space: pre; + color: inherit; + display: block; +} + +.doc-content code { + font-family: var(--font-mono); + font-size: 0.9em; +} + +.doc-content p code, .doc-content li code, .faq-answer code { + background: rgba(255, 255, 255, 0.08); + border: 1px solid var(--border-subtle); + padding: 0.15rem 0.45rem; + border-radius: var(--radius-sm); + color: var(--accent-primary-light); + white-space: normal; +} + +/* Chroma Syntax Highlighting (WCAG AA Compliant Theme: contrast >= 4.5:1 on #0D1322) */ +.chroma { + color: var(--text-primary); + background-color: transparent; +} + +.chroma .line { + display: flex; +} + +.chroma .k, .chroma .kc, .chroma .kd, .chroma .kn, .chroma .kp, .chroma .kr, .chroma .kt { + color: #FB7185; /* Keywords & Directives (Rose-400, 5.9:1) */ +} + +.chroma .c, .chroma .ch, .chroma .cm, .chroma .c1, .chroma .cs, .chroma .cp, .chroma .cpf { + color: #94A3B8; /* Comments (Muted Slate, 6.2:1) */ + font-style: italic; +} + +.chroma .s, .chroma .sa, .chroma .sb, .chroma .sc, .chroma .dl, .chroma .sd, .chroma .s2, .chroma .se, .chroma .sh, .chroma .si, .chroma .sx, .chroma .sr, .chroma .s1, .chroma .ss { + color: #FCD34D; /* Strings & Interpolations (Amber-300, 12.5:1) */ +} + +.chroma .m, .chroma .mb, .chroma .mf, .chroma .mh, .chroma .mi, .chroma .il, .chroma .mo { + color: #F472B6; /* Numbers (Pink-400, 6.2:1) */ +} + +.chroma .nt { + color: #38BDF8; /* YAML Keys / Tags (Sky-400, 9.4:1) */ + font-weight: 600; +} + +.chroma .na { + color: #93C5FD; /* Attributes / Properties (Blue-300, 10.4:1) */ +} + +.chroma .nv, .chroma .vc, .chroma .vg, .chroma .vi, .chroma .vm { + color: #FB923C; /* Variables (Orange-400, 7.7:1) */ +} + +.chroma .nf, .chroma .fm, .chroma .nb { + color: #67E8F9; /* Builtins & Functions (Cyan-300, 9.8:1) */ +} + +.chroma .o, .chroma .ow { + color: #38BDF8; /* Operators (Sky-400, 9.4:1) */ +} + +.chroma .p { + color: #94A3B8; /* Punctuation (Muted Slate, 6.2:1) */ +} + +/* Accordion FAQ */ +.faq-accordion { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.faq-item { + background: var(--bg-secondary); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-lg); + overflow: hidden; + transition: all 0.2s ease; +} + +.faq-item:hover { + border-color: var(--border-hover); +} + +.faq-summary { + padding: 1.25rem 1.5rem; + font-weight: 600; + font-size: 1.05rem; + cursor: pointer; + display: flex; + align-items: center; + justify-content: space-between; + list-style: none; + color: var(--text-primary); +} + +.faq-summary::-webkit-details-marker { + display: none; +} + +.faq-summary::after { + content: '+'; + font-size: 1.4rem; + color: var(--accent-primary-light); + transition: transform 0.2s ease; +} + +details[open] .faq-summary::after { + content: '−'; +} + +.faq-answer { + padding: 0 1.5rem 1.5rem; + color: var(--text-secondary); + font-size: 0.98rem; + line-height: 1.6; +} + +/* Mobile Navigation Drawer */ +.mobile-nav-drawer { + display: none; + position: fixed; + top: 72px; + left: 0; + right: 0; + background: var(--bg-secondary); + border-bottom: 1px solid var(--border-subtle); + padding: 1.5rem; + z-index: 99; + flex-direction: column; + gap: 1rem; + box-shadow: var(--shadow-lg); +} + +.mobile-nav-drawer.open { + display: flex; +} + +@media (max-width: 839px) { + .nav-links, .nav-actions .btn { + display: none; + } + .mobile-menu-toggle { + display: flex; + align-items: center; + justify-content: center; + min-width: 48px; + min-height: 48px; + } +} + +/* Utilities */ +.text-center { text-align: center; } +.mt-4 { margin-top: 1rem; } +.mt-8 { margin-top: 2rem; } +.mb-8 { margin-bottom: 2rem; } +.flex-center { display: flex; align-items: center; justify-content: center; } +.gap-2 { gap: 0.5rem; } +.gap-4 { gap: 1rem; } + +/* Security & AI Agent Grid */ +.security-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 2rem; +} + +@media (max-width: 1024px) { + .security-grid { + gap: 1.5rem; + } + .security-card { + padding: 1.75rem; + } +} + +@media (max-width: 839px) { + .security-grid { + grid-template-columns: 1fr; + gap: 1.5rem; + } +} + +.security-card { + background: var(--bg-card); + border: 1px solid var(--border-subtle); + border-radius: var(--radius-lg); + padding: 2.25rem; + display: flex; + flex-direction: column; + position: relative; + transition: all 0.3s ease; + box-shadow: var(--shadow-md); +} + +.security-card:hover { + transform: translateY(-3px); + border-color: var(--border-hover); + box-shadow: var(--shadow-lg), 0 0 25px rgba(16, 185, 129, 0.12); +} + +.security-card-highlight { + border-color: rgba(6, 182, 212, 0.4); + background: linear-gradient(180deg, rgba(6, 182, 212, 0.06) 0%, var(--bg-card) 100%); +} + +.security-card-highlight:hover { + border-color: #06B6D4; + box-shadow: var(--shadow-lg), 0 0 30px rgba(6, 182, 212, 0.18); +} + +.security-card-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 1.5rem; +} + +.security-icon-box { + width: 48px; + height: 48px; + border-radius: var(--radius-md); + display: flex; + align-items: center; + justify-content: center; +} + +.icon-emerald { + background: rgba(16, 185, 129, 0.15); + color: #34D399; + border: 1px solid rgba(52, 211, 153, 0.3); +} + +.icon-cyan { + background: rgba(6, 182, 212, 0.15); + color: #22D3EE; + border: 1px solid rgba(34, 211, 238, 0.3); +} + +.icon-amber { + background: rgba(245, 158, 11, 0.15); + color: #FBBF24; + border: 1px solid rgba(251, 191, 36, 0.3); +} + +.icon-purple { + background: rgba(168, 85, 247, 0.15); + color: #C084FC; + border: 1px solid rgba(192, 132, 252, 0.3); +} + +.security-badge { + font-size: 0.75rem; + font-weight: 700; + padding: 0.3rem 0.75rem; + border-radius: var(--radius-full); + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.badge-emerald { + background: rgba(16, 185, 129, 0.12); + color: #34D399; + border: 1px solid rgba(52, 211, 153, 0.3); +} + +.badge-cyan { + background: rgba(6, 182, 212, 0.12); + color: #22D3EE; + border: 1px solid rgba(34, 211, 238, 0.3); +} + +.badge-amber { + background: rgba(245, 158, 11, 0.12); + color: #FBBF24; + border: 1px solid rgba(251, 191, 36, 0.3); +} + +.badge-purple { + background: rgba(168, 85, 247, 0.12); + color: #C084FC; + border: 1px solid rgba(192, 132, 252, 0.3); +} + +.security-card h3 { + font-size: 1.35rem; + margin-bottom: 0.85rem; + color: var(--text-primary); +} + +.security-card p { + font-size: 0.98rem; + color: var(--text-secondary); + line-height: 1.65; + margin-bottom: 1.5rem; + flex-grow: 1; +} + +.security-card-footer { + display: flex; + gap: 0.6rem; + flex-wrap: wrap; + padding-top: 1.25rem; + border-top: 1px solid var(--border-subtle); +} + +.security-pill { + font-family: var(--font-mono); + font-size: 0.78rem; + font-weight: 600; + color: var(--text-muted); + background: rgba(255, 255, 255, 0.04); + padding: 0.3rem 0.65rem; + border-radius: var(--radius-sm); + border: 1px solid var(--border-subtle); +} + +/* ========================================================================== + Material Design 3 (M3) Adaptive Touch Targets (Compact < 600px) + -------------------------------------------------------------------------- + Enforces >= 48px ergonomic touch target sizing across interactive components + for touch accuracy and accessibility compliance. + ========================================================================== */ +@media (max-width: 599px) { + .btn { + min-height: 48px; + padding-top: 0.85rem; + padding-bottom: 0.85rem; + } + + .race-tab-btn { + min-height: 48px; + padding: 0.6rem 1rem; + } + + .install-tab-btn { + min-height: 48px; + display: inline-flex; + align-items: center; + } + + .copy-btn { + min-height: 48px; + padding: 0.6rem 1rem; + } + + .github-badge-btn { + min-width: 48px; + min-height: 48px; + } + + .cr-feature-card[data-active="false"] { + min-height: 52px; + } + + .cr-card-tab-header { + min-height: 52px; + } + + .hero-badge { + font-size: 0.775rem; + padding: 0.35rem 0.75rem; + gap: 0.35rem; + } + + .hero-trust-chips { + gap: 0.5rem; + margin-top: 1.75rem; + } + + .trust-chip { + font-size: 0.775rem; + padding: 0.4rem 0.75rem; + } +} + +/* ========================================================================== + Heading Anchor Links & Copy Link To Content + ========================================================================== */ + +section[id], +.section[id] { + /* Offset section padding (6rem) so target content starts cleanly at the top with ~2.25rem breathing room */ + scroll-margin-top: -3.75rem; +} + +h2[id], +h3[id], +.section-header[id], +.section-title[id], +.faq-item[id] { + scroll-margin-top: 2rem; +} + +.section-title, +.doc-content h2[id], +.doc-content h3[id] { + position: relative; +} + +.section-title { + padding-left: 2rem; + padding-right: 2rem; + box-sizing: border-box; +} + +@media (max-width: 839px) { + .section-title { + padding-left: 1.5rem; + padding-right: 1.5rem; + } +} + +.heading-anchor-holder { + display: inline-block; + position: relative; + width: 0; + height: 0; + vertical-align: middle; + line-height: 0; + pointer-events: auto; +} + +.faq-question-text { + display: inline; + position: relative; +} + +.heading-anchor-btn { + position: absolute; + left: 0.5rem; + top: 50%; + transform: translateY(-50%) scale(0.95); + display: inline-flex; + align-items: center; + justify-content: center; + width: 1.85rem; + height: 1.85rem; + border-radius: var(--radius-sm); + color: var(--text-muted); + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.08); + cursor: pointer; + text-decoration: none; + opacity: 0.45; /* Visible by default so users discover it immediately */ + transition: opacity 0.2s ease, transform 0.2s ease, background 0.2s ease, border-color 0.2s ease, color 0.2s ease; + flex-shrink: 0; + z-index: 10; +} + +/* Ergonomic touch hit-area without affecting visual bounds */ +.heading-anchor-btn::before { + content: ''; + position: absolute; + top: -8px; + right: -8px; + bottom: -8px; + left: -8px; +} + +.section-header:hover .heading-anchor-btn, +.section-title:hover .heading-anchor-btn, +.doc-content h2:hover .heading-anchor-btn, +.doc-content h3:hover .heading-anchor-btn, +.faq-item:hover .heading-anchor-btn, +.heading-anchor-btn:focus-visible { + opacity: 0.85; + color: var(--text-secondary); + border-color: rgba(255, 255, 255, 0.16); + background: rgba(255, 255, 255, 0.07); + transform: translateY(-50%) scale(1); +} + +.heading-anchor-btn:hover { + opacity: 1 !important; + color: var(--accent-primary) !important; + background: rgba(16, 185, 129, 0.15) !important; + border-color: rgba(16, 185, 129, 0.35) !important; + transform: translateY(-50%) scale(1.08) !important; +} + +.heading-anchor-btn:focus-visible { + outline: 2px solid var(--accent-primary); + outline-offset: 2px; +} + +.heading-anchor-btn .anchor-icon-check { + display: none; +} + +.heading-anchor-btn.copied { + opacity: 1 !important; + color: var(--accent-primary) !important; + background: rgba(16, 185, 129, 0.22) !important; + border-color: var(--accent-primary) !important; + transform: translateY(-50%) scale(1.05) !important; +} + +.heading-anchor-btn.copied .anchor-icon-link { + display: none; +} + +.heading-anchor-btn.copied .anchor-icon-check { + display: block; + animation: check-pop 0.3s cubic-bezier(0.16, 1, 0.3, 1); +} + +@keyframes check-pop { + 0% { transform: scale(0.6); opacity: 0; } + 100% { transform: scale(1); opacity: 1; } +} + +/* Touch devices & Compact Viewports */ +@media (hover: none), (max-width: 839px) { + .heading-anchor-btn { + opacity: 0.65; + transform: translateY(-50%) scale(1); + } +} + +/* Transient Copy Toast Notification */ +.copy-toast { + position: fixed; + bottom: 2rem; + left: 50%; + transform: translateX(-50%) translateY(24px) scale(0.96); + background: rgba(18, 26, 43, 0.94); + border: 1px solid var(--border-active); + color: var(--text-primary); + padding: 0.65rem 1.25rem; + border-radius: var(--radius-full); + display: inline-flex; + align-items: center; + gap: 0.6rem; + font-size: 0.875rem; + font-weight: 500; + box-shadow: var(--shadow-lg), 0 0 25px rgba(16, 185, 129, 0.25); + opacity: 0; + pointer-events: none; + transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1); + z-index: 10000; + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); +} + +.copy-toast.show { + opacity: 1; + transform: translateX(-50%) translateY(0) scale(1); + pointer-events: auto; +} + +.copy-toast svg { + color: var(--accent-primary); + flex-shrink: 0; +} + +/* Subtle target flash when copied/navigated to */ +@keyframes anchor-flash { + 0% { + background-color: transparent; + } + 30% { + background-color: rgba(16, 185, 129, 0.08); + } + 100% { + background-color: transparent; + } +} + +.anchor-highlight { + animation: anchor-flash 1.4s ease-out; +} + diff --git a/static/digital-earth-preview.html b/static/digital-earth-preview.html new file mode 100644 index 0000000..70bcde7 --- /dev/null +++ b/static/digital-earth-preview.html @@ -0,0 +1,254 @@ + + + + + + EarthBuild — Digital Shadow Earth Logo Explorations + + + + + + +
+
+
New Creative Direction
+

Earth in Space: The Digital Shadow

+

Earth seen from space divided into day and night. The shadow hemisphere comes alive as an illuminated digital matrix of glowing electric cyan and emerald green circuitry, container nodes, and data streams.

+
+ "let's imagine Earth, looking from space, half in shadow, let's make shadow part digital, a light one, green and blue" +
+
+ +
+ +
+
+
1. The Vector Circuit Globe
+ CLEAN GRAPHIC DESIGN +
+

Flat graphic design aesthetic. Day side shows dark celestial blue ocean & continents; the shadow side is traced with crisp electric cyan and emerald green circuit board tracks and constellation nodes.

+
+ Vector Circuit Globe +
+ +
+ + +
+
+
2. DAG Network & Container Blocks
+ BUILD ARCHITECTURE +
+

The night hemisphere is alive with illuminated container boxes, interconnected DAG pipeline nodes, and laser data channels spanning across the globe in electric cyan and emerald.

+
+ DAG Network Globe +
+ +
+ + +
+
+
3. The Container Honeycomb Grid
+ GEOMETRIC EMBLEM +
+

Minimalist Swiss emblem. Left half is a sleek dark globe silhouette; right half is an intensely glowing matrix of hexagonal container cells and vertical pipeline beams in electric cyan & emerald.

+
+ Container Honeycomb Grid +
+ +
+ + +
+
+
4. The Cinematic Orbital Hero
+ HERO KEYNOTE ART +
+

High-impact photorealistic Earth from orbit. Pristine blue day side; the shadow night side is illuminated by glowing green and cyan fiber-optic city lights, network topography, and atmospheric halo.

+
+ Cinematic Hero Globe +
+ +
+
+
+ + diff --git a/static/images/concepts/concept-1-delta-dag.svg b/static/images/concepts/concept-1-delta-dag.svg new file mode 100644 index 0000000..bb91160 --- /dev/null +++ b/static/images/concepts/concept-1-delta-dag.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/static/images/concepts/concept-2-kinetic-hex.svg b/static/images/concepts/concept-2-kinetic-hex.svg new file mode 100644 index 0000000..388842b --- /dev/null +++ b/static/images/concepts/concept-2-kinetic-hex.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/static/images/concepts/concept-3-convergence.svg b/static/images/concepts/concept-3-convergence.svg new file mode 100644 index 0000000..122fc6d --- /dev/null +++ b/static/images/concepts/concept-3-convergence.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/static/images/concepts/concept-4-apex-shard.svg b/static/images/concepts/concept-4-apex-shard.svg new file mode 100644 index 0000000..2df77af --- /dev/null +++ b/static/images/concepts/concept-4-apex-shard.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/static/images/concepts/concept-4-tectonic-vector.svg b/static/images/concepts/concept-4-tectonic-vector.svg new file mode 100644 index 0000000..c90d85e --- /dev/null +++ b/static/images/concepts/concept-4-tectonic-vector.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/static/images/concepts/concept-5-prism-container.svg b/static/images/concepts/concept-5-prism-container.svg new file mode 100644 index 0000000..981c9bd --- /dev/null +++ b/static/images/concepts/concept-5-prism-container.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/static/images/concepts/concept-6-slanted-container.svg b/static/images/concepts/concept-6-slanted-container.svg new file mode 100644 index 0000000..d4efc98 --- /dev/null +++ b/static/images/concepts/concept-6-slanted-container.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/static/images/concepts/digital-earth.svg b/static/images/concepts/digital-earth.svg new file mode 100644 index 0000000..f04e0e9 --- /dev/null +++ b/static/images/concepts/digital-earth.svg @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/images/concepts/earth_digital_dag_nodes.jpg b/static/images/concepts/earth_digital_dag_nodes.jpg new file mode 100644 index 0000000..245e9b5 Binary files /dev/null and b/static/images/concepts/earth_digital_dag_nodes.jpg differ diff --git a/static/images/concepts/earth_digital_shadow_hero.jpg b/static/images/concepts/earth_digital_shadow_hero.jpg new file mode 100644 index 0000000..1948418 Binary files /dev/null and b/static/images/concepts/earth_digital_shadow_hero.jpg differ diff --git a/static/images/concepts/earth_digital_shadow_minimal.jpg b/static/images/concepts/earth_digital_shadow_minimal.jpg new file mode 100644 index 0000000..9ef1ed2 Binary files /dev/null and b/static/images/concepts/earth_digital_shadow_minimal.jpg differ diff --git a/static/images/concepts/earth_digital_shadow_vector.jpg b/static/images/concepts/earth_digital_shadow_vector.jpg new file mode 100644 index 0000000..dd5c9b4 Binary files /dev/null and b/static/images/concepts/earth_digital_shadow_vector.jpg differ diff --git a/static/images/concepts/simple-1-circuit-hemisphere.svg b/static/images/concepts/simple-1-circuit-hemisphere.svg new file mode 100644 index 0000000..92b72d6 --- /dev/null +++ b/static/images/concepts/simple-1-circuit-hemisphere.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/static/images/concepts/simple-2-digital-meridian.svg b/static/images/concepts/simple-2-digital-meridian.svg new file mode 100644 index 0000000..ce64b5a --- /dev/null +++ b/static/images/concepts/simple-2-digital-meridian.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/static/images/concepts/simple-3-container-lattice.svg b/static/images/concepts/simple-3-container-lattice.svg new file mode 100644 index 0000000..5585c91 --- /dev/null +++ b/static/images/concepts/simple-3-container-lattice.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/static/images/concepts/simple-4-minimal-terminator.svg b/static/images/concepts/simple-4-minimal-terminator.svg new file mode 100644 index 0000000..aac925e --- /dev/null +++ b/static/images/concepts/simple-4-minimal-terminator.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/static/images/favicon.svg b/static/images/favicon.svg new file mode 100644 index 0000000..0a59a56 --- /dev/null +++ b/static/images/favicon.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/static/images/integration-diagram.svg b/static/images/integration-diagram.svg new file mode 100644 index 0000000..089cfeb --- /dev/null +++ b/static/images/integration-diagram.svg @@ -0,0 +1,110 @@ + + EarthBuild wraps your build tooling and runs anywhere + + + + + + + + + + + + Go + + TypeScript + + Python + + Rust + + npm + + maven + + cargo + + gradle · … + + + + + + + + + + + EarthBuild + wraps your build tools + into one repeatable build + + + + + + + + + + + + Your laptop + + GitHub Actions + + Jenkins + + GitLab CI + + Circle CI + + Cloud runners + + Buildkite + + …any container host + + + + One build definition. Reproducible. No build guru required. + diff --git a/static/images/logo-earthbuild.svg b/static/images/logo-earthbuild.svg new file mode 100644 index 0000000..2e2a635 --- /dev/null +++ b/static/images/logo-earthbuild.svg @@ -0,0 +1,11 @@ + + EarthBuild + + + + + + + + + diff --git a/static/js/bundle.js b/static/js/bundle.js new file mode 100644 index 0000000..c2dc98d --- /dev/null +++ b/static/js/bundle.js @@ -0,0 +1,2149 @@ +(()=>{function ys(){let a=document.getElementById("mobileMenuToggle")||document.querySelector(".mobile-menu-toggle")||document.querySelector(".nav-toggle"),s=document.getElementById("mobileNavDrawer")||document.querySelector(".mobile-nav-drawer")||document.querySelector(".nav-menu"),n=document.querySelector(".navbar")||document.querySelector(".site-nav");a&&s&&(a.addEventListener("click",e=>{e.stopPropagation();let p=s.classList.toggle("open");s.classList.toggle("active",p),a.setAttribute("aria-expanded",p?"true":"false")}),s.querySelectorAll("a").forEach(e=>{e.addEventListener("click",()=>{s.classList.remove("open","active"),a.setAttribute("aria-expanded","false")})}),document.addEventListener("click",e=>{let p=e.target;p&&!s.contains(p)&&!a.contains(p)&&(s.classList.remove("open","active"),a.setAttribute("aria-expanded","false"))}),document.addEventListener("keydown",e=>{e.key==="Escape"&&(s.classList.remove("open","active"),a.setAttribute("aria-expanded","false"))})),window.addEventListener("scroll",()=>{window.scrollY>20?n?.classList.add("scrolled"):n?.classList.remove("scrolled")},{passive:!0})}function bs(){document.querySelectorAll('a[href^="#"]').forEach(a=>{a.addEventListener("click",function(s){let n=this.getAttribute("href");if(!n||n==="#")return;let e=document.querySelector(n);if(e){s.preventDefault();let t=e.getBoundingClientRect().top+window.pageYOffset-80;window.scrollTo({top:t,behavior:"smooth"}),(document.getElementById("mobileNavDrawer")||document.querySelector(".mobile-nav-drawer")||document.querySelector(".nav-menu"))?.classList.remove("open","active")}})})}function Rs(){document.addEventListener("click",async a=>{let s=a.target?.closest?.(".copy-btn");if(!s)return;let n=s.getAttribute("data-clipboard-target"),e=n?document.getElementById(n):null,p=e?e.innerText||e.textContent:s.getAttribute("data-copy-text");if(p)try{await navigator.clipboard.writeText(p.trim());let A=s.innerHTML;s.classList.add("copied"),s.innerHTML=` + + Copied! + `,setTimeout(()=>{s.innerHTML=A,s.classList.remove("copied")},2e3)}catch(A){console.error("Clipboard copy failed:",A)}})}function Cs(){let a=document.querySelectorAll(".install-tab-btn"),s=document.getElementById("installCodeSnippet"),n={brew:"brew install earthbuild/tap/earth",curl:"curl -fsSL https://www.earthbuild.dev/install.sh | sh",windows:"curl -fsSL https://www.earthbuild.dev/install.sh | sh",nix:"nix-shell -p earthbuild"};a.forEach(e=>{e.addEventListener("click",()=>{a.forEach(A=>A.classList.remove("active")),e.classList.add("active");let p=e.getAttribute("data-install-type");s&&p&&n[p]&&(s.innerText=n[p])})})}var K={go:{filename:"examples/go/Earthfile",repoUrl:"https://github.com/earthbuild/earthbuild/tree/main/examples/go",metrics:{cachedTime:"1.1s",editTime:"1.8s",coldTime:"6.2s",editSpeedup:"3.4x Faster",speedup:"5.6x Faster",saved:"82% Saved (5.1s)",ciSaved:"82% Fewer Runner Mins",co2Saved:"~1.4g CO\u2082e / build"},code:`VERSION 0.8 +FROM golang:1.27-alpine3.24 +WORKDIR /go-example + +# deps caches vendor dependencies independently +deps: + COPY go.mod go.sum ./ + RUN go mod download + SAVE ARTIFACT go.mod AS LOCAL go.mod + SAVE ARTIFACT go.sum AS LOCAL go.sum + +# build compiles standalone Go binary with instant layer cache +build: + FROM +deps + COPY main.go . + RUN go build -o build/go-example main.go + SAVE ARTIFACT build/go-example /go-example AS LOCAL build/go-example`,cached:[{text:"$ earth +build",class:"term-cmd",delay:80},{text:"\u2500\u2500\u2500 Init \u{1F680} Found buildkit daemon as podman container (earth-buildkitd)",class:"term-dim",delay:180},{text:"\u2500\u2500\u2500 Build \u{1F527}",class:"term-bold",delay:260},{text:" +base | *cached* --> FROM golang:1.27-alpine3.24",class:"term-cached",delay:360},{text:" +base | *cached* --> WORKDIR /go-example",class:"term-cached",delay:460},{text:" +deps | *cached* --> COPY go.mod go.sum ./",class:"term-cached",delay:560},{text:" +deps | *cached* --> RUN go mod download",class:"term-cached",delay:660},{text:" +build | *cached* --> COPY main.go .",class:"term-cached",delay:760},{text:" +build | *cached* --> RUN go build -o build/go-example main.go",class:"term-cached",delay:860},{text:" +build | *cached* --> SAVE ARTIFACT build/go-example AS LOCAL build/go-example",class:"term-cached",delay:960},{text:" output | [----------] 100% exporting outputs",class:"terminal-line",delay:1020},{text:"\u{1F381} Artifact +build/go-example output as build/go-example",class:"term-running",delay:1060},{text:"============= \u{1F30D} Earth Build \u2705 SUCCESS [1.1s] =============",class:"term-success",delay:1100}],edit:[{text:"$ earth +build",class:"term-cmd",delay:80},{text:"\u2500\u2500\u2500 Init \u{1F680} Found buildkit daemon as podman container (earth-buildkitd)",class:"term-dim",delay:180},{text:"\u2500\u2500\u2500 Build \u{1F527}",class:"term-bold",delay:260},{text:" +base | *cached* --> FROM golang:1.27-alpine3.24",class:"term-cached",delay:360},{text:" +base | *cached* --> WORKDIR /go-example",class:"term-cached",delay:460},{text:" +deps | *cached* --> COPY go.mod go.sum ./",class:"term-cached",delay:560},{text:" +deps | *cached* --> RUN go mod download",class:"term-cached",delay:660},{text:" +build | --> COPY main.go .",class:"terminal-line",delay:900},{text:" +build | --> RUN go build -o build/go-example main.go",class:"term-yellow",delay:1300},{text:" +build | --> SAVE ARTIFACT build/go-example AS LOCAL build/go-example",class:"terminal-line",delay:1550},{text:" output | [----------] 100% exporting outputs",class:"terminal-line",delay:1650},{text:"\u{1F381} Artifact +build/go-example output as build/go-example",class:"term-running",delay:1720},{text:"============= \u{1F30D} Earth Build \u2705 SUCCESS [1.8s] =============",class:"term-success",delay:1800}],cold:[{text:"$ earth +build",class:"term-cmd",delay:80},{text:"\u2500\u2500\u2500 Init \u{1F680} Found buildkit daemon as podman container (earth-buildkitd)",class:"term-dim",delay:250},{text:"\u2500\u2500\u2500 Build \u{1F527}",class:"term-bold",delay:450},{text:" +base | --> FROM golang:1.27-alpine3.24",class:"terminal-line",delay:900},{text:" +base | --> WORKDIR /go-example",class:"terminal-line",delay:1400},{text:" +deps | --> COPY go.mod go.sum ./",class:"terminal-line",delay:1900},{text:" +deps | --> RUN go mod download",class:"term-yellow",delay:2700},{text:" +build | --> COPY main.go .",class:"terminal-line",delay:3800},{text:" +build | --> RUN go build -o build/go-example main.go",class:"term-yellow",delay:4900},{text:" +build | --> SAVE ARTIFACT build/go-example AS LOCAL build/go-example",class:"terminal-line",delay:5600},{text:" output | [----------] 100% exporting outputs",class:"terminal-line",delay:5900},{text:"\u{1F381} Artifact +build/go-example output as build/go-example",class:"term-running",delay:6050},{text:"============= \u{1F30D} Earth Build \u2705 SUCCESS [6.2s] =============",class:"term-success",delay:6200}]},cpp:{filename:"examples/cpp/Earthfile",repoUrl:"https://github.com/earthbuild/earthbuild/tree/main/examples/cpp",metrics:{cachedTime:"0.9s",editTime:"2.6s",coldTime:"22.8s",editSpeedup:"8.8x Faster",speedup:"25.3x Faster",saved:"96% Saved (21.9s)",ciSaved:"96% Fewer Runner Mins",co2Saved:"~5.5g CO\u2082e / build"},code:`VERSION 0.8 +FROM ubuntu:26.04 +RUN apt-get update && apt-get install -y build-essential cmake +WORKDIR /code + +# code copies C++ source tree +code: + COPY src src + +# build compiles with CMake & persistent compiler cache mount +build: + FROM +code + COPY CMakeLists.txt . + RUN --mount=type=cache,target=/root/.cache/ccache cmake -B build -S . && cmake --build build + SAVE ARTIFACT build/app AS LOCAL build/app`,cached:[{text:"$ earth +build",class:"term-cmd",delay:80},{text:"\u2500\u2500\u2500 Init \u{1F680} Found buildkit daemon as podman container (earth-buildkitd)",class:"term-dim",delay:150},{text:"\u2500\u2500\u2500 Build \u{1F527}",class:"term-bold",delay:220},{text:" +base | *cached* --> FROM ubuntu:26.04",class:"term-cached",delay:300},{text:" +base | *cached* --> RUN apt-get update && apt-get install -y build-essential cmake",class:"term-cached",delay:400},{text:" +code | *cached* --> COPY src src",class:"term-cached",delay:490},{text:" +build | *cached* --> COPY CMakeLists.txt .",class:"term-cached",delay:580},{text:" +build | *cached* --> RUN --mount=type=cache,target=/root/.cache/ccache cmake -B build -S . && cmake --build build",class:"term-cached",delay:680},{text:" +build | *cached* --> SAVE ARTIFACT build/app AS LOCAL build/app",class:"term-cached",delay:780},{text:" output | [----------] 100% exporting outputs",class:"terminal-line",delay:830},{text:"\u{1F381} Artifact +build/app output as build/app",class:"term-running",delay:870},{text:"============= \u{1F30D} Earth Build \u2705 SUCCESS [0.9s] =============",class:"term-success",delay:900}],edit:[{text:"$ earth +build",class:"term-cmd",delay:80},{text:"\u2500\u2500\u2500 Init \u{1F680} Found buildkit daemon as podman container (earth-buildkitd)",class:"term-dim",delay:180},{text:"\u2500\u2500\u2500 Build \u{1F527}",class:"term-bold",delay:260},{text:" +base | *cached* --> FROM ubuntu:26.04",class:"term-cached",delay:360},{text:" +base | *cached* --> RUN apt-get update && apt-get install -y build-essential cmake",class:"term-cached",delay:460},{text:" +code | --> COPY src src",class:"term-yellow",delay:750},{text:" +build | *cached* --> COPY CMakeLists.txt .",class:"term-cached",delay:950},{text:" +build | --> RUN --mount=type=cache,target=/root/.cache/ccache cmake -B build -S . && cmake --build build",class:"term-yellow",delay:1800},{text:" +build | [100%] Linking CXX executable build/app",class:"terminal-line",delay:2200},{text:" +build | --> SAVE ARTIFACT build/app AS LOCAL build/app",class:"terminal-line",delay:2400},{text:"============= \u{1F30D} Earth Build \u2705 SUCCESS [2.6s] =============",class:"term-success",delay:2600}],cold:[{text:"$ earth +build",class:"term-cmd",delay:80},{text:"\u2500\u2500\u2500 Init \u{1F680} Found buildkit daemon as podman container (earth-buildkitd)",class:"term-dim",delay:300},{text:"\u2500\u2500\u2500 Build \u{1F527}",class:"term-bold",delay:600},{text:" +base | --> FROM ubuntu:26.04",class:"terminal-line",delay:1500},{text:" +base | --> RUN apt-get update && apt-get install -y build-essential cmake",class:"term-yellow",delay:6500},{text:" +code | --> COPY src src",class:"terminal-line",delay:10500},{text:" +build | --> COPY CMakeLists.txt .",class:"terminal-line",delay:12e3},{text:" +build | --> RUN --mount=type=cache,target=/root/.cache/ccache cmake -B build -S . && cmake --build build",class:"terminal-line",delay:14e3},{text:" +build | [ 33%] Building CXX object CMakeFiles/app.dir/main.cpp.o",class:"terminal-line",delay:17e3},{text:" +build | [ 66%] Building CXX object CMakeFiles/app.dir/util.cpp.o",class:"terminal-line",delay:19e3},{text:" +build | [100%] Linking CXX executable build/app",class:"terminal-line",delay:21e3},{text:" +build | --> SAVE ARTIFACT build/app AS LOCAL build/app",class:"terminal-line",delay:22200},{text:"============= \u{1F30D} Earth Build \u2705 SUCCESS [22.8s] =============",class:"term-success",delay:22800}]},python:{filename:"examples/python/Earthfile",repoUrl:"https://github.com/earthbuild/earthbuild/tree/main/examples/python",metrics:{cachedTime:"0.8s",editTime:"1.8s",coldTime:"14.5s",editSpeedup:"8.1x Faster",speedup:"18.1x Faster",saved:"94% Saved (13.7s)",ciSaved:"94% Fewer Runner Mins",co2Saved:"~3.5g CO\u2082e / build"},code:`VERSION 0.8 +FROM python:3.14-slim +WORKDIR /app + +# deps caches pip dependencies in isolated container layer +deps: + COPY requirements.txt . + RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt + +# test runs unit tests inside isolated container +test: + FROM +deps + COPY src src + RUN pytest src/`,cached:[{text:"$ earth +test",class:"term-cmd",delay:80},{text:"\u2500\u2500\u2500 Init \u{1F680} Found buildkit daemon as podman container (earth-buildkitd)",class:"term-dim",delay:150},{text:"\u2500\u2500\u2500 Build \u{1F527}",class:"term-bold",delay:220},{text:" +deps | *cached* --> FROM python:3.14-slim",class:"term-cached",delay:300},{text:" +deps | *cached* --> WORKDIR /app",class:"term-cached",delay:390},{text:" +deps | *cached* --> COPY requirements.txt .",class:"term-cached",delay:480},{text:" +deps | *cached* --> RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt",class:"term-cached",delay:580},{text:" +test | *cached* --> COPY src src",class:"term-cached",delay:680},{text:" +test | *cached* --> RUN pytest src/",class:"term-cached",delay:750},{text:"============= \u{1F30D} Earth Build \u2705 SUCCESS [0.8s] =============",class:"term-success",delay:800}],edit:[{text:"$ earth +test",class:"term-cmd",delay:80},{text:"\u2500\u2500\u2500 Init \u{1F680} Found buildkit daemon as podman container (earth-buildkitd)",class:"term-dim",delay:180},{text:"\u2500\u2500\u2500 Build \u{1F527}",class:"term-bold",delay:260},{text:" +deps | *cached* --> FROM python:3.14-slim",class:"term-cached",delay:360},{text:" +deps | *cached* --> WORKDIR /app",class:"term-cached",delay:460},{text:" +deps | *cached* --> COPY requirements.txt .",class:"term-cached",delay:560},{text:" +deps | *cached* --> RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt",class:"term-cached",delay:660},{text:" +test | --> COPY src src",class:"term-yellow",delay:850},{text:" +test | --> RUN pytest src/",class:"term-yellow",delay:1400},{text:" +test | ================= 14 passed in 0.23s =================",class:"terminal-line",delay:1650},{text:"============= \u{1F30D} Earth Build \u2705 SUCCESS [1.8s] =============",class:"term-success",delay:1800}],cold:[{text:"$ earth +test",class:"term-cmd",delay:80},{text:"\u2500\u2500\u2500 Init \u{1F680} Found buildkit daemon as podman container (earth-buildkitd)",class:"term-dim",delay:300},{text:"\u2500\u2500\u2500 Build \u{1F527}",class:"term-bold",delay:600},{text:" +deps | --> FROM python:3.14-slim",class:"terminal-line",delay:1800},{text:" +deps | --> WORKDIR /app",class:"terminal-line",delay:3e3},{text:" +deps | --> COPY requirements.txt .",class:"terminal-line",delay:4500},{text:" +deps | --> RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt",class:"term-yellow",delay:7500},{text:" +test | --> COPY src src",class:"terminal-line",delay:11e3},{text:" +test | --> RUN pytest src/",class:"term-yellow",delay:13200},{text:" +test | ================= 14 passed in 0.45s =================",class:"terminal-line",delay:14e3},{text:"============= \u{1F30D} Earth Build \u2705 SUCCESS [14.5s] =============",class:"term-success",delay:14500}]},rust:{filename:"examples/rust/Earthfile",repoUrl:"https://github.com/earthbuild/earthbuild/tree/main/examples/rust",metrics:{cachedTime:"1.4s",editTime:"4.2s",coldTime:"38.4s",editSpeedup:"9.1x Faster",speedup:"27.4x Faster",saved:"96% Saved (37.0s)",ciSaved:"96% Fewer Runner Mins",co2Saved:"~9.2g CO\u2082e / build"},code:`VERSION 0.8 +FROM rust:1.85-alpine +WORKDIR /rust-app + +# deps pre-warms cargo registry & dependency build cache +deps: + COPY Cargo.toml Cargo.lock ./ + RUN mkdir src && echo "fn main() {}" > src/main.rs + RUN cargo build --release + +# build compiles release binary using persistent sccache mount +build: + FROM +deps + COPY src src + RUN --mount=type=cache,target=/usr/local/cargo/registry cargo build --release + SAVE ARTIFACT target/release/app AS LOCAL bin/app`,cached:[{text:"$ earth +build",class:"term-cmd",delay:80},{text:"\u2500\u2500\u2500 Init \u{1F680} Found buildkit daemon as podman container (earth-buildkitd)",class:"term-dim",delay:180},{text:"\u2500\u2500\u2500 Build \u{1F527}",class:"term-bold",delay:260},{text:" +base | *cached* --> FROM rust:1.85-alpine",class:"term-cached",delay:380},{text:" +base | *cached* --> WORKDIR /rust-app",class:"term-cached",delay:460},{text:" +deps | *cached* --> COPY Cargo.toml Cargo.lock ./",class:"term-cached",delay:560},{text:" +deps | *cached* --> RUN cargo build --release",class:"term-cached",delay:680},{text:" +build | *cached* --> COPY src src",class:"term-cached",delay:780},{text:" +build | *cached* --> RUN --mount=type=cache,target=/usr/local/cargo/registry cargo build --release",class:"term-cached",delay:900},{text:" +build | *cached* --> SAVE ARTIFACT target/release/app AS LOCAL bin/app",class:"term-cached",delay:1050},{text:" output | [----------] 100% exporting outputs",class:"terminal-line",delay:1180},{text:"\u{1F381} Artifact +build/app output as bin/app",class:"term-running",delay:1280},{text:"============= \u{1F30D} Earth Build \u2705 SUCCESS [1.4s] =============",class:"term-success",delay:1400}],edit:[{text:"$ earth +build",class:"term-cmd",delay:80},{text:"\u2500\u2500\u2500 Init \u{1F680} Found buildkit daemon as podman container (earth-buildkitd)",class:"term-dim",delay:180},{text:"\u2500\u2500\u2500 Build \u{1F527}",class:"term-bold",delay:260},{text:" +base | *cached* --> FROM rust:1.85-alpine",class:"term-cached",delay:380},{text:" +base | *cached* --> WORKDIR /rust-app",class:"term-cached",delay:460},{text:" +deps | *cached* --> COPY Cargo.toml Cargo.lock ./",class:"term-cached",delay:560},{text:" +deps | *cached* --> RUN cargo build --release",class:"term-cached",delay:680},{text:" +build | --> COPY src src",class:"term-yellow",delay:1100},{text:" +build | --> RUN --mount=type=cache,target=/usr/local/cargo/registry cargo build --release",class:"term-yellow",delay:2400},{text:" +build | Compiling app v0.1.0 (/rust-app)",class:"terminal-line",delay:3500},{text:" +build | --> SAVE ARTIFACT target/release/app AS LOCAL bin/app",class:"terminal-line",delay:3950},{text:"============= \u{1F30D} Earth Build \u2705 SUCCESS [4.2s] =============",class:"term-success",delay:4200}],cold:[{text:"$ earth +build",class:"term-cmd",delay:80},{text:"\u2500\u2500\u2500 Init \u{1F680} Found buildkit daemon as podman container (earth-buildkitd)",class:"term-dim",delay:300},{text:"\u2500\u2500\u2500 Build \u{1F527}",class:"term-bold",delay:600},{text:" +base | --> FROM rust:1.85-alpine",class:"terminal-line",delay:1500},{text:" +base | --> WORKDIR /rust-app",class:"terminal-line",delay:3e3},{text:" +deps | --> COPY Cargo.toml Cargo.lock ./",class:"terminal-line",delay:4500},{text:" +deps | --> RUN cargo build --release",class:"term-yellow",delay:8500},{text:" +deps | Updating crates.io index",class:"terminal-line",delay:14e3},{text:" +deps | Compiling 58 dependencies",class:"terminal-line",delay:22e3},{text:" +build | --> COPY src src",class:"terminal-line",delay:27e3},{text:" +build | --> RUN --mount=type=cache,target=/usr/local/cargo/registry cargo build --release",class:"term-yellow",delay:32e3},{text:" +build | Compiling app v0.1.0 (/rust-app)",class:"terminal-line",delay:35500},{text:" +build | --> SAVE ARTIFACT target/release/app AS LOCAL bin/app",class:"terminal-line",delay:37500},{text:"============= \u{1F30D} Earth Build \u2705 SUCCESS [38.4s] =============",class:"term-success",delay:38400}]},zig:{filename:"examples/zig/Earthfile",repoUrl:"https://github.com/earthbuild/earthbuild/tree/main/examples/zig",metrics:{cachedTime:"0.7s",editTime:"1.6s",coldTime:"8.6s",editSpeedup:"5.4x Faster",speedup:"12.3x Faster",saved:"92% Saved (7.9s)",ciSaved:"92% Fewer Runner Mins",co2Saved:"~2.0g CO\u2082e / build"},code:`VERSION 0.8 +FROM alpine:3.24 +WORKDIR /zig-app + +# toolchain downloads Zig compiler toolchain +toolchain: + RUN apk add --no-cache curl tar xz + RUN curl -LO https://ziglang.org/download/0.14.0/zig-linux-aarch64-0.14.0.tar.xz && \\ + tar -xf zig-linux-aarch64-0.14.0.tar.xz && mv zig-linux-aarch64-0.14.0 /opt/zig + ENV PATH="/opt/zig:\${PATH}" + +# build compiles optimized ReleaseFast binary +build: + FROM +toolchain + COPY build.zig src ./ + RUN --mount=type=cache,target=/root/.cache/zig zig build -Doptimize=ReleaseFast + SAVE ARTIFACT zig-out/bin/main AS LOCAL bin/zig-app`,cached:[{text:"$ earth +build",class:"term-cmd",delay:80},{text:"\u2500\u2500\u2500 Init \u{1F680} Found buildkit daemon as podman container (earth-buildkitd)",class:"term-dim",delay:150},{text:"\u2500\u2500\u2500 Build \u{1F527}",class:"term-bold",delay:220},{text:" +toolchain | *cached* --> FROM alpine:3.24",class:"term-cached",delay:300},{text:" +toolchain | *cached* --> RUN apk add --no-cache curl tar xz",class:"term-cached",delay:380},{text:" +toolchain | *cached* --> RUN curl -LO https://ziglang.org/download/0.14.0/zig-linux-aarch64-0.14.0.tar.xz",class:"term-cached",delay:460},{text:" +build | *cached* --> COPY build.zig src ./",class:"term-cached",delay:520},{text:" +build | *cached* --> RUN --mount=type=cache,target=/root/.cache/zig zig build -Doptimize=ReleaseFast",class:"term-cached",delay:580},{text:" +build | *cached* --> SAVE ARTIFACT zig-out/bin/main AS LOCAL bin/zig-app",class:"term-cached",delay:640},{text:" output | [----------] 100% exporting outputs",class:"terminal-line",delay:670},{text:"\u{1F381} Artifact +build/main output as bin/zig-app",class:"term-running",delay:685},{text:"============= \u{1F30D} Earth Build \u2705 SUCCESS [0.7s] =============",class:"term-success",delay:700}],edit:[{text:"$ earth +build",class:"term-cmd",delay:80},{text:"\u2500\u2500\u2500 Init \u{1F680} Found buildkit daemon as podman container (earth-buildkitd)",class:"term-dim",delay:180},{text:"\u2500\u2500\u2500 Build \u{1F527}",class:"term-bold",delay:260},{text:" +toolchain | *cached* --> FROM alpine:3.24",class:"term-cached",delay:380},{text:" +toolchain | *cached* --> RUN apk add --no-cache curl tar xz",class:"term-cached",delay:460},{text:" +toolchain | *cached* --> RUN curl -LO https://ziglang.org/download/0.14.0/zig-linux-aarch64-0.14.0.tar.xz",class:"term-cached",delay:540},{text:" +build | --> COPY build.zig src ./",class:"term-yellow",delay:700},{text:" +build | --> RUN --mount=type=cache,target=/root/.cache/zig zig build -Doptimize=ReleaseFast",class:"term-yellow",delay:1200},{text:" +build | --> SAVE ARTIFACT zig-out/bin/main AS LOCAL bin/zig-app",class:"terminal-line",delay:1450},{text:"============= \u{1F30D} Earth Build \u2705 SUCCESS [1.6s] =============",class:"term-success",delay:1600}],cold:[{text:"$ earth +build",class:"term-cmd",delay:80},{text:"\u2500\u2500\u2500 Init \u{1F680} Found buildkit daemon as podman container (earth-buildkitd)",class:"term-dim",delay:250},{text:"\u2500\u2500\u2500 Build \u{1F527}",class:"term-bold",delay:500},{text:" +toolchain | --> FROM alpine:3.24",class:"terminal-line",delay:1200},{text:" +toolchain | --> RUN apk add --no-cache curl tar xz",class:"terminal-line",delay:2500},{text:" +toolchain | --> RUN curl -LO https://ziglang.org/download/0.14.0/zig-linux-aarch64-0.14.0.tar.xz",class:"terminal-line",delay:4500},{text:" +build | --> COPY build.zig src ./",class:"terminal-line",delay:5400},{text:" +build | --> RUN --mount=type=cache,target=/root/.cache/zig zig build -Doptimize=ReleaseFast",class:"term-yellow",delay:6900},{text:" +build | [1/4] Compiling src/main.zig",class:"terminal-line",delay:7600},{text:" +build | [4/4] Linking binary zig-out/bin/main",class:"terminal-line",delay:8100},{text:" +build | --> SAVE ARTIFACT zig-out/bin/main AS LOCAL bin/zig-app",class:"terminal-line",delay:8400},{text:"============= \u{1F30D} Earth Build \u2705 SUCCESS [8.6s] =============",class:"term-success",delay:8600}]},typescript:{filename:"examples/typescript-node/Earthfile",repoUrl:"https://github.com/earthbuild/earthbuild/tree/main/examples/typescript-node",metrics:{cachedTime:"1.2s",editTime:"2.8s",coldTime:"16.2s",editSpeedup:"5.8x Faster",speedup:"13.5x Faster",saved:"93% Saved (15.0s)",ciSaved:"93% Fewer Runner Mins",co2Saved:"~3.8g CO\u2082e / build"},code:`VERSION 0.8 +FROM node:26-alpine +WORKDIR /ts-app + +# deps caches npm dependencies +deps: + COPY package.json package-lock.json ./ + RUN --mount=type=cache,target=/root/.npm npm ci + +# build typechecks & builds production bundle +build: + FROM +deps + COPY tsconfig.json vite.config.ts src ./ + RUN npm run build + SAVE ARTIFACT dist AS LOCAL dist`,cached:[{text:"$ earth +build",class:"term-cmd",delay:80},{text:"\u2500\u2500\u2500 Init \u{1F680} Found buildkit daemon as podman container (earth-buildkitd)",class:"term-dim",delay:180},{text:"\u2500\u2500\u2500 Build \u{1F527}",class:"term-bold",delay:260},{text:" +deps | *cached* --> FROM node:26-alpine",class:"term-cached",delay:380},{text:" +deps | *cached* --> WORKDIR /ts-app",class:"term-cached",delay:450},{text:" +deps | *cached* --> COPY package.json package-lock.json ./",class:"term-cached",delay:520},{text:" +deps | *cached* --> RUN --mount=type=cache,target=/root/.npm npm ci",class:"term-cached",delay:600},{text:" +build | *cached* --> COPY tsconfig.json vite.config.ts src ./",class:"term-cached",delay:680},{text:" +build | *cached* --> RUN npm run build",class:"term-cached",delay:800},{text:" +build | *cached* --> SAVE ARTIFACT dist AS LOCAL dist",class:"term-cached",delay:950},{text:" output | [----------] 100% exporting outputs",class:"terminal-line",delay:1050},{text:"\u{1F381} Artifact +build/dist output as dist",class:"term-running",delay:1120},{text:"============= \u{1F30D} Earth Build \u2705 SUCCESS [1.2s] =============",class:"term-success",delay:1200}],edit:[{text:"$ earth +build",class:"term-cmd",delay:80},{text:"\u2500\u2500\u2500 Init \u{1F680} Found buildkit daemon as podman container (earth-buildkitd)",class:"term-dim",delay:180},{text:"\u2500\u2500\u2500 Build \u{1F527}",class:"term-bold",delay:260},{text:" +deps | *cached* --> FROM node:26-alpine",class:"term-cached",delay:380},{text:" +deps | *cached* --> WORKDIR /ts-app",class:"term-cached",delay:450},{text:" +deps | *cached* --> COPY package.json package-lock.json ./",class:"term-cached",delay:520},{text:" +deps | *cached* --> RUN --mount=type=cache,target=/root/.npm npm ci",class:"term-cached",delay:600},{text:" +build | --> COPY tsconfig.json vite.config.ts src ./",class:"term-yellow",delay:900},{text:" +build | --> RUN npm run build",class:"term-yellow",delay:2e3},{text:" +build | --> SAVE ARTIFACT dist AS LOCAL dist",class:"terminal-line",delay:2600},{text:"============= \u{1F30D} Earth Build \u2705 SUCCESS [2.8s] =============",class:"term-success",delay:2800}],cold:[{text:"$ earth +build",class:"term-cmd",delay:80},{text:"\u2500\u2500\u2500 Init \u{1F680} Found buildkit daemon as podman container (earth-buildkitd)",class:"term-dim",delay:300},{text:"\u2500\u2500\u2500 Build \u{1F527}",class:"term-bold",delay:600},{text:" +deps | --> FROM node:26-alpine",class:"terminal-line",delay:1500},{text:" +deps | --> WORKDIR /ts-app",class:"terminal-line",delay:2500},{text:" +deps | --> COPY package.json package-lock.json ./",class:"terminal-line",delay:3500},{text:" +deps | --> RUN --mount=type=cache,target=/root/.npm npm ci",class:"term-yellow",delay:6500},{text:" +deps | added 214 packages in 3.8s",class:"terminal-line",delay:7e3},{text:" +build | --> COPY tsconfig.json vite.config.ts src ./",class:"terminal-line",delay:8500},{text:" +build | --> RUN npm run build",class:"term-yellow",delay:12500},{text:" +build | --> SAVE ARTIFACT dist AS LOCAL dist",class:"terminal-line",delay:15600},{text:"============= \u{1F30D} Earth Build \u2705 SUCCESS [16.2s] =============",class:"term-success",delay:16200}]},js:{filename:"examples/js/Earthfile",repoUrl:"https://github.com/earthbuild/earthbuild/tree/main/examples/js",metrics:{cachedTime:"0.9s",editTime:"1.8s",coldTime:"11.4s",editSpeedup:"6.3x Faster",speedup:"12.6x Faster",saved:"92% Saved (10.5s)",ciSaved:"92% Fewer Runner Mins",co2Saved:"~2.6g CO\u2082e / build"},code:`VERSION 0.8 +FROM node:26-alpine +WORKDIR /js-app + +# deps caches npm modules layer +deps: + COPY package.json package-lock.json ./ + RUN npm install + +# test runs test suite +test: + FROM +deps + COPY src test ./ + RUN npm test`,cached:[{text:"$ earth +test",class:"term-cmd",delay:80},{text:"\u2500\u2500\u2500 Init \u{1F680} Found buildkit daemon as podman container (earth-buildkitd)",class:"term-dim",delay:160},{text:"\u2500\u2500\u2500 Build \u{1F527}",class:"term-bold",delay:240},{text:" +base | *cached* --> FROM node:26-alpine",class:"term-cached",delay:340},{text:" +base | *cached* --> WORKDIR /js-app",class:"term-cached",delay:400},{text:" +deps | *cached* --> COPY package.json package-lock.json ./",class:"term-cached",delay:480},{text:" +deps | *cached* --> RUN npm install",class:"term-cached",delay:580},{text:" +test | *cached* --> COPY src test ./",class:"term-cached",delay:700},{text:" +test | *cached* --> RUN npm test",class:"term-cached",delay:820},{text:"============= \u{1F30D} Earth Build \u2705 SUCCESS [0.9s] =============",class:"term-success",delay:900}],edit:[{text:"$ earth +test",class:"term-cmd",delay:80},{text:"\u2500\u2500\u2500 Init \u{1F680} Found buildkit daemon as podman container (earth-buildkitd)",class:"term-dim",delay:180},{text:"\u2500\u2500\u2500 Build \u{1F527}",class:"term-bold",delay:260},{text:" +base | *cached* --> FROM node:26-alpine",class:"term-cached",delay:380},{text:" +base | *cached* --> WORKDIR /js-app",class:"term-cached",delay:440},{text:" +deps | *cached* --> COPY package.json package-lock.json ./",class:"term-cached",delay:500},{text:" +deps | *cached* --> RUN npm install",class:"term-cached",delay:580},{text:" +test | --> COPY src test ./",class:"term-yellow",delay:850},{text:" +test | --> RUN npm test",class:"term-yellow",delay:1400},{text:" +test | Tests: 12 passed, 12 total",class:"terminal-line",delay:1650},{text:"============= \u{1F30D} Earth Build \u2705 SUCCESS [1.8s] =============",class:"term-success",delay:1800}],cold:[{text:"$ earth +test",class:"term-cmd",delay:80},{text:"\u2500\u2500\u2500 Init \u{1F680} Found buildkit daemon as podman container (earth-buildkitd)",class:"term-dim",delay:300},{text:"\u2500\u2500\u2500 Build \u{1F527}",class:"term-bold",delay:600},{text:" +base | --> FROM node:26-alpine",class:"terminal-line",delay:1800},{text:" +base | --> WORKDIR /js-app",class:"terminal-line",delay:3e3},{text:" +deps | --> COPY package.json package-lock.json ./",class:"terminal-line",delay:4e3},{text:" +deps | --> RUN npm install",class:"term-yellow",delay:6500},{text:" +deps | added 142 packages in 4.6s",class:"terminal-line",delay:7200},{text:" +test | --> COPY src test ./",class:"terminal-line",delay:8500},{text:" +test | --> RUN npm test",class:"term-yellow",delay:10500},{text:" +test | Tests: 12 passed, 12 total",class:"terminal-line",delay:11e3},{text:"============= \u{1F30D} Earth Build \u2705 SUCCESS [11.4s] =============",class:"term-success",delay:11400}]},java:{filename:"examples/java/Earthfile",repoUrl:"https://github.com/earthbuild/earthbuild/tree/main/examples/java",metrics:{cachedTime:"1.5s",editTime:"3.4s",coldTime:"21.8s",editSpeedup:"6.4x Faster",speedup:"14.5x Faster",saved:"93% Saved (20.3s)",ciSaved:"93% Fewer Runner Mins",co2Saved:"~5.1g CO\u2082e / build"},code:`VERSION 0.8 +FROM gradle:8.12-jdk23 +WORKDIR /java-app + +# deps caches Gradle dependencies and daemon +deps: + COPY build.gradle settings.gradle ./ + RUN gradle dependencies --no-daemon + +# build compiles & tests multi-module project +build: + FROM +deps + COPY src src + RUN --mount=type=cache,target=/root/.gradle/caches \\ + gradle assemble test --no-daemon + SAVE ARTIFACT build/libs/*.jar AS LOCAL build/libs/app.jar`,cached:[{text:"$ earth +build",class:"term-cmd",delay:80},{text:"\u2500\u2500\u2500 Init \u{1F680} Found buildkit daemon as podman container (earth-buildkitd)",class:"term-dim",delay:180},{text:"\u2500\u2500\u2500 Build \u{1F527}",class:"term-bold",delay:260},{text:" +deps | *cached* --> FROM gradle:8.12-jdk23",class:"term-cached",delay:350},{text:" +deps | *cached* --> WORKDIR /java-app",class:"term-cached",delay:420},{text:" +deps | *cached* --> COPY build.gradle settings.gradle ./",class:"term-cached",delay:490},{text:" +deps | *cached* --> RUN gradle dependencies --no-daemon",class:"term-cached",delay:560},{text:" +build | *cached* --> COPY src src",class:"term-cached",delay:640},{text:" +build | *cached* --> RUN --mount=type=cache,target=/root/.gradle/caches gradle assemble test --no-daemon",class:"term-cached",delay:850},{text:" +build | *cached* --> SAVE ARTIFACT build/libs/*.jar AS LOCAL build/libs/app.jar",class:"term-cached",delay:1100},{text:" output | [----------] 100% exporting outputs",class:"terminal-line",delay:1250},{text:"\u{1F381} Artifact +build/libs/app.jar output as build/libs/app.jar",class:"term-running",delay:1380},{text:"============= \u{1F30D} Earth Build \u2705 SUCCESS [1.5s] =============",class:"term-success",delay:1500}],edit:[{text:"$ earth +build",class:"term-cmd",delay:80},{text:"\u2500\u2500\u2500 Init \u{1F680} Found buildkit daemon as podman container (earth-buildkitd)",class:"term-dim",delay:180},{text:"\u2500\u2500\u2500 Build \u{1F527}",class:"term-bold",delay:260},{text:" +deps | *cached* --> FROM gradle:8.12-jdk23",class:"term-cached",delay:350},{text:" +deps | *cached* --> WORKDIR /java-app",class:"term-cached",delay:420},{text:" +deps | *cached* --> COPY build.gradle settings.gradle ./",class:"term-cached",delay:490},{text:" +deps | *cached* --> RUN gradle dependencies --no-daemon",class:"term-cached",delay:560},{text:" +build | --> COPY src src",class:"term-yellow",delay:800},{text:" +build | --> RUN --mount=type=cache,target=/root/.gradle/caches gradle assemble test --no-daemon",class:"term-yellow",delay:1500},{text:" +build | > Task :compileJava",class:"terminal-line",delay:2200},{text:" +build | > Task :test",class:"terminal-line",delay:2800},{text:" +build | --> SAVE ARTIFACT build/libs/*.jar AS LOCAL build/libs/app.jar",class:"terminal-line",delay:3150},{text:"============= \u{1F30D} Earth Build \u2705 SUCCESS [3.4s] =============",class:"term-success",delay:3400}],cold:[{text:"$ earth +build",class:"term-cmd",delay:80},{text:"\u2500\u2500\u2500 Init \u{1F680} Found buildkit daemon as podman container (earth-buildkitd)",class:"term-dim",delay:300},{text:"\u2500\u2500\u2500 Build \u{1F527}",class:"term-bold",delay:600},{text:" +deps | --> FROM gradle:8.12-jdk23",class:"terminal-line",delay:1500},{text:" +deps | --> WORKDIR /java-app",class:"terminal-line",delay:2500},{text:" +deps | --> COPY build.gradle settings.gradle ./",class:"terminal-line",delay:3500},{text:" +deps | --> RUN gradle dependencies --no-daemon",class:"term-yellow",delay:6500},{text:" +build | --> COPY src src",class:"terminal-line",delay:9e3},{text:" +build | --> RUN --mount=type=cache,target=/root/.gradle/caches gradle assemble test --no-daemon",class:"terminal-line",delay:12e3},{text:" +build | > Task :compileJava",class:"terminal-line",delay:15e3},{text:" +build | > Task :test",class:"terminal-line",delay:17500},{text:" +build | > Task :assemble",class:"terminal-line",delay:19500},{text:" +build | --> SAVE ARTIFACT build/libs/*.jar AS LOCAL build/libs/app.jar",class:"terminal-line",delay:21e3},{text:"============= \u{1F30D} Earth Build \u2705 SUCCESS [21.8s] =============",class:"term-success",delay:21800}]}};var as=class{deckEl;coldWindow;coldLinesEl;coldScreenEl;coldCursorEl;editWindow;editLinesEl;editScreenEl;editCursorEl;cachedWindow;cachedLinesEl;cachedScreenEl;cachedCursorEl;parallelColdFill;parallelColdVal;parallelEditFill;parallelEditVal;parallelEditTime;parallelEditSpeedupBadge;parallelCachedFill;parallelCachedTime;parallelSpeedupBadge;summarySavedVal;summaryCiVal;summaryCo2Val;isPlaying=!1;timeouts=[];rafId=null;constructor(){this.deckEl=document.getElementById("stackedWindowsDeck"),this.coldWindow=document.getElementById("coldWindow"),this.coldLinesEl=document.getElementById("coldLines"),this.coldScreenEl=document.getElementById("coldScreen"),this.coldCursorEl=document.getElementById("coldCursor"),this.editWindow=document.getElementById("editWindow"),this.editLinesEl=document.getElementById("editLines"),this.editScreenEl=document.getElementById("editScreen"),this.editCursorEl=document.getElementById("editCursor"),this.cachedWindow=document.getElementById("cachedWindow"),this.cachedLinesEl=document.getElementById("cachedLines"),this.cachedScreenEl=document.getElementById("cachedScreen"),this.cachedCursorEl=document.getElementById("cachedCursor"),this.parallelColdFill=document.getElementById("parallelColdFill"),this.parallelColdVal=document.getElementById("parallelColdVal"),this.parallelEditFill=document.getElementById("parallelEditFill"),this.parallelEditVal=document.getElementById("parallelEditVal"),this.parallelEditTime=document.getElementById("parallelEditTime"),this.parallelEditSpeedupBadge=document.getElementById("parallelEditSpeedupBadge"),this.parallelCachedFill=document.getElementById("parallelCachedFill"),this.parallelCachedTime=document.getElementById("parallelCachedTime"),this.parallelSpeedupBadge=document.getElementById("parallelSpeedupBadge"),this.summarySavedVal=document.getElementById("summarySavedVal"),this.summaryCiVal=document.getElementById("summaryCiVal"),this.summaryCo2Val=document.getElementById("summaryCo2Val")}clear(){this.timeouts.forEach(s=>clearTimeout(s)),this.timeouts=[],this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null),this.coldLinesEl&&(this.coldLinesEl.innerHTML=""),this.editLinesEl&&(this.editLinesEl.innerHTML=""),this.cachedLinesEl&&(this.cachedLinesEl.innerHTML=""),this.parallelColdFill&&(this.parallelColdFill.style.width="0%"),this.parallelEditFill&&(this.parallelEditFill.style.width="0%"),this.parallelCachedFill&&(this.parallelCachedFill.style.width="0%"),this.coldWindow&&this.coldWindow.classList.remove("cold-pulse"),this.editWindow&&this.editWindow.classList.remove("edit-pulse"),this.cachedWindow&&this.cachedWindow.classList.remove("winner-pulse"),this.isPlaying=!1}showStatic(s){let n=K[s]||K.python;this.clear();let e=parseFloat(n.metrics.coldTime)||6.2,p=parseFloat(n.metrics.editTime)||1.8,A=parseFloat(n.metrics.cachedTime)||1.1,t=(e/p).toFixed(1),c=(e/A).toFixed(1),r=`${t}x Faster`,d=`${c}x Faster`,i=Math.max(0,e-A),o=Math.round(i/e*100),k=`${o}% Saved (${i.toFixed(1)}s)`,b=`${o}% Fewer Runner Mins`,S=`~${(i*.25).toFixed(1)}g CO\u2082e / build`,D=p/e*100,y=A/e*100;this.summarySavedVal&&(this.summarySavedVal.textContent=k),this.summaryCiVal&&(this.summaryCiVal.textContent=b),this.summaryCo2Val&&(this.summaryCo2Val.textContent=S),this.parallelColdVal&&(this.parallelColdVal.textContent=n.metrics.coldTime),this.parallelColdFill&&(this.parallelColdFill.style.width="100%"),this.parallelEditTime&&(this.parallelEditTime.textContent=n.metrics.editTime),this.parallelEditFill&&(this.parallelEditFill.style.width=`${D.toFixed(2)}%`),this.parallelEditSpeedupBadge&&(this.parallelEditSpeedupBadge.textContent=r),this.parallelCachedTime&&(this.parallelCachedTime.textContent=n.metrics.cachedTime),this.parallelCachedFill&&(this.parallelCachedFill.style.width=`${y.toFixed(2)}%`),this.parallelSpeedupBadge&&(this.parallelSpeedupBadge.textContent=d);let T=document.getElementById("coldTimePill"),x=document.getElementById("editTimePill"),P=document.getElementById("cachedTimePill");T&&(T.textContent=n.metrics.coldTime),x&&(x.textContent=n.metrics.editTime),P&&(P.textContent=n.metrics.cachedTime);let h=(R,C,I)=>{if(!R)return;let N=document.createDocumentFragment();I.forEach(w=>{let m=document.createElement("div");m.className=`player-terminal-line ${w.class||""}`,m.textContent=w.text,N.appendChild(m)}),R.appendChild(N),C&&(C.scrollTop=C.scrollHeight)};h(this.coldLinesEl,this.coldScreenEl,n.cold),h(this.editLinesEl,this.editScreenEl,n.edit),h(this.cachedLinesEl,this.cachedScreenEl,n.cached),this.coldWindow&&this.coldWindow.classList.add("cold-pulse"),this.editWindow&&this.editWindow.classList.add("edit-pulse"),this.cachedWindow&&this.cachedWindow.classList.add("winner-pulse")}startRace(s){let n=K[s]||K.python;this.clear();let e=parseFloat(n.metrics.coldTime)||6.2,p=parseFloat(n.metrics.editTime)||1.8,A=parseFloat(n.metrics.cachedTime)||1.1,t=(e/p).toFixed(1),c=(e/A).toFixed(1),r=`${t}x Faster`,d=`${c}x Faster`,i=Math.max(0,e-A),o=Math.round(i/e*100),k=`${o}% Saved (${i.toFixed(1)}s)`,b=`${o}% Fewer Runner Mins`,S=`~${(i*.25).toFixed(1)}g CO\u2082e / build`,D=e*1e3,y=p*1e3,T=A*1e3,x=p/e*100,P=A/e*100;this.summarySavedVal&&(this.summarySavedVal.textContent=k),this.summaryCiVal&&(this.summaryCiVal.textContent=b),this.summaryCo2Val&&(this.summaryCo2Val.textContent=S),this.parallelColdVal&&(this.parallelColdVal.textContent=`0.0s / ${n.metrics.coldTime}`),this.parallelEditTime&&(this.parallelEditTime.textContent="0.0s"),this.parallelEditSpeedupBadge&&(this.parallelEditSpeedupBadge.textContent=r),this.parallelCachedTime&&(this.parallelCachedTime.textContent="0.0s"),this.parallelSpeedupBadge&&(this.parallelSpeedupBadge.textContent=d);let h=document.getElementById("coldTimePill"),R=document.getElementById("editTimePill"),C=document.getElementById("cachedTimePill");h&&(h.textContent=n.metrics.coldTime),R&&(R.textContent=n.metrics.editTime),C&&(C.textContent=n.metrics.cachedTime),this.isPlaying=!0;let I=performance.now(),N=()=>{if(!this.isPlaying)return;let m=performance.now()-I;if(m<=T){let f=m/T,O=f*P;this.parallelCachedFill&&(this.parallelCachedFill.style.width=`${O.toFixed(2)}%`);let g=(f*A).toFixed(1);this.parallelCachedTime&&(this.parallelCachedTime.textContent=`${g}s`)}else this.parallelCachedFill&&(this.parallelCachedFill.style.width=`${P.toFixed(2)}%`),this.parallelCachedTime&&(this.parallelCachedTime.textContent=n.metrics.cachedTime),this.parallelSpeedupBadge&&(this.parallelSpeedupBadge.textContent=d);if(m<=y){let f=m/y,O=f*x;this.parallelEditFill&&(this.parallelEditFill.style.width=`${O.toFixed(2)}%`);let g=(f*p).toFixed(1);this.parallelEditTime&&(this.parallelEditTime.textContent=`${g}s`)}else this.parallelEditFill&&(this.parallelEditFill.style.width=`${x.toFixed(2)}%`),this.parallelEditTime&&(this.parallelEditTime.textContent=n.metrics.editTime),this.parallelEditSpeedupBadge&&(this.parallelEditSpeedupBadge.textContent=r);if(m<=D){let f=m/D,O=Math.min(100,f*100);this.parallelColdFill&&(this.parallelColdFill.style.width=`${O.toFixed(2)}%`);let g=(f*e).toFixed(1);this.parallelColdVal&&(this.parallelColdVal.textContent=`${g}s / ${n.metrics.coldTime}`),this.rafId=requestAnimationFrame(N)}else this.isPlaying=!1,this.parallelColdFill&&(this.parallelColdFill.style.width="100%"),this.parallelColdVal&&(this.parallelColdVal.textContent=n.metrics.coldTime)};this.rafId=requestAnimationFrame(N),n.cached.forEach((w,m)=>{let f=w.delay,O=window.setTimeout(()=>{let g=document.createElement("div");g.className=`player-terminal-line ${w.class||""}`,g.textContent=w.text,this.cachedLinesEl&&(this.cachedLinesEl.appendChild(g),this.cachedScreenEl&&(this.cachedScreenEl.scrollTop=this.cachedScreenEl.scrollHeight)),m===n.cached.length-1&&this.cachedWindow&&this.cachedWindow.classList.add("winner-pulse")},f);this.timeouts.push(O)}),n.edit.forEach((w,m)=>{let f=w.delay,O=window.setTimeout(()=>{let g=document.createElement("div");g.className=`player-terminal-line ${w.class||""}`,g.textContent=w.text,this.editLinesEl&&(this.editLinesEl.appendChild(g),this.editScreenEl&&(this.editScreenEl.scrollTop=this.editScreenEl.scrollHeight)),m===n.edit.length-1&&this.editWindow&&this.editWindow.classList.add("edit-pulse")},f);this.timeouts.push(O)}),n.cold.forEach((w,m)=>{let f=w.delay,O=window.setTimeout(()=>{let g=document.createElement("div");g.className=`player-terminal-line ${w.class||""}`,g.textContent=w.text,this.coldLinesEl&&(this.coldLinesEl.appendChild(g),this.coldScreenEl&&(this.coldScreenEl.scrollTop=this.coldScreenEl.scrollHeight)),m===n.cold.length-1&&(this.isPlaying=!1,this.coldWindow&&this.coldWindow.classList.add("cold-pulse"),this.parallelColdFill&&(this.parallelColdFill.style.width="100%"),this.parallelColdVal&&(this.parallelColdVal.textContent=n.metrics.coldTime))},f);this.timeouts.push(O)})}};var cs={python:{dockerfile:{legacyTitle:"Standard Dockerfile",legacyCode:`FROM python:3.14-slim AS deps +WORKDIR /app +COPY requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt pyinstaller + +FROM deps AS test +COPY src tests ./ ./ +RUN pytest tests/ + +FROM deps AS builder +COPY src tests ./ ./ +RUN pyinstaller --onefile --distpath dist -n app src/main.py + +FROM python:3.14-slim AS runtime +COPY --from=builder /app/dist/app /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`,earthfileTitle:"Earthfile (Direct Host Artifacts & Push Built-In)",earthfileCode:`VERSION 0.8 +FROM python:3.14-slim +WORKDIR /app + +deps: + COPY requirements.txt ./ + RUN pip install --no-cache-dir -r requirements.txt pyinstaller + +test: + FROM +deps + COPY . ./ + RUN pytest tests/ + +build: + FROM +deps + COPY . ./ + RUN pyinstaller --onefile --distpath bin -n app src/main.py + SAVE ARTIFACT bin/app AS LOCAL bin/app + +docker: + FROM python:3.14-slim + WORKDIR /app + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},bake:{legacyTitle:"Docker Buildx Bake (Bake HCL + Dockerfile)",legacyCode:`group "default" { + targets = ["test", "build", "image"] +} + +target "test" { + dockerfile = "Dockerfile" + target = "test" +} + +target "build" { + dockerfile = "Dockerfile" + target = "builder" + output = ["type=local,dest=bin"] +} + +target "image" { + dockerfile = "Dockerfile" + target = "runtime" + tags = ["docker.io/myorg/myapp:latest"] +}`,earthfileTitle:"Earthfile (Self-Contained & Deterministic)",earthfileCode:`VERSION 0.8 +FROM python:3.14-slim +WORKDIR /app + +deps: + COPY requirements.txt ./ + RUN pip install --no-cache-dir -r requirements.txt pyinstaller + +test: + FROM +deps + COPY . ./ + RUN pytest tests/ + +build: + FROM +deps + COPY . ./ + RUN pyinstaller --onefile --distpath bin -n app src/main.py + SAVE ARTIFACT bin/app AS LOCAL bin/app + +docker: + FROM python:3.14-slim + WORKDIR /app + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},makefile:{legacyTitle:"Makefile (Python Host Execution)",legacyCode:`.PHONY: all test build docker + +all: test build docker + +test: + pytest tests/ + +build: + pyinstaller --onefile --distpath dist -n app src/main.py + +docker: build + docker build -t docker.io/myorg/myapp:latest . + docker push docker.io/myorg/myapp:latest`,earthfileTitle:"Earthfile (Hermetic & Parallel)",earthfileCode:`VERSION 0.8 +FROM python:3.14-slim +WORKDIR /app + +deps: + COPY requirements.txt ./ + RUN pip install --no-cache-dir -r requirements.txt pyinstaller + +test: + FROM +deps + COPY . ./ + RUN pytest tests/ + +build: + FROM +deps + COPY . ./ + RUN pyinstaller --onefile --distpath bin -n app src/main.py + SAVE ARTIFACT bin/app AS LOCAL bin/app + +docker: + FROM python:3.14-slim + WORKDIR /app + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},dagger:{legacyTitle:"Dagger (Python SDK)",legacyCode:`import dagger +from dagger import dag, function, object_type + +@object_type +class Pipeline: + @function + async def test(self, src: dagger.Directory) -> str: + return await ( + dag.container() + .from_("python:3.14-slim") + .with_directory("/app", src) + .with_workdir("/app") + .with_exec(["pip", "install", "-r", "requirements.txt"]) + .with_exec(["pytest", "tests/"]) + .stdout() + ) + + @function + async def build(self, src: dagger.Directory) -> dagger.File: + bin_file = ( + dag.container() + .from_("python:3.14-slim") + .with_directory("/app", src) + .with_workdir("/app") + .with_exec(["pip", "install", "-r", "requirements.txt", "pyinstaller"]) + .with_exec(["pyinstaller", "--onefile", "-n", "app", "src/main.py"]) + .file("dist/app") + ) + await bin_file.export("bin/app") + return bin_file + + @function + async def publish(self, src: dagger.Directory) -> str: + bin_file = await self.build(src) + return await ( + dag.container() + .from_("python:3.14-slim") + .with_file("/usr/local/bin/app", bin_file) + .with_entrypoint(["/usr/local/bin/app"]) + .publish("docker.io/myorg/myapp:latest") + )`,earthfileTitle:"Earthfile",earthfileCode:`VERSION 0.8 +FROM python:3.14-slim +WORKDIR /app + +deps: + COPY requirements.txt ./ + RUN pip install --no-cache-dir -r requirements.txt pyinstaller + +test: + FROM +deps + COPY . ./ + RUN pytest tests/ + +build: + FROM +deps + COPY . ./ + RUN pyinstaller --onefile --distpath bin -n app src/main.py + SAVE ARTIFACT bin/app AS LOCAL bin/app + +docker: + FROM python:3.14-slim + WORKDIR /app + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},gha:{legacyTitle:"Standard GitHub Actions Workflow",legacyCode:`name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-26.04 + steps: + - uses: actions/checkout@v4 + + - name: Run Tests + run: pytest tests/ + + - name: Build Host Binary / Package + run: pyinstaller --onefile --distpath dist -n app src/main.py + + - uses: actions/upload-artifact@v4 + with: + name: app-artifact + path: dist/app + + - uses: docker/login-action@v3 + with: + username: \${{ secrets.DOCKER_USER }} + password: \${{ secrets.DOCKER_TOKEN }} + + - uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: docker.io/myorg/myapp:latest`,earthfileTitle:"Clean GitHub Action with EarthBuild (10 Lines)",earthfileCode:`name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup EarthBuild + uses: earthbuild/actions-setup@v2 + + - name: Test, Build Binary & Push Image + run: earth --ci --push +all + env: + EARTH_DOCKER_CONFIG: \${{ secrets.DOCKER_CONFIG }}`}},js:{dockerfile:{legacyTitle:"Standard Dockerfile",legacyCode:`FROM node:26-alpine AS deps +WORKDIR /app +COPY package*.json ./ +RUN npm ci + +FROM deps AS test +COPY src ./src package*.json ./ +RUN npm test + +FROM deps AS builder +COPY src ./src package*.json ./ +RUN npm run build + +FROM node:26-alpine AS runtime +COPY --from=builder /app/dist/bundle.js /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`,earthfileTitle:"Earthfile (Direct Host Artifacts & Push Built-In)",earthfileCode:`VERSION 0.8 +FROM node:26-alpine +WORKDIR /app + +deps: + COPY package*.json ./ + RUN npm ci + +test: + FROM +deps + COPY . ./ + RUN npm test + +build: + FROM +deps + COPY . ./ + RUN npm run build + SAVE ARTIFACT dist AS LOCAL dist + +docker: + FROM node:26-alpine + WORKDIR /app + COPY package*.json ./ + RUN npm ci --omit=dev + COPY +build/dist ./dist + CMD ["node", "dist/index.js"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},bake:{legacyTitle:"Docker Buildx Bake (Bake HCL + Dockerfile)",legacyCode:`group "default" { + targets = ["test", "build", "image"] +} + +target "test" { + dockerfile = "Dockerfile" + target = "test" +} + +target "build" { + dockerfile = "Dockerfile" + target = "builder" + output = ["type=local,dest=bin"] +} + +target "image" { + dockerfile = "Dockerfile" + target = "runtime" + tags = ["docker.io/myorg/myapp:latest"] +}`,earthfileTitle:"Earthfile (Self-Contained & Deterministic)",earthfileCode:`VERSION 0.8 +FROM node:26-alpine +WORKDIR /app + +deps: + COPY package*.json ./ + RUN npm ci + +test: + FROM +deps + COPY . ./ + RUN npm test + +build: + FROM +deps + COPY . ./ + RUN npm run build + SAVE ARTIFACT dist AS LOCAL dist + +docker: + FROM node:26-alpine + WORKDIR /app + COPY package*.json ./ + RUN npm ci --omit=dev + COPY +build/dist ./dist + CMD ["node", "dist/index.js"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},makefile:{legacyTitle:"Makefile (JavaScript Host Execution)",legacyCode:`.PHONY: all test build docker + +all: test build docker + +test: + npm test + +build: + npm run build + +docker: build + docker build -t docker.io/myorg/myapp:latest . + docker push docker.io/myorg/myapp:latest`,earthfileTitle:"Earthfile (Hermetic & Parallel)",earthfileCode:`VERSION 0.8 +FROM node:26-alpine +WORKDIR /app + +deps: + COPY package*.json ./ + RUN npm ci + +test: + FROM +deps + COPY . ./ + RUN npm test + +build: + FROM +deps + COPY . ./ + RUN npm run build + SAVE ARTIFACT dist AS LOCAL dist + +docker: + FROM node:26-alpine + WORKDIR /app + COPY package*.json ./ + RUN npm ci --omit=dev + COPY +build/dist ./dist + CMD ["node", "dist/index.js"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},dagger:{legacyTitle:"Dagger (TypeScript SDK)",legacyCode:`import { dag, Directory, File, object, func } from "@dagger.io/dagger"; + +@object() +export class Pipeline { + @func() + async test(src: Directory): Promise<string> { + return dag.container() + .from("node:26-alpine") + .withDirectory("/app", src) + .withWorkdir("/app") + .withExec(["npm", "ci"]) + .withExec(["npm", "test"]) + .stdout(); + } + + @func() + async build(src: Directory): Promise<File> { + const bundle = dag.container() + .from("node:26-alpine") + .withDirectory("/app", src) + .withWorkdir("/app") + .withExec(["npm", "ci"]) + .withExec(["npm", "run", "build"]) + .file("dist/index.js"); + await bundle.export("dist/index.js"); + return bundle; + } + + @func() + async publish(src: Directory): Promise<string> { + const bundle = await this.build(src); + return dag.container() + .from("node:26-alpine") + .withFile("/app/dist/index.js", bundle) + .withEntrypoint(["node", "/app/dist/index.js"]) + .publish("docker.io/myorg/myapp:latest"); + } +}`,earthfileTitle:"Earthfile",earthfileCode:`VERSION 0.8 +FROM node:26-alpine +WORKDIR /app + +deps: + COPY package*.json ./ + RUN npm ci + +test: + FROM +deps + COPY . ./ + RUN npm test + +build: + FROM +deps + COPY . ./ + RUN npm run build + SAVE ARTIFACT dist AS LOCAL dist + +docker: + FROM node:26-alpine + WORKDIR /app + COPY package*.json ./ + RUN npm ci --omit=dev + COPY +build/dist ./dist + CMD ["node", "dist/index.js"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},gha:{legacyTitle:"Standard GitHub Actions Workflow",legacyCode:`name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-26.04 + steps: + - uses: actions/checkout@v4 + + - name: Run Tests + run: npm test + + - name: Build Host Binary / Package + run: npm run build + + - uses: actions/upload-artifact@v4 + with: + name: app-artifact + path: dist/bundle.js + + - uses: docker/login-action@v3 + with: + username: \${{ secrets.DOCKER_USER }} + password: \${{ secrets.DOCKER_TOKEN }} + + - uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: docker.io/myorg/myapp:latest`,earthfileTitle:"Clean GitHub Action with EarthBuild (10 Lines)",earthfileCode:`name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup EarthBuild + uses: earthbuild/actions-setup@v2 + + - name: Test, Build Binary & Push Image + run: earth --ci --push +all + env: + EARTH_DOCKER_CONFIG: \${{ secrets.DOCKER_CONFIG }}`}},typescript:{dockerfile:{legacyTitle:"Standard Dockerfile",legacyCode:`FROM node:26-alpine AS deps +WORKDIR /app +COPY package*.json tsconfig.json ./ +RUN npm ci + +FROM deps AS test +COPY src ./src tsconfig.json ./ +RUN npm test + +FROM deps AS builder +COPY src ./src tsconfig.json ./ +RUN npx tsc --outDir dist + +FROM node:26-alpine AS runtime +COPY --from=builder /app/dist/index.js /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`,earthfileTitle:"Earthfile (Direct Host Artifacts & Push Built-In)",earthfileCode:`VERSION 0.8 +FROM node:26-alpine +WORKDIR /app + +deps: + COPY package*.json tsconfig.json ./ + RUN npm ci + +test: + FROM +deps + COPY src ./src + RUN npm test + +build: + FROM +deps + COPY src ./src + RUN npx tsc + SAVE ARTIFACT dist AS LOCAL dist + +docker: + FROM node:26-alpine + WORKDIR /app + COPY package*.json ./ + RUN npm ci --omit=dev + COPY +build/dist ./dist + CMD ["node", "dist/index.js"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},bake:{legacyTitle:"Docker Buildx Bake (Bake HCL + Dockerfile)",legacyCode:`group "default" { + targets = ["test", "build", "image"] +} + +target "test" { + dockerfile = "Dockerfile" + target = "test" +} + +target "build" { + dockerfile = "Dockerfile" + target = "builder" + output = ["type=local,dest=bin"] +} + +target "image" { + dockerfile = "Dockerfile" + target = "runtime" + tags = ["docker.io/myorg/myapp:latest"] +}`,earthfileTitle:"Earthfile (Self-Contained & Deterministic)",earthfileCode:`VERSION 0.8 +FROM node:26-alpine +WORKDIR /app + +deps: + COPY package*.json tsconfig.json ./ + RUN npm ci + +test: + FROM +deps + COPY src ./src + RUN npm test + +build: + FROM +deps + COPY src ./src + RUN npx tsc + SAVE ARTIFACT dist AS LOCAL dist + +docker: + FROM node:26-alpine + WORKDIR /app + COPY package*.json ./ + RUN npm ci --omit=dev + COPY +build/dist ./dist + CMD ["node", "dist/index.js"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},makefile:{legacyTitle:"Makefile (TypeScript Host Execution)",legacyCode:`.PHONY: all test build docker + +all: test build docker + +test: + npm test + +build: + npx tsc --outDir dist + +docker: build + docker build -t docker.io/myorg/myapp:latest . + docker push docker.io/myorg/myapp:latest`,earthfileTitle:"Earthfile (Hermetic & Parallel)",earthfileCode:`VERSION 0.8 +FROM node:26-alpine +WORKDIR /app + +deps: + COPY package*.json tsconfig.json ./ + RUN npm ci + +test: + FROM +deps + COPY src ./src + RUN npm test + +build: + FROM +deps + COPY src ./src + RUN npx tsc + SAVE ARTIFACT dist AS LOCAL dist + +docker: + FROM node:26-alpine + WORKDIR /app + COPY package*.json ./ + RUN npm ci --omit=dev + COPY +build/dist ./dist + CMD ["node", "dist/index.js"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},dagger:{legacyTitle:"Dagger (TypeScript SDK)",legacyCode:`import { dag, Directory, File, object, func } from "@dagger.io/dagger"; + +@object() +export class Pipeline { + @func() + async test(src: Directory): Promise<string> { + return dag.container() + .from("node:26-alpine") + .withDirectory("/app", src) + .withWorkdir("/app") + .withExec(["npm", "ci"]) + .withExec(["npm", "test"]) + .stdout(); + } + + @func() + async build(src: Directory): Promise<File> { + const bundle = dag.container() + .from("node:26-alpine") + .withDirectory("/app", src) + .withWorkdir("/app") + .withExec(["npm", "ci"]) + .withExec(["npm", "run", "build"]) + .file("dist/index.js"); + await bundle.export("dist/index.js"); + return bundle; + } + + @func() + async publish(src: Directory): Promise<string> { + const bundle = await this.build(src); + return dag.container() + .from("node:26-alpine") + .withFile("/app/dist/index.js", bundle) + .withEntrypoint(["node", "/app/dist/index.js"]) + .publish("docker.io/myorg/myapp:latest"); + } +}`,earthfileTitle:"Earthfile",earthfileCode:`VERSION 0.8 +FROM node:26-alpine +WORKDIR /app + +deps: + COPY package*.json tsconfig.json ./ + RUN npm ci + +test: + FROM +deps + COPY src ./src + RUN npm test + +build: + FROM +deps + COPY src ./src + RUN npx tsc + SAVE ARTIFACT dist AS LOCAL dist + +docker: + FROM node:26-alpine + WORKDIR /app + COPY package*.json ./ + RUN npm ci --omit=dev + COPY +build/dist ./dist + CMD ["node", "dist/index.js"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},gha:{legacyTitle:"Standard GitHub Actions Workflow",legacyCode:`name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-26.04 + steps: + - uses: actions/checkout@v4 + + - name: Run Tests + run: npm test + + - name: Build Host Binary / Package + run: npx tsc --outDir dist + + - uses: actions/upload-artifact@v4 + with: + name: app-artifact + path: dist/index.js + + - uses: docker/login-action@v3 + with: + username: \${{ secrets.DOCKER_USER }} + password: \${{ secrets.DOCKER_TOKEN }} + + - uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: docker.io/myorg/myapp:latest`,earthfileTitle:"Clean GitHub Action with EarthBuild (10 Lines)",earthfileCode:`name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup EarthBuild + uses: earthbuild/actions-setup@v2 + + - name: Test, Build Binary & Push Image + run: earth --ci --push +all + env: + EARTH_DOCKER_CONFIG: \${{ secrets.DOCKER_CONFIG }}`}},java:{dockerfile:{legacyTitle:"Standard Dockerfile",legacyCode:`FROM maven:3.9-eclipse-temurin-25-alpine AS deps +WORKDIR /app +COPY pom.xml ./ +RUN mvn dependency:go-offline + +FROM deps AS test +COPY src ./src pom.xml ./ +RUN mvn test + +FROM deps AS builder +COPY src ./src pom.xml ./ +RUN mvn package -DskipTests + +FROM eclipse-temurin:25-jre-alpine AS runtime +COPY --from=builder /app/target/app.jar /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`,earthfileTitle:"Earthfile (Direct Host Artifacts & Push Built-In)",earthfileCode:`VERSION 0.8 +FROM maven:3.9-eclipse-temurin-25-alpine +WORKDIR /app + +deps: + COPY pom.xml ./ + RUN mvn dependency:go-offline + +test: + FROM +deps + COPY src ./src + RUN mvn test + +build: + FROM +deps + COPY src ./src + RUN mvn package -DskipTests + SAVE ARTIFACT target/*.jar AS LOCAL bin/app.jar + +docker: + FROM eclipse-temurin:25-jre-alpine + COPY +build/bin/app.jar /app/app.jar + ENTRYPOINT ["java", "-jar", "/app/app.jar"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},bake:{legacyTitle:"Docker Buildx Bake (Bake HCL + Dockerfile)",legacyCode:`group "default" { + targets = ["test", "build", "image"] +} + +target "test" { + dockerfile = "Dockerfile" + target = "test" +} + +target "build" { + dockerfile = "Dockerfile" + target = "builder" + output = ["type=local,dest=bin"] +} + +target "image" { + dockerfile = "Dockerfile" + target = "runtime" + tags = ["docker.io/myorg/myapp:latest"] +}`,earthfileTitle:"Earthfile (Self-Contained & Deterministic)",earthfileCode:`VERSION 0.8 +FROM maven:3.9-eclipse-temurin-25-alpine +WORKDIR /app + +deps: + COPY pom.xml ./ + RUN mvn dependency:go-offline + +test: + FROM +deps + COPY src ./src + RUN mvn test + +build: + FROM +deps + COPY src ./src + RUN mvn package -DskipTests + SAVE ARTIFACT target/*.jar AS LOCAL bin/app.jar + +docker: + FROM eclipse-temurin:25-jre-alpine + COPY +build/bin/app.jar /app/app.jar + ENTRYPOINT ["java", "-jar", "/app/app.jar"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},makefile:{legacyTitle:"Makefile (Java Host Execution)",legacyCode:`.PHONY: all test build docker + +all: test build docker + +test: + mvn test + +build: + mvn package -DskipTests + +docker: build + docker build -t docker.io/myorg/myapp:latest . + docker push docker.io/myorg/myapp:latest`,earthfileTitle:"Earthfile (Hermetic & Parallel)",earthfileCode:`VERSION 0.8 +FROM maven:3.9-eclipse-temurin-25-alpine +WORKDIR /app + +deps: + COPY pom.xml ./ + RUN mvn dependency:go-offline + +test: + FROM +deps + COPY src ./src + RUN mvn test + +build: + FROM +deps + COPY src ./src + RUN mvn package -DskipTests + SAVE ARTIFACT target/*.jar AS LOCAL bin/app.jar + +docker: + FROM eclipse-temurin:25-jre-alpine + COPY +build/bin/app.jar /app/app.jar + ENTRYPOINT ["java", "-jar", "/app/app.jar"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},dagger:{legacyTitle:"Dagger (Java SDK)",legacyCode:`package io.dagger.modules.pipeline; + +import static io.dagger.client.Dagger.dag; +import io.dagger.client.Container; +import io.dagger.client.Directory; +import io.dagger.client.File; +import io.dagger.module.annotation.Function; +import io.dagger.module.annotation.Object; +import java.util.List; + +@Object +public class Pipeline { + + private Container base(Directory src) { + return dag().container() + .from("maven:3.9-eclipse-temurin-25-alpine") + .withWorkdir("/app") + .withMountedDirectory("/app", src) + .withExec(List.of("mvn", "dependency:go-offline")); + } + + @Function + public String test(Directory src) throws Exception { + return base(src) + .withExec(List.of("mvn", "test")) + .stdout(); + } + + @Function + public File build(Directory src) throws Exception { + File jar = base(src) + .withExec(List.of("mvn", "package", "-DskipTests")) + .file("target/app.jar"); + jar.export("bin/app.jar"); + return jar; + } + + @Function + public String publish(Directory src) throws Exception { + File jar = build(src); + return dag().container() + .from("eclipse-temurin:25-jre-alpine") + .withFile("/app/app.jar", jar) + .withEntrypoint(List.of("java", "-jar", "/app/app.jar")) + .publish("docker.io/myorg/myapp:latest"); + } +}`,earthfileTitle:"Earthfile",earthfileCode:`VERSION 0.8 +FROM maven:3.9-eclipse-temurin-25-alpine +WORKDIR /app + +deps: + COPY pom.xml ./ + RUN mvn dependency:go-offline + +test: + FROM +deps + COPY src ./src + RUN mvn test + +build: + FROM +deps + COPY src ./src + RUN mvn package -DskipTests + SAVE ARTIFACT target/*.jar AS LOCAL bin/app.jar + +docker: + FROM eclipse-temurin:25-jre-alpine + COPY +build/bin/app.jar /app/app.jar + ENTRYPOINT ["java", "-jar", "/app/app.jar"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},gha:{legacyTitle:"Standard GitHub Actions Workflow",legacyCode:`name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-26.04 + steps: + - uses: actions/checkout@v4 + + - name: Run Tests + run: mvn test + + - name: Build Host Binary / Package + run: mvn package -DskipTests + + - uses: actions/upload-artifact@v4 + with: + name: app-artifact + path: target/app.jar + + - uses: docker/login-action@v3 + with: + username: \${{ secrets.DOCKER_USER }} + password: \${{ secrets.DOCKER_TOKEN }} + + - uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: docker.io/myorg/myapp:latest`,earthfileTitle:"Clean GitHub Action with EarthBuild (10 Lines)",earthfileCode:`name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup EarthBuild + uses: earthbuild/actions-setup@v2 + + - name: Test, Build Binary & Push Image + run: earth --ci --push +all + env: + EARTH_DOCKER_CONFIG: \${{ secrets.DOCKER_CONFIG }}`}},cpp:{dockerfile:{legacyTitle:"Standard Dockerfile",legacyCode:`FROM alpine:3.24 AS deps +WORKDIR /app +COPY CMakeLists.txt ./ +RUN cmake -B build -G Ninja + +FROM deps AS test +COPY src ./src CMakeLists.txt ./ +RUN ctest --test-dir build --output-on-failure + +FROM deps AS builder +COPY src ./src CMakeLists.txt ./ +RUN cmake --build build --config Release + +FROM alpine:3.24 AS runtime +COPY --from=builder /app/build/bin/app /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`,earthfileTitle:"Earthfile (Direct Host Artifacts & Push Built-In)",earthfileCode:`VERSION 0.8 +FROM alpine:3.24 +RUN apk add --no-cache build-base cmake ninja +WORKDIR /app + +deps: + COPY CMakeLists.txt ./ + RUN cmake -B build -G Ninja + +test: + FROM +deps + COPY src ./src + RUN cmake --build build --target test + +build: + FROM +deps + COPY src ./src + RUN cmake --build build --config Release + SAVE ARTIFACT build/bin/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + RUN apk add --no-cache libstdc++ + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},bake:{legacyTitle:"Docker Buildx Bake (Bake HCL + Dockerfile)",legacyCode:`group "default" { + targets = ["test", "build", "image"] +} + +target "test" { + dockerfile = "Dockerfile" + target = "test" +} + +target "build" { + dockerfile = "Dockerfile" + target = "builder" + output = ["type=local,dest=bin"] +} + +target "image" { + dockerfile = "Dockerfile" + target = "runtime" + tags = ["docker.io/myorg/myapp:latest"] +}`,earthfileTitle:"Earthfile (Self-Contained & Deterministic)",earthfileCode:`VERSION 0.8 +FROM alpine:3.24 +RUN apk add --no-cache build-base cmake ninja +WORKDIR /app + +deps: + COPY CMakeLists.txt ./ + RUN cmake -B build -G Ninja + +test: + FROM +deps + COPY src ./src + RUN cmake --build build --target test + +build: + FROM +deps + COPY src ./src + RUN cmake --build build --config Release + SAVE ARTIFACT build/bin/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + RUN apk add --no-cache libstdc++ + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},makefile:{legacyTitle:"Makefile (C++ Host Execution)",legacyCode:`.PHONY: all test build docker + +all: test build docker + +test: + ctest --test-dir build --output-on-failure + +build: + cmake --build build --config Release + +docker: build + docker build -t docker.io/myorg/myapp:latest . + docker push docker.io/myorg/myapp:latest`,earthfileTitle:"Earthfile (Hermetic & Parallel)",earthfileCode:`VERSION 0.8 +FROM alpine:3.24 +RUN apk add --no-cache build-base cmake ninja +WORKDIR /app + +deps: + COPY CMakeLists.txt ./ + RUN cmake -B build -G Ninja + +test: + FROM +deps + COPY src ./src + RUN cmake --build build --target test + +build: + FROM +deps + COPY src ./src + RUN cmake --build build --config Release + SAVE ARTIFACT build/bin/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + RUN apk add --no-cache libstdc++ + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},dagger:{legacyTitle:"Dagger (Go SDK Module \u2014 No Native C++ SDK)",legacyCode:`package main + +import ( + "context" + "dagger/pipeline/internal/dagger" +) + +type Pipeline struct{} + +func (m *Pipeline) base(src *dagger.Directory) *dagger.Container { + return dag.Container(). + From("alpine:3.24"). + WithWorkdir("/app"). + WithDirectory(".", src). + WithExec([]string{"sh", "-c", "cmake -B build -G Ninja"}) +} + +func (m *Pipeline) Test(ctx context.Context, src *dagger.Directory) (string, error) { + return m.base(src).WithExec([]string{"sh", "-c", "ctest --test-dir build --output-on-failure"}).Stdout(ctx) +} + +func (m *Pipeline) Build(ctx context.Context, src *dagger.Directory) (*dagger.File, error) { + bin := m.base(src). + WithExec([]string{"sh", "-c", "cmake --build build --config Release"}). + File("build/bin/app") + + if _, err := bin.Export(ctx, "bin/app"); err != nil { + return nil, err + } + return bin, nil +} + +func (m *Pipeline) Publish(ctx context.Context, src *dagger.Directory) (string, error) { + bin, err := m.Build(ctx, src) + if err != nil { + return "", err + } + return dag.Container(). + From("alpine:3.24"). + WithFile("/usr/local/bin/app", bin). + WithEntrypoint([]string{"/usr/local/bin/app"}). + Publish(ctx, "docker.io/myorg/myapp:latest") +}`,earthfileTitle:"Earthfile",earthfileCode:`VERSION 0.8 +FROM alpine:3.24 +RUN apk add --no-cache build-base cmake ninja +WORKDIR /app + +deps: + COPY CMakeLists.txt ./ + RUN cmake -B build -G Ninja + +test: + FROM +deps + COPY src ./src + RUN cmake --build build --target test + +build: + FROM +deps + COPY src ./src + RUN cmake --build build --config Release + SAVE ARTIFACT build/bin/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + RUN apk add --no-cache libstdc++ + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},gha:{legacyTitle:"Standard GitHub Actions Workflow",legacyCode:`name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-26.04 + steps: + - uses: actions/checkout@v4 + + - name: Run Tests + run: ctest --test-dir build --output-on-failure + + - name: Build Host Binary / Package + run: cmake --build build --config Release + + - uses: actions/upload-artifact@v4 + with: + name: app-artifact + path: build/bin/app + + - uses: docker/login-action@v3 + with: + username: \${{ secrets.DOCKER_USER }} + password: \${{ secrets.DOCKER_TOKEN }} + + - uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: docker.io/myorg/myapp:latest`,earthfileTitle:"Clean GitHub Action with EarthBuild (10 Lines)",earthfileCode:`name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup EarthBuild + uses: earthbuild/actions-setup@v2 + + - name: Test, Build Binary & Push Image + run: earth --ci --push +all + env: + EARTH_DOCKER_CONFIG: \${{ secrets.DOCKER_CONFIG }}`}},go:{dockerfile:{legacyTitle:"Standard Dockerfile",legacyCode:`FROM golang:1.27-alpine3.24 AS deps +WORKDIR /app +COPY go.mod go.sum ./ +RUN go mod download + +FROM deps AS test +COPY *.go ./ +RUN go test -v ./... + +FROM deps AS builder +COPY *.go ./ +RUN CGO_ENABLED=0 go build -o bin/app . + +FROM alpine:3.24 AS runtime +COPY --from=builder /app/bin/app /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`,earthfileTitle:"Earthfile (Direct Host Artifacts & Push Built-In)",earthfileCode:`VERSION 0.8 +FROM golang:1.27-alpine3.24 +WORKDIR /app + +deps: + COPY go.mod go.sum ./ + RUN go mod download + +test: + FROM +deps + COPY *.go ./ + RUN go test -v ./... + +build: + FROM +deps + COPY *.go ./ + RUN CGO_ENABLED=0 go build -o bin/app . + SAVE ARTIFACT bin/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},bake:{legacyTitle:"Docker Buildx Bake (Bake HCL + Dockerfile)",legacyCode:`group "default" { + targets = ["test", "build", "image"] +} + +target "test" { + dockerfile = "Dockerfile" + target = "test" +} + +target "build" { + dockerfile = "Dockerfile" + target = "builder" + output = ["type=local,dest=bin"] +} + +target "image" { + dockerfile = "Dockerfile" + target = "runtime" + tags = ["docker.io/myorg/myapp:latest"] +}`,earthfileTitle:"Earthfile (Self-Contained & Deterministic)",earthfileCode:`VERSION 0.8 +FROM golang:1.27-alpine3.24 +WORKDIR /app + +deps: + COPY go.mod go.sum ./ + RUN go mod download + +test: + FROM +deps + COPY *.go ./ + RUN go test -v ./... + +build: + FROM +deps + COPY *.go ./ + RUN CGO_ENABLED=0 go build -o bin/app . + SAVE ARTIFACT bin/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},makefile:{legacyTitle:"Makefile (Go Host Execution)",legacyCode:`.PHONY: all test build docker + +all: test build docker + +test: + go test -v ./... + +build: + CGO_ENABLED=0 go build -o bin/app . + +docker: build + docker build -t docker.io/myorg/myapp:latest . + docker push docker.io/myorg/myapp:latest`,earthfileTitle:"Earthfile (Hermetic & Parallel)",earthfileCode:`VERSION 0.8 +FROM golang:1.27-alpine3.24 +WORKDIR /app + +deps: + COPY go.mod go.sum ./ + RUN go mod download + +test: + FROM +deps + COPY *.go ./ + RUN go test -v ./... + +build: + FROM +deps + COPY *.go ./ + RUN CGO_ENABLED=0 go build -o bin/app . + SAVE ARTIFACT bin/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},dagger:{legacyTitle:"Dagger (Go SDK)",legacyCode:`package main + +import ( + "context" + "dagger/pipeline/internal/dagger" +) + +type Pipeline struct{} + +func (m *Pipeline) base(src *dagger.Directory) *dagger.Container { + return dag.Container(). + From("golang:1.27-alpine3.24"). + WithWorkdir("/app"). + WithDirectory(".", src). + WithExec([]string{"sh", "-c", "go mod download"}) +} + +func (m *Pipeline) Test(ctx context.Context, src *dagger.Directory) (string, error) { + return m.base(src).WithExec([]string{"sh", "-c", "go test -v ./..."}).Stdout(ctx) +} + +func (m *Pipeline) Build(ctx context.Context, src *dagger.Directory) (*dagger.File, error) { + bin := m.base(src). + WithExec([]string{"sh", "-c", "CGO_ENABLED=0 go build -o bin/app ."}). + File("bin/app") + + if _, err := bin.Export(ctx, "bin/app"); err != nil { + return nil, err + } + return bin, nil +} + +func (m *Pipeline) Publish(ctx context.Context, src *dagger.Directory) (string, error) { + bin, err := m.Build(ctx, src) + if err != nil { + return "", err + } + return dag.Container(). + From("alpine:3.24"). + WithFile("/usr/local/bin/app", bin). + WithEntrypoint([]string{"/usr/local/bin/app"}). + Publish(ctx, "docker.io/myorg/myapp:latest") +}`,earthfileTitle:"Earthfile",earthfileCode:`VERSION 0.8 +FROM golang:1.27-alpine3.24 +WORKDIR /app + +deps: + COPY go.mod go.sum ./ + RUN go mod download + +test: + FROM +deps + COPY *.go ./ + RUN go test -v ./... + +build: + FROM +deps + COPY *.go ./ + RUN CGO_ENABLED=0 go build -o bin/app . + SAVE ARTIFACT bin/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},gha:{legacyTitle:"Standard GitHub Actions Workflow",legacyCode:`name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-26.04 + steps: + - uses: actions/checkout@v4 + + - name: Run Tests + run: go test -v ./... + + - name: Build Host Binary / Package + run: CGO_ENABLED=0 go build -o bin/app . + + - uses: actions/upload-artifact@v4 + with: + name: app-artifact + path: bin/app + + - uses: docker/login-action@v3 + with: + username: \${{ secrets.DOCKER_USER }} + password: \${{ secrets.DOCKER_TOKEN }} + + - uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: docker.io/myorg/myapp:latest`,earthfileTitle:"Clean GitHub Action with EarthBuild (10 Lines)",earthfileCode:`name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup EarthBuild + uses: earthbuild/actions-setup@v2 + + - name: Test, Build Binary & Push Image + run: earth --ci --push +all + env: + EARTH_DOCKER_CONFIG: \${{ secrets.DOCKER_CONFIG }}`}},rust:{dockerfile:{legacyTitle:"Standard Dockerfile",legacyCode:`FROM rust:1.85-alpine3.24 AS deps +WORKDIR /app +COPY Cargo.toml Cargo.lock ./ +RUN cargo fetch + +FROM deps AS test +COPY src ./src ./ +RUN cargo test + +FROM deps AS builder +COPY src ./src ./ +RUN cargo build --release + +FROM alpine:3.24 AS runtime +COPY --from=builder /app/target/release/app /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`,earthfileTitle:"Earthfile (Direct Host Artifacts & Push Built-In)",earthfileCode:`VERSION 0.8 +FROM rust:1.85-alpine3.24 +RUN apk add --no-cache musl-dev +WORKDIR /app + +deps: + COPY Cargo.toml Cargo.lock ./ + RUN cargo fetch + +test: + FROM +deps + COPY src ./src + RUN cargo test + +build: + FROM +deps + COPY src ./src + RUN cargo build --release + SAVE ARTIFACT target/release/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},bake:{legacyTitle:"Docker Buildx Bake (Bake HCL + Dockerfile)",legacyCode:`group "default" { + targets = ["test", "build", "image"] +} + +target "test" { + dockerfile = "Dockerfile" + target = "test" +} + +target "build" { + dockerfile = "Dockerfile" + target = "builder" + output = ["type=local,dest=bin"] +} + +target "image" { + dockerfile = "Dockerfile" + target = "runtime" + tags = ["docker.io/myorg/myapp:latest"] +}`,earthfileTitle:"Earthfile (Self-Contained & Deterministic)",earthfileCode:`VERSION 0.8 +FROM rust:1.85-alpine3.24 +RUN apk add --no-cache musl-dev +WORKDIR /app + +deps: + COPY Cargo.toml Cargo.lock ./ + RUN cargo fetch + +test: + FROM +deps + COPY src ./src + RUN cargo test + +build: + FROM +deps + COPY src ./src + RUN cargo build --release + SAVE ARTIFACT target/release/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},makefile:{legacyTitle:"Makefile (Rust Host Execution)",legacyCode:`.PHONY: all test build docker + +all: test build docker + +test: + cargo test + +build: + cargo build --release + +docker: build + docker build -t docker.io/myorg/myapp:latest . + docker push docker.io/myorg/myapp:latest`,earthfileTitle:"Earthfile (Hermetic & Parallel)",earthfileCode:`VERSION 0.8 +FROM rust:1.85-alpine3.24 +RUN apk add --no-cache musl-dev +WORKDIR /app + +deps: + COPY Cargo.toml Cargo.lock ./ + RUN cargo fetch + +test: + FROM +deps + COPY src ./src + RUN cargo test + +build: + FROM +deps + COPY src ./src + RUN cargo build --release + SAVE ARTIFACT target/release/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},dagger:{legacyTitle:"Dagger (Rust SDK Client)",legacyCode:`use dagger_sdk::connect; + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + let client = connect().await?; + + let base = client + .container() + .from("rust:1.85-alpine") + .with_workdir("/app") + .with_directory("/app", client.host().directory(".")) + .with_exec(vec!["cargo", "fetch"]); + + base.with_exec(vec!["cargo", "test"]).sync().await?; + + let bin = base + .with_exec(vec!["cargo", "build", "--release"]) + .file("target/release/app"); + bin.export("bin/app").await?; + + client + .container() + .from("alpine:3.24") + .with_file("/usr/local/bin/app", bin) + .with_entrypoint(vec!["/usr/local/bin/app"]) + .publish("docker.io/myorg/myapp:latest") + .await?; + + Ok(()) +}`,earthfileTitle:"Earthfile",earthfileCode:`VERSION 0.8 +FROM rust:1.85-alpine3.24 +RUN apk add --no-cache musl-dev +WORKDIR /app + +deps: + COPY Cargo.toml Cargo.lock ./ + RUN cargo fetch + +test: + FROM +deps + COPY src ./src + RUN cargo test + +build: + FROM +deps + COPY src ./src + RUN cargo build --release + SAVE ARTIFACT target/release/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},gha:{legacyTitle:"Standard GitHub Actions Workflow",legacyCode:`name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-26.04 + steps: + - uses: actions/checkout@v4 + + - name: Run Tests + run: cargo test + + - name: Build Host Binary / Package + run: cargo build --release + + - uses: actions/upload-artifact@v4 + with: + name: app-artifact + path: target/release/app + + - uses: docker/login-action@v3 + with: + username: \${{ secrets.DOCKER_USER }} + password: \${{ secrets.DOCKER_TOKEN }} + + - uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: docker.io/myorg/myapp:latest`,earthfileTitle:"Clean GitHub Action with EarthBuild (10 Lines)",earthfileCode:`name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup EarthBuild + uses: earthbuild/actions-setup@v2 + + - name: Test, Build Binary & Push Image + run: earth --ci --push +all + env: + EARTH_DOCKER_CONFIG: \${{ secrets.DOCKER_CONFIG }}`}},zig:{dockerfile:{legacyTitle:"Standard Dockerfile",legacyCode:`FROM alpine:3.24 AS deps +WORKDIR /app +COPY build.zig build.zig.zon ./ +RUN zig build --fetch + +FROM deps AS test +COPY src ./src build.zig build.zig.zon ./ +RUN zig build test + +FROM deps AS builder +COPY src ./src build.zig build.zig.zon ./ +RUN zig build -Doptimize=ReleaseSafe + +FROM alpine:3.24 AS runtime +COPY --from=builder /app/zig-out/bin/app /usr/local/bin/app +ENTRYPOINT ["/usr/local/bin/app"]`,earthfileTitle:"Earthfile (Direct Host Artifacts & Push Built-In)",earthfileCode:`VERSION 0.8 +FROM alpine:3.24 +RUN apk add --no-cache zig +WORKDIR /app + +deps: + COPY build.zig build.zig.zon ./ + RUN zig build --fetch + +test: + FROM +deps + COPY src ./src + RUN zig build test + +build: + FROM +deps + COPY src ./src + RUN zig build -Doptimize=ReleaseSafe + SAVE ARTIFACT zig-out/bin/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},bake:{legacyTitle:"Docker Buildx Bake (Bake HCL + Dockerfile)",legacyCode:`group "default" { + targets = ["test", "build", "image"] +} + +target "test" { + dockerfile = "Dockerfile" + target = "test" +} + +target "build" { + dockerfile = "Dockerfile" + target = "builder" + output = ["type=local,dest=bin"] +} + +target "image" { + dockerfile = "Dockerfile" + target = "runtime" + tags = ["docker.io/myorg/myapp:latest"] +}`,earthfileTitle:"Earthfile (Self-Contained & Deterministic)",earthfileCode:`VERSION 0.8 +FROM alpine:3.24 +RUN apk add --no-cache zig +WORKDIR /app + +deps: + COPY build.zig build.zig.zon ./ + RUN zig build --fetch + +test: + FROM +deps + COPY src ./src + RUN zig build test + +build: + FROM +deps + COPY src ./src + RUN zig build -Doptimize=ReleaseSafe + SAVE ARTIFACT zig-out/bin/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},makefile:{legacyTitle:"Makefile (Zig Host Execution)",legacyCode:`.PHONY: all test build docker + +all: test build docker + +test: + zig build test + +build: + zig build -Doptimize=ReleaseSafe + +docker: build + docker build -t docker.io/myorg/myapp:latest . + docker push docker.io/myorg/myapp:latest`,earthfileTitle:"Earthfile (Hermetic & Parallel)",earthfileCode:`VERSION 0.8 +FROM alpine:3.24 +RUN apk add --no-cache zig +WORKDIR /app + +deps: + COPY build.zig build.zig.zon ./ + RUN zig build --fetch + +test: + FROM +deps + COPY src ./src + RUN zig build test + +build: + FROM +deps + COPY src ./src + RUN zig build -Doptimize=ReleaseSafe + SAVE ARTIFACT zig-out/bin/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},dagger:{legacyTitle:"Dagger (Go SDK Module \u2014 No Native Zig SDK)",legacyCode:`package main + +import ( + "context" + "dagger/pipeline/internal/dagger" +) + +type Pipeline struct{} + +func (m *Pipeline) base(src *dagger.Directory) *dagger.Container { + return dag.Container(). + From("alpine:3.24"). + WithWorkdir("/app"). + WithDirectory(".", src). + WithExec([]string{"sh", "-c", "zig build --fetch"}) +} + +func (m *Pipeline) Test(ctx context.Context, src *dagger.Directory) (string, error) { + return m.base(src).WithExec([]string{"sh", "-c", "zig build test"}).Stdout(ctx) +} + +func (m *Pipeline) Build(ctx context.Context, src *dagger.Directory) (*dagger.File, error) { + bin := m.base(src). + WithExec([]string{"sh", "-c", "zig build -Doptimize=ReleaseSafe"}). + File("zig-out/bin/app") + + if _, err := bin.Export(ctx, "bin/app"); err != nil { + return nil, err + } + return bin, nil +} + +func (m *Pipeline) Publish(ctx context.Context, src *dagger.Directory) (string, error) { + bin, err := m.Build(ctx, src) + if err != nil { + return "", err + } + return dag.Container(). + From("alpine:3.24"). + WithFile("/usr/local/bin/app", bin). + WithEntrypoint([]string{"/usr/local/bin/app"}). + Publish(ctx, "docker.io/myorg/myapp:latest") +}`,earthfileTitle:"Earthfile",earthfileCode:`VERSION 0.8 +FROM alpine:3.24 +RUN apk add --no-cache zig +WORKDIR /app + +deps: + COPY build.zig build.zig.zon ./ + RUN zig build --fetch + +test: + FROM +deps + COPY src ./src + RUN zig build test + +build: + FROM +deps + COPY src ./src + RUN zig build -Doptimize=ReleaseSafe + SAVE ARTIFACT zig-out/bin/app AS LOCAL bin/app + +docker: + FROM alpine:3.24 + COPY +build/bin/app /usr/local/bin/app + ENTRYPOINT ["/usr/local/bin/app"] + SAVE IMAGE --push docker.io/myorg/myapp:latest + +all: + BUILD +test + BUILD +build + BUILD +docker`},gha:{legacyTitle:"Standard GitHub Actions Workflow",legacyCode:`name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-26.04 + steps: + - uses: actions/checkout@v4 + + - name: Run Tests + run: zig build test + + - name: Build Host Binary / Package + run: zig build -Doptimize=ReleaseSafe + + - uses: actions/upload-artifact@v4 + with: + name: app-artifact + path: zig-out/bin/app + + - uses: docker/login-action@v3 + with: + username: \${{ secrets.DOCKER_USER }} + password: \${{ secrets.DOCKER_TOKEN }} + + - uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: docker.io/myorg/myapp:latest`,earthfileTitle:"Clean GitHub Action with EarthBuild (10 Lines)",earthfileCode:`name: CI & Release +on: [push] + +jobs: + pipeline: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup EarthBuild + uses: earthbuild/actions-setup@v2 + + - name: Test, Build Binary & Push Image + run: earth --ci --push +all + env: + EARTH_DOCKER_CONFIG: \${{ secrets.DOCKER_CONFIG }}`}}};var Q="go",ss="dockerfile";function ns(a,s){let n=cs[a]?.[s];if(!n)return;let e=`mig-${s}`,p=document.getElementById(e);if(!p)return;let A=p.querySelector(".mig-legacy-title"),t=p.querySelector(".mig-legacy-code"),c=p.querySelector(".mig-earthfile-title"),r=p.querySelector(".mig-earthfile-code");A&&(A.textContent=n.legacyTitle),t&&(t.innerHTML=n.legacyCode),c&&(c.textContent=n.earthfileTitle),r&&(r.innerHTML=n.earthfileCode)}function Es(a){Q=a,document.querySelectorAll("#migLangTabs .race-tab-btn").forEach(n=>{n.classList.toggle("active",n.getAttribute("data-lang")===a)}),ns(Q,ss)}function xs(){let a=document.querySelectorAll(".matrix-filter-btn"),s=document.querySelectorAll(".comparison-table tbody tr");a.forEach(n=>{n.addEventListener("click",()=>{a.forEach(p=>p.classList.remove("active")),n.classList.add("active");let e=n.getAttribute("data-filter")||n.getAttribute("data-category");s.forEach(p=>{let A=p.getAttribute("data-category");e==="all"||A===e?p.style.display="":p.style.display="none"})})})}function Os(){let a=document.querySelectorAll("#migLangTabs .race-tab-btn"),s=document.querySelectorAll(".migration-tab-btn"),n=document.querySelector(".migration-container");a.forEach(e=>{e.addEventListener("click",()=>{a.forEach(A=>A.classList.remove("active")),e.classList.add("active");let p=e.getAttribute("data-lang");!p||!cs[p]||(Q=p,ns(Q,ss))})}),s.forEach(e=>{e.addEventListener("click",()=>{s.forEach(c=>c.classList.remove("active")),e.classList.add("active");let p=e.getAttribute("data-target");if(!p)return;ss=p.replace("mig-","");let A=document.getElementById(p);if(!A&&n){let c=document.getElementById(`tmpl-${p}`);if(c){let r=c.content.cloneNode(!0);n.appendChild(r),A=document.getElementById(p)}}document.querySelectorAll(".migration-panel").forEach(c=>{c.id===p?c.style.display="grid":c.style.display="none"}),ns(Q,ss)})}),ns(Q,ss)}function vs(a){let s=document.querySelectorAll("#raceLangTabs .race-tab-btn"),n=document.getElementById("raceCodePane"),e=document.getElementById("raceFileLink"),p=document.getElementById("langNavSentinel"),A=document.getElementById("raceLangNavbar");if(p&&A){let d=()=>{let o=p.getBoundingClientRect().top<=20;A.classList.toggle("is-stuck",o)};window.addEventListener("scroll",d,{passive:!0}),window.addEventListener("resize",d,{passive:!0}),d()}let t=document.getElementById("stackedWindowsDeck"),c=document.querySelectorAll(".cr-feature-card, .stacked-terminal-window");function r(d){t&&(t.setAttribute("data-front",d),c.forEach(i=>{let o=i.getAttribute("data-window")===d;i.classList.toggle("is-front",o),i.setAttribute("data-active",o?"true":"false"),i.setAttribute("aria-selected",o?"true":"false");let k=i.querySelector(".cr-card-window-content");k&&k.setAttribute("aria-hidden",o?"false":"true")}))}c.forEach(d=>{let i=d.getAttribute("data-window");if(!i)return;let o=d.querySelector(".cr-card-tab-header, .stacked-window-header");d.addEventListener("click",()=>{r(i)}),o&&o.addEventListener("click",k=>{k.stopPropagation(),r(i)}),d.addEventListener("focus",()=>{r(i)})}),s.forEach(d=>{d.addEventListener("click",()=>{s.forEach(o=>o.classList.remove("active")),d.classList.add("active");let i=d.getAttribute("data-lang");!i||!K[i]||(e&&(e.innerHTML=`view code \u2197`),n&&(n.innerHTML=`${K[i].code}`),a.startRace(i),Es(i))})}),r("repeat"),a.showStatic("python")}var ps=null;function os(a){let s=document.getElementById("copyToast");s||(s=document.createElement("div"),s.id="copyToast",s.className="copy-toast",s.setAttribute("role","status"),s.setAttribute("aria-live","polite"),s.innerHTML=` + + + `,document.body.appendChild(s));let n=s.querySelector(".copy-toast-msg");n&&(n.textContent=a),s.classList.add("show"),ps!==null&&window.clearTimeout(ps),ps=window.setTimeout(()=>{s?.classList.remove("show"),ps=null},2200)}function ds(a,s=!0){a instanceof HTMLDetailsElement&&(a.open=!0);let n=a.closest("details");n&&(n.open=!0);let p=(a.querySelector(".section-header")||a).getBoundingClientRect().top+window.pageYOffset-32;window.scrollTo({top:Math.max(0,p),behavior:s?"smooth":"auto"})}function rs(a,s){let n=document.createElement("a");return n.href=`#${a}`,n.className="heading-anchor-btn",n.setAttribute("data-anchor-id",a),n.setAttribute("aria-label",`Copy link to "${s}"`),n.setAttribute("title",`Copy link to "${s}"`),n.innerHTML=` + + + `,n.addEventListener("click",async e=>{if(e.metaKey||e.ctrlKey||e.button===1)return;e.preventDefault();let p=window.location.search||"",A=`${window.location.origin}${window.location.pathname}${p}#${a}`;try{if(navigator.clipboard&&navigator.clipboard.writeText)await navigator.clipboard.writeText(A);else{let c=document.createElement("textarea");c.value=A,c.style.position="fixed",c.style.opacity="0",document.body.appendChild(c),c.select(),document.execCommand("copy"),document.body.removeChild(c)}window.history.pushState(null,"",`${p}#${a}`);let t=document.getElementById(a);t&&(ds(t,!0),t.classList.add("anchor-highlight"),setTimeout(()=>t.classList.remove("anchor-highlight"),1500)),n.classList.add("copied"),os("Link copied to clipboard!"),setTimeout(()=>{n.classList.remove("copied")},2e3)}catch(t){console.error("Failed to copy link to clipboard:",t)}}),n}function is(a,s){let n=document.createElement("span");n.className="heading-anchor-holder",n.appendChild(s),a.appendChild(n)}function Ss(){document.querySelectorAll("section[id]").forEach(p=>{let A=p.id;if(!A)return;let t=p.querySelector(".section-title");if(t&&!t.querySelector(".heading-anchor-btn")){let c=t.textContent?.trim().replace(/\s+/g," ")||A,r=rs(A,c);is(t,r)}}),document.querySelectorAll(".doc-content h2[id], .doc-content h3[id]").forEach(p=>{let A=p.id;if(!A||p.querySelector(".heading-anchor-btn"))return;let t=p.textContent?.trim().replace(/\s+/g," ")||A,c=rs(A,t);is(p,c)}),document.querySelectorAll(".faq-item[id]").forEach(p=>{let A=p.id;if(!A)return;let t=p.querySelector(".faq-summary");if(t&&!t.querySelector(".heading-anchor-btn")){let c=t.textContent?.trim().replace(/\s+/g," ")||A,r=rs(A,c),d=document.createElement("span");for(d.className="faq-question-text";t.firstChild;)d.appendChild(t.firstChild);is(d,r),t.appendChild(d)}}),document.addEventListener("click",p=>{let A=p.target?.closest('a[href*="#"]');if(!A||A.classList.contains("heading-anchor-btn")||p.metaKey||p.ctrlKey||p.button===1||A.target==="_blank")return;let t=A.getAttribute("href");if(!t)return;let c="";if(t.startsWith("#")?c=t.substring(1):t.startsWith("/#")&&(window.location.pathname==="/"||window.location.pathname==="")&&(c=t.substring(2)),c){let r=document.getElementById(c);r&&(p.preventDefault(),window.history.pushState(null,"",`#${c}`),ds(r,!0),r.classList.add("anchor-highlight"),setTimeout(()=>r.classList.remove("anchor-highlight"),1500))}});let e=(p=!0)=>{if(window.location.hash){let A=window.location.hash.substring(1);A.includes("?")&&(A=A.split("?")[0]);let t=document.getElementById(A);t&&(ds(t,p),setTimeout(()=>{t.classList.add("anchor-highlight"),setTimeout(()=>t.classList.remove("anchor-highlight"),1800)},p?250:0))}};window.addEventListener("hashchange",()=>e(!0)),window.location.hash&&(document.readyState==="complete"?setTimeout(()=>e(!1),50):window.addEventListener("load",()=>{setTimeout(()=>e(!1),50)}))}function js(){let a=new URLSearchParams(window.location.search),s=a.get("team")||a.get("teamSize"),n=a.get("builds")||a.get("buildsPerDay"),e=a.get("mins")||a.get("minutesSaved");if((!s||!n||!e)&&window.location.hash.includes("?")){let A=window.location.hash.split("?")[1],t=new URLSearchParams(A);s||(s=t.get("team")||t.get("teamSize")),n||(n=t.get("builds")||t.get("buildsPerDay")),e||(e=t.get("mins")||t.get("minutesSaved"))}let p=A=>{if(!A)return;let t=parseInt(A,10);return isNaN(t)?void 0:t};return{team:p(s),builds:p(n),mins:p(e)}}function Ts(a,s,n){let e=new URL(window.location.href);return e.searchParams.set("team",String(a)),e.searchParams.set("builds",String(s)),e.searchParams.set("mins",String(n)),e.hash="calculator",e.toString()}function Ps(){let a=document.getElementById("roiTeamSize"),s=document.getElementById("roiBuildsPerDay"),n=document.getElementById("roiMinutesSaved"),e=document.getElementById("roiTeamSizeVal"),p=document.getElementById("roiBuildsPerDayVal"),A=document.getElementById("roiMinutesSavedVal"),t=document.getElementById("roiHoursSaved"),c=document.getElementById("roiHoursPerDev"),r=document.getElementById("roiRunnerMinutesSaved"),d=document.getElementById("roiVcpuHours"),i=document.getElementById("roiCo2Saved"),o=document.getElementById("roiCo2Equivalent"),k=document.getElementById("roiCopyShareBtn"),b=js();if(a&&b.team!==void 0){let y=Math.max(1,Math.min(100,b.team));a.value=String(y)}if(s&&b.builds!==void 0){let y=Math.max(1,Math.min(25,b.builds));s.value=String(y)}if(n&&b.mins!==void 0){let y=Math.max(2,Math.min(30,b.mins));n.value=String(y)}let S=null;function D(y=!0){let T=parseInt(a?.value||"20",10),x=parseInt(s?.value||"8",10),P=parseInt(n?.value||"12",10);e&&(e.innerText=`${T} devs`),p&&(p.innerText=`${x} builds/day`),A&&(A.innerText=`${P} mins`);let R=T*x*21*P,C=Math.round(R/60),I=T>0?Math.round(C/T):0,N=Math.round(R*2/60),w=Math.round(R*15/1e3),m=Math.round(w*2);t&&(t.innerText=`${C.toLocaleString()} hrs`),c&&(c.innerText=`\u2248 ~${I.toLocaleString()} hrs saved / dev / month`),r&&(r.innerText=`${R.toLocaleString()} mins`),d&&(d.innerText=`\u2248 ${N.toLocaleString()} vCPU-hrs (2-vCPU runner baseline)`),i&&(w>=1e3?i.innerText=`${(w/1e3).toFixed(1)} t CO\u2082e`:i.innerText=`${w.toLocaleString()} kg CO\u2082e`),o&&(m>=1e3?o.innerText=`\u2248 ~${(m/1e3).toFixed(1)} MWh data center energy spared`:o.innerText=`\u2248 ~${m.toLocaleString()} kWh data center energy spared`),y&&(S!==null&&window.clearTimeout(S),S=window.setTimeout(()=>{let f=Ts(T,x,P);window.history.replaceState(null,"",f)},100))}[a,s,n].forEach(y=>{y?.addEventListener("input",()=>D(!0))}),k&&k.addEventListener("click",async()=>{let y=parseInt(a?.value||"20",10),T=parseInt(s?.value||"8",10),x=parseInt(n?.value||"12",10),P=Ts(y,T,x);try{if(navigator.clipboard&&navigator.clipboard.writeText)await navigator.clipboard.writeText(P);else{let I=document.createElement("textarea");I.value=P,I.style.position="fixed",I.style.opacity="0",document.body.appendChild(I),I.select(),document.execCommand("copy"),document.body.removeChild(I)}window.history.replaceState(null,"",P);let h=k.querySelector(".roi-copy-btn-text"),R=k.querySelector(".roi-btn-link-icon"),C=k.querySelector(".roi-btn-check-icon");k.classList.add("copied"),h&&(h.textContent="Link Copied!"),R&&(R.style.display="none"),C&&(C.style.display="inline-block"),os(`Calculator link (${y} devs, ${x}m saved) copied!`),setTimeout(()=>{k.classList.remove("copied"),h&&(h.textContent="Share Calculations"),R&&(R.style.display="inline-block"),C&&(C.style.display="none")},2200)}catch(h){console.error("Failed to copy calculator link:",h)}}),D(!!(b.team||b.builds||b.mins))}var v={brightBlue:879020,mediumBlue:2863871,brightGreen:3978097,darkGreen:2263842,brickYellow:14667423,darkStoneGrey:6583435,white:16777215},Vs="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//gAAD//4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//////Hf///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/////////////AAAAAAAAAAAAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAB/////////////wAAAAX/8AAP8AAAAAAfwAAAAAAAAAAAAAAAAAAAAAAAAAOB////g////////AAAAD//wAAAAAAAAAAH/gAAAAAAAAAAAAAAAAAAAAAAAeP3vv/7////////+AAAAB//AAAAAAAAAAAAf8AAAAAAAAAAAAAAAAAAAAAA8+AL4f/g////////+AAAAAfvgAAAAAAAAAAAF+AAAAAAAAAAAAAAAAAAAAAH/3R////gP////////AAAAAHAAAAAAAf4AAAHP//AAAA8AAAAAAAAAAAAAAACP/5///8AAAf/////+AAAAAAAAAAAAf+AAAH////gAAD//gAAAAAAAAAAAAAf4+AN//+AAAP/////+AAAAAAAAAAAAeAAAAP///4AAAA/nAAAAAAAAAAAAAAf/B8///3wAAH/////8AAAAAAAAAAAB4AEAP//////H4AfAAAAAAAAAAAAAAA//+5/9//4AAD/////wAAAAAAAAAAAHwAHt////////8Af/AAAAA///////////////////////////////////////////////////////////+wAA//+gAOYf//1+///+AB/////4AAAAAAH/wAAB8A/v/////////////8AIDAAD///+P/////v/j///AB////+AAAAAAAf//gAAA+/////////////////P+////////////////////////////////+AAAP///wMYAAAAAAAAAAAAAAAQAB///////////////////////////////4AAAD/4AAAcAAAAAAAAAAAAAAAAATP//////////////////////////////wBAH/7AAABwAAAAAAAAAAAAAAAAA8f//////////////////////////////gHwD8AAAAAAAAAAAAAAAAAAAAAAA////////////////////////////////////////////////////////////APH////////////PcB/wAP/gAAfgAAAP/3/////////////////////////+ACP////////////A///gAD/AAAAAAAB//H//////////////////////////AAf///////////wAG/5AAD/AAAAAAAD//H/////////////////////////gAA////////////wAA/4QAB+AAAAAAAB//n//////////////////////D/+AAAb/zwH///////wAA/44AAAAAAAAAAB//D//////////////////////D6IAAAAv4AA///////4AB//8AAAAAAAAA4B5/B///////////////////4AYPgAAAAAH8AAD///////AAf/8AAAAAAAAA+AP+H///////////////////gAA/wAAAAAfQAAB///////wAf/+AAAAAAAAB8AO8H///////////////////AAB/wAAAAD8AAAA////////x///gAAAAAAAD8AP4H//////////////////+AAB/AAAAAGAAAAD////////z///4AAAAAAAH/AP////////////////////+yAB/gAAAAAAAAADP///////x///+AAAAAAAPfj//////////////////////+AB+AAAAAAAAAABv///////5///+AAAAAAAPfz///////////////////////AA4AAAAAAAAAAAD///////////+AAAAAAAM/////////////////////////AA4AAAAAAAAAAAH///////////MAAAAAAAA////////////////////////7AAAAAAAAAAAAAAH//////////+fgAAAAAAAj///////////////////////7AAAAAAAAAAAAAAA//////////w/wAAAAAAAf///////////////////////+gAAAAAAAAAAAAAAf/////////x/wAAAAAAAf////////7//////////////yAAAAAAAAAAAAAAAf/////////9DgAAAAAAAD////////g//////////////nAAAAAAAAAAAAAAAP//////////gAAAAAAAAD/////P/+A/////////////+GAAAAAAAAAAAAAAAP/////////4AAAAAAAAAD/////Hf8D/////////////8HQAAAAAAAAAAAAAAf////////5gAAAAAAAAP//3z/+AH+D/////////////8PwAAAAAAAAAAAAAAf////////wAAAAAAAAAH/4P5/8HB+A////////////+wfgAAAAAAAAAAAAAAf////////4AAAAAAAAAP/8M+f//n/Af///////////8AdAAAAAAAAAAAAAAAf////////gAAAAAAAAAH/gMP/////gf///////////8AcAAAAAAAAAAAAAAAP///////8AAAAAAAAAAP/AMH/P///g///////////fwAMAAAAAAAAAAAAAAAP///////8AAAAAAAAAAP/gI/Pv///A//////////+T4AYAAAAAAAAAAAAAAAH///////4AAAAAAAAAAH/A3+Hn///g///////////78C4AAAAAAAAAAAAAAAD///////4AAAAAAAAAAH8//MHH///w///////////48D4AAAAAAAAAAAAAAAB///////4AAAAAAAAAAB//+AB4P//////////////A8/4AAAAAAAAAAAAAAAB///////wAAAAAAAAAAB//+AAgP//////////////g7/gAAAAAAAAAAAAAAAAf//////AAAAAAAAAAAH///AAAB//////////////gH8AAAAAAAAAAAAAAAAAP/////8AAAAAAAAAAAP///8PAD//////////////wHwAAAAAAAAAAAAAAAAAH/////4AAAAAAAAAAAP///+f/7//////////////wDAAAAAAAAAAAAAAAAAAH/////4AAAAAAAAAAAP/////////////////////wAAAAAAAAAAAAAAAAAAAD////i8AAAAAAAAAAAf/////////////////////4AAAAAAAAAAAAAAAAAAAB///gAcAAAAAAAAAAB//////////v///////////wAAAAAAAAAAAAAAAAAAAD//+AAcgAAAAAAAAAD//////////3///////////gAAAAAAAAAAAAAAAAAAAA7/+AAdwAAAAAAAAAH///////+//9///////////AAAAAAAAAAAAAAAAAAAAAf/+AANgAAAAAAAAAH///////+//8f//////////QAAAAAAAAAAAAAAAAAAAAc/+AABgAAAAAAAAAP///////+f//4Af///////+wAAAAAAAAAAAAAAAAAAAAGf+AAcgAAAAAAAAAf////////P//+Af///////8wAAAAAAAAAAAAIAAAAAAAGf+AB/gAAAAAAAAAf////////n///AH///////wwAAAAAAAAAAAAPAAAAAAAAP+B9H4AAAAAAAAA/////////3///AD///v//8AgAAAAAAAAAAAABgAAAAAAAP+B8B8AAAAAAAAAf////////n//+AD//8P/+YAAAAAAAAAAAAAABwAAAAAAAP/B4A/4AAAAAAAAf////////z//8AA//wH/88AAAAAAAAAAAAAAAgAAAAAAAD//4B3/gAAAAAAAf////////x//8AAf/AD/+4A4AAAAAAAAAAAAAAAAAAAAAB//wAwjgAAAAAAAf////////5//4AAf/AD/+AA4AAAAAAAAAAAAAAAAAAAAAAP/0AAAAAAAAAAAf////////4//gAAf+AD//gB4AAAAAAAAAAAAAAAAAAAAAADP/gAAAAAAAAAAf////////8/+AAAf4ADf/gBwAAAAAAAAAAAAAAAAAAAAAAAH/gAAAAAAAAAA///////////wAAAP4AAf/gA4AAAAAAAAAAAAAAAAAAAAAAAD/gAAAAAAAAAAf//////////gAAAP4AAP/wA8AAAAAAAAAAAAAAAAAAAAAAAAPgAoAAAAAAAAf/////////8MAAAPwAAP/wAzAAAAAAAAAAAAAAAAAAAAAAAAHgH8AAAAAAAAf/////////x8AAAHwAAMfwBfAAAAAAAAAAAAAAAAAAAAAAAADgP//AAAAAAAH//////////8AAADwAAMfADfAAAAAAAAAAAAAAAAAAAAAAAAD/f//AAAAAAAH//////////4AAAD4AAMOAGNgAAAAAAAAAAAAAAAAAAAAAAAA////gAAAAAAD//////////4AAAD8AAOEAEPgAAAAAAAAAAAAAAAAAAAAAAAAN///wAAAAAAB//////////wAAABcAAOAAAfgAAAAAAAAAAAAAAAAAAAAAAAAA///6AAAAAAA//////////wAAAAcAAHgAMPgAAAAAAAAAAAAAAAAAAAAAAAAA////wAAAAAAP/x///////gAAAAIABzgAfBAAAAAAAAAAAAAAAAAAAAAAAAAA////4AAAAAAPuB///////AAAAAAAB7wA+AAAAAAAAAAAAAAAAAAAAAAAAAAA////4AAAAAAAAAP/////+AAAAAAAA/wD8AAAAAAAAAAAAAAAAAAAAAAAAAAB////4AAAAAAAAAH/////+AAAAAAAAfwX+AQAAAAAAAAAAAAAAAAAAAAAAAAB////+AAAAAAAAAH/////4AAAAAAAAP4f8xYAAAAAAAAAAAAAAAAAAAAAAAAH////8AAAAAAAAAH/////wAAAAAAAAHw//+YAAAAAAAAAAAAAAAAAAAAAAAAH/////wAAAAAAAAP/////gAAAAAAAADwf98bwAAAAAAAAAAAAAAAAAAAAAAAH/////8AAAAAAAAP/////AAAAAAAAAD4f58D5gAAAAAAAAAAAAAAAAAAAAAAH//////4AAAAAAAH////+AAAAAAAAAD8P7wN78AwAAAAAAAAAAAAAAAAAAAAH//////8AAAAAAAB////+AAAAAAAAAA+H55+//gcAAAAAAAAAAAAAAAAAAAAP//////+AAAAAAAB////8AAAAAAAAAA8Ap8A3/wcAAAAAAAAAAAAAAAAAAAAH///////gAAAAAAB////4AAAAAAAAAAfABsAJ/zzAAAAAAAAAAAAAAAAAAAAP///////wAAAAAAA////8AAAAAAAAAAH+AAAI//hgAAAAAAAAAAAAAAAAAAAD///////wAAAAAAA////8AAAAAAAAAAD/gAAA/8A8AAAAAAAAAAAAAAAAAAAD///////gAAAAAAA////8AAAAAAAAAAAP37wB/OAHAAAAAAAAAAAAAAAAAAAB///////gAAAAAAA////8AAAAAAAAAAAAPnAAGHgHAAAAAAAAAAAAAAAAAAAB///////AAAAAAAAf///+AAAAAAAAAAAAAmAACHgBgAAAAAAAAAAAAAAAAAAA//////+AAAAAAAAf///+AAAAAAAAAAAAAAB+HAAAAAAAAAAAAAAAAAAAAAAA//////+AAAAAAAAf///+AwAAAAAAAAAAAAD+HAAAAAAAAAAAAAAAAAAAAAAAf/////8AAAAAAAA////+B4AAAAAAAAAAAA3+HAAAAAAAAAAAAAAAAAAAAAAAf/////8AAAAAAAA////+B4AAAAAAAAAAAB/8HwAADAAAAAAAAAAAAAAAAAAAP/////8AAAAAAAB////+H4AAAAAAAAAAAD//HwAADAA////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAA/////4AAAAAAAB////4fwAAAAAAAAAAAP///4AAAAGAAAAAAAAAAAAAAAAAAf////4AAAAAAAB////gPwAAAAAAAAAAAf///4AAAAGAAAAAAAAAAAAAAAAAAf////4AAAAAAAA////APwAAAAAAAAAAB////+AAAAAAAAAAAAAAAAAAAAAAAf////wAAAAAAAAf//+AfgAAAAAAAAAAP////+AAMAAAAAAAAAAAAAAAAAAAAf////wAAAAAAAAf///AfAAAAAAAAAAA//////AAOAAAAAAAAAAAAAAAAAAAAf////gAAAAAAAAP///AfAAAAAAAAAAB//////gADAAAAAAAAAAAAAAAAAAAAf///8AAAAAAAAAP///AfAAAAAAAAAAB//////gAAAAAAAAAAAAAAAAAAAAAAf///wAAAAAAAAAP///AfAAAAAAAAAAB//////4AAAAAAAAAAAAAAAAAAAAAAf///AAAAAAAAAAP//8AOAAAAAAAAAAB//////4AAAAAAAAAAAAAAAAAAAAAAf///AAAAAAAAAAP//4AAAAAAAAAAAAB//////8AAAAAAAAAAAAAAAAAAAAAAf///AAAAAAAAAAH//4AAAAAAAAAAAAA//////8AAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAD//4AAAAAAAAAAAAA//////8AAAAAAAAAAAAAAAAAAAAAA///+AAAAAAAAAAB//wAAAAAAAAAAAAAf/////8AAAAAAAAAAAAAAAAAAAAAA///8AAAAAAAAAAB//gAAAAAAAAAAAAA//////8AAAAAAAAAAAAAAAAAAAAAA///4AAAAAAAAAAA//gAAAAAAAAAAAAAf/////4AAAAAAAAAAAAAAAAAAAAAA///wAAAAAAAAAAB/+AAAAAAAAAAAAAAf/w///4AAAAAAAAAAAAAAAAAAAAAA///wAAAAAAAAAAA/8AAAAAAAAAAAAAAf+AP//wAAAAAAAAAAAAAAAAAAAAAA///gAAAAAAAAAAA8AAAAAAAAAAAAAAAfIAH//wAADAAAAAAAAAAAAAAAAAAB//4AAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAD//gAABgAAAAAAAAAAAAAAAAAB//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/gAABwAAAAAAAAAAAAAAAAAD//4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/AAAA+AAAAAAAAAAAAAAAAAD//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/8AAD/AAAAAAAAAAAAAAAAAP//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//gAf/wAAAAAAAAAAAAAAAAAP//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//wAf/wAAAAAAAAAAAAAAAAAP//4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//gAP/wAAAAAAAAAAAAAAAAAP//4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//gAP/wAAAAAAAAAAAAAAAAAP//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//wAP/wAAAAAAAAAAAAAAAAAP//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//wAP/wAAAAAAAAAAAAAAAAAP//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//gAP/gAAAAAAAAAAAAAAAAAP//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//AAP/AAAAAAAAAAAAAAAAAAP/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf+AAAfwAAAAAAAAAAAAAAAAAAP/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfwAAB+AAAAAAAAAAAAAAAAAAP/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAAAP4AAAAAAAAAAAAAAAAAAP+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAAAfwAAAAAAAAAAAAAAAAAAf/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAAAfwAAAAAAAAAAAAAAAAAAf/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAfwAAAAAAAAAAAAAAAAAAf/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAf4AAAAAAAAAAAAAAAAAAf/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAf+AAAAAAAAAAAAAAAAAAf/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAP+AAAAAAAAAAAAAAAAAAf/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAABP+AAAAAAAAAAAAAAAAAAf/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAfP+AAAAAAAAAAAAAAAAAAf+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAB8f+AAAAAAAAAAAAAAAAAAf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAB8f/AAAAAAAAAAAAAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAB8f/AAAAAAAAAAAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAA8f/AAAAAAAAAAAAAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAA8f/AAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAA4f/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAA4f/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAMf/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAIf/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wAP//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHP8AD//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE//gAAf/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",es=null;function Ws(){if(es)return es;let a=atob(Vs),s=a.length,n=new Uint8Array(s);for(let e=0;e=180;)s-=360;let n=Math.min(359,Math.max(0,Math.floor(s+180))),p=Math.min(179,Math.max(0,Math.floor(90-a)))*360+n,A=p>>3,t=7-(p&7);return(Ws()[A]&1<180;)s-=360;for(;s<-180;)s+=360;return a<=-60?{type:"ice",color:v.white}:a>=60&&a<=84&&s>=-55&&s<=-20?{type:"ice",color:v.white}:a>=75?{type:"ice",color:v.white}:zs(a,s)?a>=27&&a<=36&&s>=75&&s<=98?{type:"mountain_snow",color:v.white}:a>=-45&&a<=5&&s>=-76&&s<=-68?{type:"mountain",color:v.darkStoneGrey}:a>=35&&a<=58&&s>=-120&&s<=-108?{type:"mountain",color:v.darkStoneGrey}:a>=44&&a<=48&&s>=6&&s<=15?{type:"mountain",color:v.darkStoneGrey}:a>=17&&a<=29&&s>=-13&&s<=34?{type:"desert",color:v.brickYellow}:a>=15&&a<=30&&s>=40&&s<=56?{type:"desert",color:v.brickYellow}:a<=-20&&a>=-30&&s>=120&&s<=136?{type:"desert",color:v.brickYellow}:a>=39&&a<=45&&s>=88&&s<=106?{type:"desert",color:v.brickYellow}:a>=-10&&a<=4&&s>=-72&&s<=-50?{type:"rainforest",color:v.darkGreen}:a>=-4&&a<=4&&s>=14&&s<=26?{type:"rainforest",color:v.darkGreen}:{type:"land",color:v.brightGreen}:Math.abs(a)<24&&(s>-90&&s<-62||s>108&&s<154)?{type:"reef",color:v.mediumBlue}:{type:"ocean",color:v.brightBlue}}var Gs="/js/three.min.js",$s="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js";function Is(a){return new Promise(s=>{let n=document.querySelector(`script[src="${a}"]`);if(n){typeof THREE<"u"?s(!0):(n.addEventListener("load",()=>s(!0),{once:!0}),n.addEventListener("error",()=>s(!1),{once:!0}));return}let e=document.createElement("script");e.src=a,e.async=!0,e.onload=()=>s(!0),e.onerror=()=>s(!1),document.head.appendChild(e)})}async function _s(){typeof THREE<"u"||(await Is(Gs),!(typeof THREE<"u")&&await Is($s))}function qs(){try{let a=document.createElement("canvas");return!!(window.WebGLRenderingContext&&(a.getContext("webgl")||a.getContext("experimental-webgl")))}catch{return!1}}async function Ls(){let a=document.getElementById("heroEarthBackdrop"),s=document.getElementById("digitalEarthCanvas");if(!a||!s||!qs()||(typeof THREE>"u"&&await _s(),typeof THREE>"u"))return;let n=new THREE.Scene,e=a.clientWidth<768,p=new THREE.PerspectiveCamera(38,a.clientWidth/a.clientHeight,.1,100);p.position.set(0,0,e?18:15.5);let A;try{A=new THREE.WebGLRenderer({canvas:s,alpha:!0,antialias:!0,powerPreference:"high-performance"})}catch{return}if(!A)return;A.setSize(a.clientWidth,a.clientHeight),A.setPixelRatio(Math.min(window.devicePixelRatio,2)),A.toneMapping=THREE.ACESFilmicToneMapping,A.toneMappingExposure=1.05;let t=THREE.MathUtils.degToRad(-23.4),c=THREE.MathUtils.degToRad(8),r=new THREE.Group;r.rotation.z=t,r.rotation.x=c,n.add(r);let d=new THREE.Group,i=THREE.MathUtils.degToRad(75);d.rotation.y=i,r.add(d);let o=e?13.5:19.2,k=e?.38:.267,b=[],S=o*o,D=Math.ceil(o);for(let l=-D;l<=D;l++){let u=l*l,V=(l+1)*(l+1),J=(l-1)*(l-1);for(let E=-D;E<=D;E++){let $=E*E,W=(E+1)*(E+1),Z=(E-1)*(E-1),B=u+$;for(let L=-D;L<=D;L++){let U=L*L,F=B+U;if(F<=S&&(u+W+U>S||V+$+U>S||J+$+U>S||u+Z+U>S||B+(L+1)*(L+1)>S||B+(L-1)*(L-1)>S)){let M=Math.sqrt(F),G=l/M,_=E/M,q=L/M,H=Math.asin(_)*(180/Math.PI),Y=Math.atan2(G,q)*(180/Math.PI),X=Ks(H,Y);b.push({x:l*k,y:E*k,z:L*k,color:X.color})}}}}let y=new THREE.Vector3(-.6,.7,1.3).normalize(),T={value:y},x=new THREE.MeshStandardMaterial({roughness:.35,metalness:0,envMapIntensity:.75});x.customProgramCacheKey=()=>"digital-earth-day-night-v1",x.onBeforeCompile=l=>{l.uniforms.uSunDirection=T,l.vertexShader=` + varying vec3 vGlobeNormal; + ${l.vertexShader} + `,l.vertexShader=l.vertexShader.replace("#include ",` + #include + #ifdef USE_INSTANCING + // Radial direction of the voxel from the planet center in world coordinates + vec4 instanceCenter = vec4(instanceMatrix[3].xyz, 0.0); + vGlobeNormal = normalize((modelMatrix * instanceCenter).xyz); + #else + vGlobeNormal = normalize((modelMatrix * vec4(position, 0.0)).xyz); + #endif + `),l.fragmentShader=` + uniform vec3 uSunDirection; + varying vec3 vGlobeNormal; + ${l.fragmentShader} + `,l.fragmentShader=l.fragmentShader.replace("#include ",` + #include + + // Celestial illumination angle (dot product with sun direction in world space) + vec3 sunDir = normalize(uSunDirection); + float globeSunDot = dot(vGlobeNormal, sunDir); + + // Day factor: 1.0 on day side, 0.0 on dark night side + // Smooth terminator transition across the twilight band + float dayFactor = smoothstep(-0.25, 0.25, globeSunDot); + + // Cut off direct sunlight on the dark night side + reflectedLight.directDiffuse *= dayFactor; + reflectedLight.directSpecular *= dayFactor; + + // Night side ambient tint: luminous sapphire/slate glow so continents remain visible + vec3 nightAmbientTint = vec3(0.28, 0.40, 0.62); + reflectedLight.indirectDiffuse = mix(reflectedLight.indirectDiffuse * nightAmbientTint * 2.2, reflectedLight.indirectDiffuse, dayFactor); + + // Warm golden atmospheric sunset / sunrise rim along the terminator + float sunsetGlow = smoothstep(-0.20, 0.05, globeSunDot) * (1.0 - smoothstep(0.05, 0.30, globeSunDot)); + vec3 sunsetColor = vec3(1.0, 0.55, 0.20); // Golden-orange sunset/sunrise + reflectedLight.directDiffuse += sunsetColor * (sunsetGlow * 0.55); + `)};let P=new THREE.BoxGeometry(k,k,k),h=new THREE.InstancedMesh(P,x,b.length),R=new THREE.Matrix4,C=new THREE.Color,I=new THREE.Vector3;for(let l=0;l0){let u=l.touches[0];f=u.clientX/window.innerWidth*2-1,O=-(u.clientY/window.innerHeight)*2+1}}function Fs(){f=0,O=0}window.addEventListener("mousemove",Ds,{passive:!0}),window.addEventListener("touchmove",Bs,{passive:!0}),document.addEventListener("mouseleave",Fs,{passive:!0});function Ns(l){let u=p.aspect,V=Math.tan(THREE.MathUtils.degToRad(p.fov/2)),J=p.position.z,E;if(l<=0){let M=-l;E=-22*Math.pow(M,1.25)}else{let M=Math.min(l,1.4);E=-16*Math.pow(M,1.35)}let W=(J-E)*V,Z=W*u,B,L,U=THREE.MathUtils.degToRad(-5.5),F=Math.cos(U),z=Math.sin(U);if(l<=0){let M=l*(Math.PI*.5),G=Math.sin(M),_=Math.cos(M),q=Z*.9,H=W*.86,Y=q*G,X=H*(_-1);B=Y*F-X*z,L=Y*z+X*F}else{let M=Math.min(l,1.4)*(Math.PI*.5),G=Math.sin(M),_=Math.cos(M),q=Z*.95,H=W+9,Y=q*G,X=-H*(1-_);B=Y*F-X*z,L=Y*z+X*F}return{x:B,y:L,z:E}}let ks=new THREE.Clock,ts=!0,Us=2.4,ws=typeof window<"u"?window.scrollY:0,ms=ws>400,ls=ws,gs=750,Hs=gs*1.35,us=document.querySelector(".hero-section")||a;"IntersectionObserver"in window&&us&&new IntersectionObserver(u=>{u.forEach(V=>{ts=V.isIntersecting||typeof window<"u"&&window.scrollY<600,ts?(s.style.visibility="visible",a&&(a.style.visibility="visible")):(s.style.opacity="0",s.style.visibility="hidden",a&&(a.style.visibility="hidden"))})},{threshold:.02}).observe(us);let hs=window.matchMedia("(prefers-reduced-motion: reduce)"),j=hs.matches?.2:1;hs.addEventListener("change",l=>{j=l.matches?.2:1});function fs(){if(requestAnimationFrame(fs),!ts){s.style.visibility!=="hidden"&&(s.style.opacity="0",s.style.visibility="hidden",a&&(a.style.visibility="hidden"));return}let l=ks.getDelta(),u=ks.getElapsedTime();i+=l*.08*j,g=THREE.MathUtils.lerp(g,f,.05),As=THREE.MathUtils.lerp(As,O,.05);let V=-As*.22*j,J=g*.32*j,E=g*.08*j,$=0,W=1;if(!ms){let H=j<.5?1:Math.min(u/Us,1);W=1-Math.pow(1-H,3.2),$=-1+W*1,H>=1&&(ms=!0)}let Z=Math.min(window.scrollY,Hs);ls=THREE.MathUtils.lerp(ls,Z,.08);let B=Math.min(ls/gs,1.25),U=B*(2-Math.min(B,1))*1,F=Math.min(Math.max($+U,-1),1.35),z=Ns(F),M=-Math.sin(F*Math.PI*.5)*.16*j;r.rotation.x=c+V,r.rotation.z=t+E+M,d.rotation.y=i+J;let G=Math.max(0,1-Math.abs(F)*1.5)*W,_=Math.sin(u*.75)*.12*G*j,q=Math.sin(u*.4)*.08*G*j;if(r.position.set(z.x+q,z.y+_,z.z),B>.7){let H=(B-.7)/.3,Y=Math.max(0,1-H);s.style.opacity=Y.toFixed(3),Y<=.01?s.style.visibility="hidden":s.style.visibility="visible"}else s.style.opacity="1.0",s.style.visibility="visible";A.render(n,p)}fs(),s.classList.add("loaded");function Ys(){if(!a)return;let l=a.clientWidth,u=a.clientHeight;p.aspect=l/u;let V=l<768;p.position.z=V?18:15.5,p.updateProjectionMatrix(),A.setSize(l,u),A.setPixelRatio(Math.min(window.devicePixelRatio,2))}window.addEventListener("resize",Ys)}function Xs(){let a=document.getElementById("digitalEarthCanvas"),s=document.getElementById("heroEarthBackdrop");if(!a)return;let n=!!(typeof window<"u"&&window.location.hash&&window.location.hash!=="#"&&window.location.hash!=="#hero"),e=!1,p=()=>{e||(e=!0,s&&(s.style.display=""),a.style.display="",Ls())};if(n){s&&(s.style.display="none"),a.style.display="none";let A=()=>{window.scrollY<200&&(window.removeEventListener("scroll",A),p())};window.addEventListener("scroll",A,{passive:!0});return}p()}function Ms(){ys(),Rs(),Ss(),Cs(),Xs();let a=new as;vs(a),Ps(),xs(),Os(),bs()}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",Ms):Ms();})(); diff --git a/static/js/three.min.js b/static/js/three.min.js new file mode 100644 index 0000000..b14f47c --- /dev/null +++ b/static/js/three.min.js @@ -0,0 +1,6 @@ +/** + * @license + * Copyright 2010-2021 Three.js Authors + * SPDX-License-Identifier: MIT + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).THREE={})}(this,(function(t){"use strict";const e="128",n=100,i=300,r=301,s=302,a=303,o=304,l=306,c=307,h=1e3,u=1001,d=1002,p=1003,m=1004,f=1005,g=1006,v=1007,y=1008,x=1009,_=1012,w=1014,b=1015,M=1016,S=1020,T=1022,E=1023,A=1026,L=1027,R=33776,C=33777,P=33778,D=33779,I=35840,N=35841,B=35842,z=35843,F=37492,O=37496,H=2300,G=2301,U=2302,k=2400,V=2401,W=2402,j=2500,q=2501,X=3e3,Y=3001,Z=3007,J=3002,Q=3004,K=3005,$=3006,tt=7680,et=35044,nt=35048,it="300 es";class rt{addEventListener(t,e){void 0===this._listeners&&(this._listeners={});const n=this._listeners;void 0===n[t]&&(n[t]=[]),-1===n[t].indexOf(e)&&n[t].push(e)}hasEventListener(t,e){if(void 0===this._listeners)return!1;const n=this._listeners;return void 0!==n[t]&&-1!==n[t].indexOf(e)}removeEventListener(t,e){if(void 0===this._listeners)return;const n=this._listeners[t];if(void 0!==n){const t=n.indexOf(e);-1!==t&&n.splice(t,1)}}dispatchEvent(t){if(void 0===this._listeners)return;const e=this._listeners[t.type];if(void 0!==e){t.target=this;const n=e.slice(0);for(let e=0,i=n.length;e>8&255]+st[t>>16&255]+st[t>>24&255]+"-"+st[255&e]+st[e>>8&255]+"-"+st[e>>16&15|64]+st[e>>24&255]+"-"+st[63&n|128]+st[n>>8&255]+"-"+st[n>>16&255]+st[n>>24&255]+st[255&i]+st[i>>8&255]+st[i>>16&255]+st[i>>24&255]).toUpperCase()}function ht(t,e,n){return Math.max(e,Math.min(n,t))}function ut(t,e){return(t%e+e)%e}function dt(t,e,n){return(1-n)*t+n*e}function pt(t){return 0==(t&t-1)&&0!==t}function mt(t){return Math.pow(2,Math.ceil(Math.log(t)/Math.LN2))}function ft(t){return Math.pow(2,Math.floor(Math.log(t)/Math.LN2))}var gt=Object.freeze({__proto__:null,DEG2RAD:ot,RAD2DEG:lt,generateUUID:ct,clamp:ht,euclideanModulo:ut,mapLinear:function(t,e,n,i,r){return i+(t-e)*(r-i)/(n-e)},inverseLerp:function(t,e,n){return t!==e?(n-t)/(e-t):0},lerp:dt,damp:function(t,e,n,i){return dt(t,e,1-Math.exp(-n*i))},pingpong:function(t,e=1){return e-Math.abs(ut(t,2*e)-e)},smoothstep:function(t,e,n){return t<=e?0:t>=n?1:(t=(t-e)/(n-e))*t*(3-2*t)},smootherstep:function(t,e,n){return t<=e?0:t>=n?1:(t=(t-e)/(n-e))*t*t*(t*(6*t-15)+10)},randInt:function(t,e){return t+Math.floor(Math.random()*(e-t+1))},randFloat:function(t,e){return t+Math.random()*(e-t)},randFloatSpread:function(t){return t*(.5-Math.random())},seededRandom:function(t){return void 0!==t&&(at=t%2147483647),at=16807*at%2147483647,(at-1)/2147483646},degToRad:function(t){return t*ot},radToDeg:function(t){return t*lt},isPowerOfTwo:pt,ceilPowerOfTwo:mt,floorPowerOfTwo:ft,setQuaternionFromProperEuler:function(t,e,n,i,r){const s=Math.cos,a=Math.sin,o=s(n/2),l=a(n/2),c=s((e+i)/2),h=a((e+i)/2),u=s((e-i)/2),d=a((e-i)/2),p=s((i-e)/2),m=a((i-e)/2);switch(r){case"XYX":t.set(o*h,l*u,l*d,o*c);break;case"YZY":t.set(l*d,o*h,l*u,o*c);break;case"ZXZ":t.set(l*u,l*d,o*h,o*c);break;case"XZX":t.set(o*h,l*m,l*p,o*c);break;case"YXY":t.set(l*p,o*h,l*m,o*c);break;case"ZYZ":t.set(l*m,l*p,o*h,o*c);break;default:console.warn("THREE.MathUtils: .setQuaternionFromProperEuler() encountered an unknown order: "+r)}}});class vt{constructor(t=0,e=0){this.x=t,this.y=e}get width(){return this.x}set width(t){this.x=t}get height(){return this.y}set height(t){this.y=t}set(t,e){return this.x=t,this.y=e,this}setScalar(t){return this.x=t,this.y=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y)}copy(t){return this.x=t.x,this.y=t.y,this}add(t,e){return void 0!==e?(console.warn("THREE.Vector2: .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(t,e)):(this.x+=t.x,this.y+=t.y,this)}addScalar(t){return this.x+=t,this.y+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this}sub(t,e){return void 0!==e?(console.warn("THREE.Vector2: .sub() now only accepts one argument. Use .subVectors( a, b ) instead."),this.subVectors(t,e)):(this.x-=t.x,this.y-=t.y,this)}subScalar(t){return this.x-=t,this.y-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this}multiply(t){return this.x*=t.x,this.y*=t.y,this}multiplyScalar(t){return this.x*=t,this.y*=t,this}divide(t){return this.x/=t.x,this.y/=t.y,this}divideScalar(t){return this.multiplyScalar(1/t)}applyMatrix3(t){const e=this.x,n=this.y,i=t.elements;return this.x=i[0]*e+i[3]*n+i[6],this.y=i[1]*e+i[4]*n+i[7],this}min(t){return this.x=Math.min(this.x,t.x),this.y=Math.min(this.y,t.y),this}max(t){return this.x=Math.max(this.x,t.x),this.y=Math.max(this.y,t.y),this}clamp(t,e){return this.x=Math.max(t.x,Math.min(e.x,this.x)),this.y=Math.max(t.y,Math.min(e.y,this.y)),this}clampScalar(t,e){return this.x=Math.max(t,Math.min(e,this.x)),this.y=Math.max(t,Math.min(e,this.y)),this}clampLength(t,e){const n=this.length();return this.divideScalar(n||1).multiplyScalar(Math.max(t,Math.min(e,n)))}floor(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this}ceil(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this}round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this}roundToZero(){return this.x=this.x<0?Math.ceil(this.x):Math.floor(this.x),this.y=this.y<0?Math.ceil(this.y):Math.floor(this.y),this}negate(){return this.x=-this.x,this.y=-this.y,this}dot(t){return this.x*t.x+this.y*t.y}cross(t){return this.x*t.y-this.y*t.x}lengthSq(){return this.x*this.x+this.y*this.y}length(){return Math.sqrt(this.x*this.x+this.y*this.y)}manhattanLength(){return Math.abs(this.x)+Math.abs(this.y)}normalize(){return this.divideScalar(this.length()||1)}angle(){return Math.atan2(-this.y,-this.x)+Math.PI}distanceTo(t){return Math.sqrt(this.distanceToSquared(t))}distanceToSquared(t){const e=this.x-t.x,n=this.y-t.y;return e*e+n*n}manhattanDistanceTo(t){return Math.abs(this.x-t.x)+Math.abs(this.y-t.y)}setLength(t){return this.normalize().multiplyScalar(t)}lerp(t,e){return this.x+=(t.x-this.x)*e,this.y+=(t.y-this.y)*e,this}lerpVectors(t,e,n){return this.x=t.x+(e.x-t.x)*n,this.y=t.y+(e.y-t.y)*n,this}equals(t){return t.x===this.x&&t.y===this.y}fromArray(t,e=0){return this.x=t[e],this.y=t[e+1],this}toArray(t=[],e=0){return t[e]=this.x,t[e+1]=this.y,t}fromBufferAttribute(t,e,n){return void 0!==n&&console.warn("THREE.Vector2: offset has been removed from .fromBufferAttribute()."),this.x=t.getX(e),this.y=t.getY(e),this}rotateAround(t,e){const n=Math.cos(e),i=Math.sin(e),r=this.x-t.x,s=this.y-t.y;return this.x=r*n-s*i+t.x,this.y=r*i+s*n+t.y,this}random(){return this.x=Math.random(),this.y=Math.random(),this}}vt.prototype.isVector2=!0;class yt{constructor(){this.elements=[1,0,0,0,1,0,0,0,1],arguments.length>0&&console.error("THREE.Matrix3: the constructor no longer reads arguments. use .set() instead.")}set(t,e,n,i,r,s,a,o,l){const c=this.elements;return c[0]=t,c[1]=i,c[2]=a,c[3]=e,c[4]=r,c[5]=o,c[6]=n,c[7]=s,c[8]=l,this}identity(){return this.set(1,0,0,0,1,0,0,0,1),this}copy(t){const e=this.elements,n=t.elements;return e[0]=n[0],e[1]=n[1],e[2]=n[2],e[3]=n[3],e[4]=n[4],e[5]=n[5],e[6]=n[6],e[7]=n[7],e[8]=n[8],this}extractBasis(t,e,n){return t.setFromMatrix3Column(this,0),e.setFromMatrix3Column(this,1),n.setFromMatrix3Column(this,2),this}setFromMatrix4(t){const e=t.elements;return this.set(e[0],e[4],e[8],e[1],e[5],e[9],e[2],e[6],e[10]),this}multiply(t){return this.multiplyMatrices(this,t)}premultiply(t){return this.multiplyMatrices(t,this)}multiplyMatrices(t,e){const n=t.elements,i=e.elements,r=this.elements,s=n[0],a=n[3],o=n[6],l=n[1],c=n[4],h=n[7],u=n[2],d=n[5],p=n[8],m=i[0],f=i[3],g=i[6],v=i[1],y=i[4],x=i[7],_=i[2],w=i[5],b=i[8];return r[0]=s*m+a*v+o*_,r[3]=s*f+a*y+o*w,r[6]=s*g+a*x+o*b,r[1]=l*m+c*v+h*_,r[4]=l*f+c*y+h*w,r[7]=l*g+c*x+h*b,r[2]=u*m+d*v+p*_,r[5]=u*f+d*y+p*w,r[8]=u*g+d*x+p*b,this}multiplyScalar(t){const e=this.elements;return e[0]*=t,e[3]*=t,e[6]*=t,e[1]*=t,e[4]*=t,e[7]*=t,e[2]*=t,e[5]*=t,e[8]*=t,this}determinant(){const t=this.elements,e=t[0],n=t[1],i=t[2],r=t[3],s=t[4],a=t[5],o=t[6],l=t[7],c=t[8];return e*s*c-e*a*l-n*r*c+n*a*o+i*r*l-i*s*o}invert(){const t=this.elements,e=t[0],n=t[1],i=t[2],r=t[3],s=t[4],a=t[5],o=t[6],l=t[7],c=t[8],h=c*s-a*l,u=a*o-c*r,d=l*r-s*o,p=e*h+n*u+i*d;if(0===p)return this.set(0,0,0,0,0,0,0,0,0);const m=1/p;return t[0]=h*m,t[1]=(i*l-c*n)*m,t[2]=(a*n-i*s)*m,t[3]=u*m,t[4]=(c*e-i*o)*m,t[5]=(i*r-a*e)*m,t[6]=d*m,t[7]=(n*o-l*e)*m,t[8]=(s*e-n*r)*m,this}transpose(){let t;const e=this.elements;return t=e[1],e[1]=e[3],e[3]=t,t=e[2],e[2]=e[6],e[6]=t,t=e[5],e[5]=e[7],e[7]=t,this}getNormalMatrix(t){return this.setFromMatrix4(t).invert().transpose()}transposeIntoArray(t){const e=this.elements;return t[0]=e[0],t[1]=e[3],t[2]=e[6],t[3]=e[1],t[4]=e[4],t[5]=e[7],t[6]=e[2],t[7]=e[5],t[8]=e[8],this}setUvTransform(t,e,n,i,r,s,a){const o=Math.cos(r),l=Math.sin(r);return this.set(n*o,n*l,-n*(o*s+l*a)+s+t,-i*l,i*o,-i*(-l*s+o*a)+a+e,0,0,1),this}scale(t,e){const n=this.elements;return n[0]*=t,n[3]*=t,n[6]*=t,n[1]*=e,n[4]*=e,n[7]*=e,this}rotate(t){const e=Math.cos(t),n=Math.sin(t),i=this.elements,r=i[0],s=i[3],a=i[6],o=i[1],l=i[4],c=i[7];return i[0]=e*r+n*o,i[3]=e*s+n*l,i[6]=e*a+n*c,i[1]=-n*r+e*o,i[4]=-n*s+e*l,i[7]=-n*a+e*c,this}translate(t,e){const n=this.elements;return n[0]+=t*n[2],n[3]+=t*n[5],n[6]+=t*n[8],n[1]+=e*n[2],n[4]+=e*n[5],n[7]+=e*n[8],this}equals(t){const e=this.elements,n=t.elements;for(let t=0;t<9;t++)if(e[t]!==n[t])return!1;return!0}fromArray(t,e=0){for(let n=0;n<9;n++)this.elements[n]=t[n+e];return this}toArray(t=[],e=0){const n=this.elements;return t[e]=n[0],t[e+1]=n[1],t[e+2]=n[2],t[e+3]=n[3],t[e+4]=n[4],t[e+5]=n[5],t[e+6]=n[6],t[e+7]=n[7],t[e+8]=n[8],t}clone(){return(new this.constructor).fromArray(this.elements)}}let xt;yt.prototype.isMatrix3=!0;class _t{static getDataURL(t){if(/^data:/i.test(t.src))return t.src;if("undefined"==typeof HTMLCanvasElement)return t.src;let e;if(t instanceof HTMLCanvasElement)e=t;else{void 0===xt&&(xt=document.createElementNS("http://www.w3.org/1999/xhtml","canvas")),xt.width=t.width,xt.height=t.height;const n=xt.getContext("2d");t instanceof ImageData?n.putImageData(t,0,0):n.drawImage(t,0,0,t.width,t.height),e=xt}return e.width>2048||e.height>2048?(console.warn("THREE.ImageUtils.getDataURL: Image converted to jpg for performance reasons",t),e.toDataURL("image/jpeg",.6)):e.toDataURL("image/png")}}let wt=0;class bt extends rt{constructor(t=bt.DEFAULT_IMAGE,e=bt.DEFAULT_MAPPING,n=1001,i=1001,r=1006,s=1008,a=1023,o=1009,l=1,c=3e3){super(),Object.defineProperty(this,"id",{value:wt++}),this.uuid=ct(),this.name="",this.image=t,this.mipmaps=[],this.mapping=e,this.wrapS=n,this.wrapT=i,this.magFilter=r,this.minFilter=s,this.anisotropy=l,this.format=a,this.internalFormat=null,this.type=o,this.offset=new vt(0,0),this.repeat=new vt(1,1),this.center=new vt(0,0),this.rotation=0,this.matrixAutoUpdate=!0,this.matrix=new yt,this.generateMipmaps=!0,this.premultiplyAlpha=!1,this.flipY=!0,this.unpackAlignment=4,this.encoding=c,this.version=0,this.onUpdate=null}updateMatrix(){this.matrix.setUvTransform(this.offset.x,this.offset.y,this.repeat.x,this.repeat.y,this.rotation,this.center.x,this.center.y)}clone(){return(new this.constructor).copy(this)}copy(t){return this.name=t.name,this.image=t.image,this.mipmaps=t.mipmaps.slice(0),this.mapping=t.mapping,this.wrapS=t.wrapS,this.wrapT=t.wrapT,this.magFilter=t.magFilter,this.minFilter=t.minFilter,this.anisotropy=t.anisotropy,this.format=t.format,this.internalFormat=t.internalFormat,this.type=t.type,this.offset.copy(t.offset),this.repeat.copy(t.repeat),this.center.copy(t.center),this.rotation=t.rotation,this.matrixAutoUpdate=t.matrixAutoUpdate,this.matrix.copy(t.matrix),this.generateMipmaps=t.generateMipmaps,this.premultiplyAlpha=t.premultiplyAlpha,this.flipY=t.flipY,this.unpackAlignment=t.unpackAlignment,this.encoding=t.encoding,this}toJSON(t){const e=void 0===t||"string"==typeof t;if(!e&&void 0!==t.textures[this.uuid])return t.textures[this.uuid];const n={metadata:{version:4.5,type:"Texture",generator:"Texture.toJSON"},uuid:this.uuid,name:this.name,mapping:this.mapping,repeat:[this.repeat.x,this.repeat.y],offset:[this.offset.x,this.offset.y],center:[this.center.x,this.center.y],rotation:this.rotation,wrap:[this.wrapS,this.wrapT],format:this.format,type:this.type,encoding:this.encoding,minFilter:this.minFilter,magFilter:this.magFilter,anisotropy:this.anisotropy,flipY:this.flipY,premultiplyAlpha:this.premultiplyAlpha,unpackAlignment:this.unpackAlignment};if(void 0!==this.image){const i=this.image;if(void 0===i.uuid&&(i.uuid=ct()),!e&&void 0===t.images[i.uuid]){let e;if(Array.isArray(i)){e=[];for(let t=0,n=i.length;t1)switch(this.wrapS){case h:t.x=t.x-Math.floor(t.x);break;case u:t.x=t.x<0?0:1;break;case d:1===Math.abs(Math.floor(t.x)%2)?t.x=Math.ceil(t.x)-t.x:t.x=t.x-Math.floor(t.x)}if(t.y<0||t.y>1)switch(this.wrapT){case h:t.y=t.y-Math.floor(t.y);break;case u:t.y=t.y<0?0:1;break;case d:1===Math.abs(Math.floor(t.y)%2)?t.y=Math.ceil(t.y)-t.y:t.y=t.y-Math.floor(t.y)}return this.flipY&&(t.y=1-t.y),t}set needsUpdate(t){!0===t&&this.version++}}function Mt(t){return"undefined"!=typeof HTMLImageElement&&t instanceof HTMLImageElement||"undefined"!=typeof HTMLCanvasElement&&t instanceof HTMLCanvasElement||"undefined"!=typeof ImageBitmap&&t instanceof ImageBitmap?_t.getDataURL(t):t.data?{data:Array.prototype.slice.call(t.data),width:t.width,height:t.height,type:t.data.constructor.name}:(console.warn("THREE.Texture: Unable to serialize Texture."),{})}bt.DEFAULT_IMAGE=void 0,bt.DEFAULT_MAPPING=i,bt.prototype.isTexture=!0;class St{constructor(t=0,e=0,n=0,i=1){this.x=t,this.y=e,this.z=n,this.w=i}get width(){return this.z}set width(t){this.z=t}get height(){return this.w}set height(t){this.w=t}set(t,e,n,i){return this.x=t,this.y=e,this.z=n,this.w=i,this}setScalar(t){return this.x=t,this.y=t,this.z=t,this.w=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setZ(t){return this.z=t,this}setW(t){return this.w=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;case 2:this.z=e;break;case 3:this.w=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;case 2:return this.z;case 3:return this.w;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y,this.z,this.w)}copy(t){return this.x=t.x,this.y=t.y,this.z=t.z,this.w=void 0!==t.w?t.w:1,this}add(t,e){return void 0!==e?(console.warn("THREE.Vector4: .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(t,e)):(this.x+=t.x,this.y+=t.y,this.z+=t.z,this.w+=t.w,this)}addScalar(t){return this.x+=t,this.y+=t,this.z+=t,this.w+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this.z=t.z+e.z,this.w=t.w+e.w,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this.z+=t.z*e,this.w+=t.w*e,this}sub(t,e){return void 0!==e?(console.warn("THREE.Vector4: .sub() now only accepts one argument. Use .subVectors( a, b ) instead."),this.subVectors(t,e)):(this.x-=t.x,this.y-=t.y,this.z-=t.z,this.w-=t.w,this)}subScalar(t){return this.x-=t,this.y-=t,this.z-=t,this.w-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this.z=t.z-e.z,this.w=t.w-e.w,this}multiply(t){return this.x*=t.x,this.y*=t.y,this.z*=t.z,this.w*=t.w,this}multiplyScalar(t){return this.x*=t,this.y*=t,this.z*=t,this.w*=t,this}applyMatrix4(t){const e=this.x,n=this.y,i=this.z,r=this.w,s=t.elements;return this.x=s[0]*e+s[4]*n+s[8]*i+s[12]*r,this.y=s[1]*e+s[5]*n+s[9]*i+s[13]*r,this.z=s[2]*e+s[6]*n+s[10]*i+s[14]*r,this.w=s[3]*e+s[7]*n+s[11]*i+s[15]*r,this}divideScalar(t){return this.multiplyScalar(1/t)}setAxisAngleFromQuaternion(t){this.w=2*Math.acos(t.w);const e=Math.sqrt(1-t.w*t.w);return e<1e-4?(this.x=1,this.y=0,this.z=0):(this.x=t.x/e,this.y=t.y/e,this.z=t.z/e),this}setAxisAngleFromRotationMatrix(t){let e,n,i,r;const s=.01,a=.1,o=t.elements,l=o[0],c=o[4],h=o[8],u=o[1],d=o[5],p=o[9],m=o[2],f=o[6],g=o[10];if(Math.abs(c-u)o&&t>v?tv?o=0?1:-1,i=1-e*e;if(i>Number.EPSILON){const r=Math.sqrt(i),s=Math.atan2(r,e*n);t=Math.sin(t*s)/r,a=Math.sin(a*s)/r}const r=a*n;if(o=o*t+u*r,l=l*t+d*r,c=c*t+p*r,h=h*t+m*r,t===1-a){const t=1/Math.sqrt(o*o+l*l+c*c+h*h);o*=t,l*=t,c*=t,h*=t}}t[e]=o,t[e+1]=l,t[e+2]=c,t[e+3]=h}static multiplyQuaternionsFlat(t,e,n,i,r,s){const a=n[i],o=n[i+1],l=n[i+2],c=n[i+3],h=r[s],u=r[s+1],d=r[s+2],p=r[s+3];return t[e]=a*p+c*h+o*d-l*u,t[e+1]=o*p+c*u+l*h-a*d,t[e+2]=l*p+c*d+a*u-o*h,t[e+3]=c*p-a*h-o*u-l*d,t}get x(){return this._x}set x(t){this._x=t,this._onChangeCallback()}get y(){return this._y}set y(t){this._y=t,this._onChangeCallback()}get z(){return this._z}set z(t){this._z=t,this._onChangeCallback()}get w(){return this._w}set w(t){this._w=t,this._onChangeCallback()}set(t,e,n,i){return this._x=t,this._y=e,this._z=n,this._w=i,this._onChangeCallback(),this}clone(){return new this.constructor(this._x,this._y,this._z,this._w)}copy(t){return this._x=t.x,this._y=t.y,this._z=t.z,this._w=t.w,this._onChangeCallback(),this}setFromEuler(t,e){if(!t||!t.isEuler)throw new Error("THREE.Quaternion: .setFromEuler() now expects an Euler rotation rather than a Vector3 and order.");const n=t._x,i=t._y,r=t._z,s=t._order,a=Math.cos,o=Math.sin,l=a(n/2),c=a(i/2),h=a(r/2),u=o(n/2),d=o(i/2),p=o(r/2);switch(s){case"XYZ":this._x=u*c*h+l*d*p,this._y=l*d*h-u*c*p,this._z=l*c*p+u*d*h,this._w=l*c*h-u*d*p;break;case"YXZ":this._x=u*c*h+l*d*p,this._y=l*d*h-u*c*p,this._z=l*c*p-u*d*h,this._w=l*c*h+u*d*p;break;case"ZXY":this._x=u*c*h-l*d*p,this._y=l*d*h+u*c*p,this._z=l*c*p+u*d*h,this._w=l*c*h-u*d*p;break;case"ZYX":this._x=u*c*h-l*d*p,this._y=l*d*h+u*c*p,this._z=l*c*p-u*d*h,this._w=l*c*h+u*d*p;break;case"YZX":this._x=u*c*h+l*d*p,this._y=l*d*h+u*c*p,this._z=l*c*p-u*d*h,this._w=l*c*h-u*d*p;break;case"XZY":this._x=u*c*h-l*d*p,this._y=l*d*h-u*c*p,this._z=l*c*p+u*d*h,this._w=l*c*h+u*d*p;break;default:console.warn("THREE.Quaternion: .setFromEuler() encountered an unknown order: "+s)}return!1!==e&&this._onChangeCallback(),this}setFromAxisAngle(t,e){const n=e/2,i=Math.sin(n);return this._x=t.x*i,this._y=t.y*i,this._z=t.z*i,this._w=Math.cos(n),this._onChangeCallback(),this}setFromRotationMatrix(t){const e=t.elements,n=e[0],i=e[4],r=e[8],s=e[1],a=e[5],o=e[9],l=e[2],c=e[6],h=e[10],u=n+a+h;if(u>0){const t=.5/Math.sqrt(u+1);this._w=.25/t,this._x=(c-o)*t,this._y=(r-l)*t,this._z=(s-i)*t}else if(n>a&&n>h){const t=2*Math.sqrt(1+n-a-h);this._w=(c-o)/t,this._x=.25*t,this._y=(i+s)/t,this._z=(r+l)/t}else if(a>h){const t=2*Math.sqrt(1+a-n-h);this._w=(r-l)/t,this._x=(i+s)/t,this._y=.25*t,this._z=(o+c)/t}else{const t=2*Math.sqrt(1+h-n-a);this._w=(s-i)/t,this._x=(r+l)/t,this._y=(o+c)/t,this._z=.25*t}return this._onChangeCallback(),this}setFromUnitVectors(t,e){let n=t.dot(e)+1;return nMath.abs(t.z)?(this._x=-t.y,this._y=t.x,this._z=0,this._w=n):(this._x=0,this._y=-t.z,this._z=t.y,this._w=n)):(this._x=t.y*e.z-t.z*e.y,this._y=t.z*e.x-t.x*e.z,this._z=t.x*e.y-t.y*e.x,this._w=n),this.normalize()}angleTo(t){return 2*Math.acos(Math.abs(ht(this.dot(t),-1,1)))}rotateTowards(t,e){const n=this.angleTo(t);if(0===n)return this;const i=Math.min(1,e/n);return this.slerp(t,i),this}identity(){return this.set(0,0,0,1)}invert(){return this.conjugate()}conjugate(){return this._x*=-1,this._y*=-1,this._z*=-1,this._onChangeCallback(),this}dot(t){return this._x*t._x+this._y*t._y+this._z*t._z+this._w*t._w}lengthSq(){return this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w}length(){return Math.sqrt(this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w)}normalize(){let t=this.length();return 0===t?(this._x=0,this._y=0,this._z=0,this._w=1):(t=1/t,this._x=this._x*t,this._y=this._y*t,this._z=this._z*t,this._w=this._w*t),this._onChangeCallback(),this}multiply(t,e){return void 0!==e?(console.warn("THREE.Quaternion: .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead."),this.multiplyQuaternions(t,e)):this.multiplyQuaternions(this,t)}premultiply(t){return this.multiplyQuaternions(t,this)}multiplyQuaternions(t,e){const n=t._x,i=t._y,r=t._z,s=t._w,a=e._x,o=e._y,l=e._z,c=e._w;return this._x=n*c+s*a+i*l-r*o,this._y=i*c+s*o+r*a-n*l,this._z=r*c+s*l+n*o-i*a,this._w=s*c-n*a-i*o-r*l,this._onChangeCallback(),this}slerp(t,e){if(0===e)return this;if(1===e)return this.copy(t);const n=this._x,i=this._y,r=this._z,s=this._w;let a=s*t._w+n*t._x+i*t._y+r*t._z;if(a<0?(this._w=-t._w,this._x=-t._x,this._y=-t._y,this._z=-t._z,a=-a):this.copy(t),a>=1)return this._w=s,this._x=n,this._y=i,this._z=r,this;const o=1-a*a;if(o<=Number.EPSILON){const t=1-e;return this._w=t*s+e*this._w,this._x=t*n+e*this._x,this._y=t*i+e*this._y,this._z=t*r+e*this._z,this.normalize(),this._onChangeCallback(),this}const l=Math.sqrt(o),c=Math.atan2(l,a),h=Math.sin((1-e)*c)/l,u=Math.sin(e*c)/l;return this._w=s*h+this._w*u,this._x=n*h+this._x*u,this._y=i*h+this._y*u,this._z=r*h+this._z*u,this._onChangeCallback(),this}slerpQuaternions(t,e,n){this.copy(t).slerp(e,n)}equals(t){return t._x===this._x&&t._y===this._y&&t._z===this._z&&t._w===this._w}fromArray(t,e=0){return this._x=t[e],this._y=t[e+1],this._z=t[e+2],this._w=t[e+3],this._onChangeCallback(),this}toArray(t=[],e=0){return t[e]=this._x,t[e+1]=this._y,t[e+2]=this._z,t[e+3]=this._w,t}fromBufferAttribute(t,e){return this._x=t.getX(e),this._y=t.getY(e),this._z=t.getZ(e),this._w=t.getW(e),this}_onChange(t){return this._onChangeCallback=t,this}_onChangeCallback(){}}At.prototype.isQuaternion=!0;class Lt{constructor(t=0,e=0,n=0){this.x=t,this.y=e,this.z=n}set(t,e,n){return void 0===n&&(n=this.z),this.x=t,this.y=e,this.z=n,this}setScalar(t){return this.x=t,this.y=t,this.z=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setZ(t){return this.z=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;case 2:this.z=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;case 2:return this.z;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y,this.z)}copy(t){return this.x=t.x,this.y=t.y,this.z=t.z,this}add(t,e){return void 0!==e?(console.warn("THREE.Vector3: .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(t,e)):(this.x+=t.x,this.y+=t.y,this.z+=t.z,this)}addScalar(t){return this.x+=t,this.y+=t,this.z+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this.z=t.z+e.z,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this.z+=t.z*e,this}sub(t,e){return void 0!==e?(console.warn("THREE.Vector3: .sub() now only accepts one argument. Use .subVectors( a, b ) instead."),this.subVectors(t,e)):(this.x-=t.x,this.y-=t.y,this.z-=t.z,this)}subScalar(t){return this.x-=t,this.y-=t,this.z-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this.z=t.z-e.z,this}multiply(t,e){return void 0!==e?(console.warn("THREE.Vector3: .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead."),this.multiplyVectors(t,e)):(this.x*=t.x,this.y*=t.y,this.z*=t.z,this)}multiplyScalar(t){return this.x*=t,this.y*=t,this.z*=t,this}multiplyVectors(t,e){return this.x=t.x*e.x,this.y=t.y*e.y,this.z=t.z*e.z,this}applyEuler(t){return t&&t.isEuler||console.error("THREE.Vector3: .applyEuler() now expects an Euler rotation rather than a Vector3 and order."),this.applyQuaternion(Ct.setFromEuler(t))}applyAxisAngle(t,e){return this.applyQuaternion(Ct.setFromAxisAngle(t,e))}applyMatrix3(t){const e=this.x,n=this.y,i=this.z,r=t.elements;return this.x=r[0]*e+r[3]*n+r[6]*i,this.y=r[1]*e+r[4]*n+r[7]*i,this.z=r[2]*e+r[5]*n+r[8]*i,this}applyNormalMatrix(t){return this.applyMatrix3(t).normalize()}applyMatrix4(t){const e=this.x,n=this.y,i=this.z,r=t.elements,s=1/(r[3]*e+r[7]*n+r[11]*i+r[15]);return this.x=(r[0]*e+r[4]*n+r[8]*i+r[12])*s,this.y=(r[1]*e+r[5]*n+r[9]*i+r[13])*s,this.z=(r[2]*e+r[6]*n+r[10]*i+r[14])*s,this}applyQuaternion(t){const e=this.x,n=this.y,i=this.z,r=t.x,s=t.y,a=t.z,o=t.w,l=o*e+s*i-a*n,c=o*n+a*e-r*i,h=o*i+r*n-s*e,u=-r*e-s*n-a*i;return this.x=l*o+u*-r+c*-a-h*-s,this.y=c*o+u*-s+h*-r-l*-a,this.z=h*o+u*-a+l*-s-c*-r,this}project(t){return this.applyMatrix4(t.matrixWorldInverse).applyMatrix4(t.projectionMatrix)}unproject(t){return this.applyMatrix4(t.projectionMatrixInverse).applyMatrix4(t.matrixWorld)}transformDirection(t){const e=this.x,n=this.y,i=this.z,r=t.elements;return this.x=r[0]*e+r[4]*n+r[8]*i,this.y=r[1]*e+r[5]*n+r[9]*i,this.z=r[2]*e+r[6]*n+r[10]*i,this.normalize()}divide(t){return this.x/=t.x,this.y/=t.y,this.z/=t.z,this}divideScalar(t){return this.multiplyScalar(1/t)}min(t){return this.x=Math.min(this.x,t.x),this.y=Math.min(this.y,t.y),this.z=Math.min(this.z,t.z),this}max(t){return this.x=Math.max(this.x,t.x),this.y=Math.max(this.y,t.y),this.z=Math.max(this.z,t.z),this}clamp(t,e){return this.x=Math.max(t.x,Math.min(e.x,this.x)),this.y=Math.max(t.y,Math.min(e.y,this.y)),this.z=Math.max(t.z,Math.min(e.z,this.z)),this}clampScalar(t,e){return this.x=Math.max(t,Math.min(e,this.x)),this.y=Math.max(t,Math.min(e,this.y)),this.z=Math.max(t,Math.min(e,this.z)),this}clampLength(t,e){const n=this.length();return this.divideScalar(n||1).multiplyScalar(Math.max(t,Math.min(e,n)))}floor(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this.z=Math.floor(this.z),this}ceil(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this.z=Math.ceil(this.z),this}round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this.z=Math.round(this.z),this}roundToZero(){return this.x=this.x<0?Math.ceil(this.x):Math.floor(this.x),this.y=this.y<0?Math.ceil(this.y):Math.floor(this.y),this.z=this.z<0?Math.ceil(this.z):Math.floor(this.z),this}negate(){return this.x=-this.x,this.y=-this.y,this.z=-this.z,this}dot(t){return this.x*t.x+this.y*t.y+this.z*t.z}lengthSq(){return this.x*this.x+this.y*this.y+this.z*this.z}length(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z)}manhattanLength(){return Math.abs(this.x)+Math.abs(this.y)+Math.abs(this.z)}normalize(){return this.divideScalar(this.length()||1)}setLength(t){return this.normalize().multiplyScalar(t)}lerp(t,e){return this.x+=(t.x-this.x)*e,this.y+=(t.y-this.y)*e,this.z+=(t.z-this.z)*e,this}lerpVectors(t,e,n){return this.x=t.x+(e.x-t.x)*n,this.y=t.y+(e.y-t.y)*n,this.z=t.z+(e.z-t.z)*n,this}cross(t,e){return void 0!==e?(console.warn("THREE.Vector3: .cross() now only accepts one argument. Use .crossVectors( a, b ) instead."),this.crossVectors(t,e)):this.crossVectors(this,t)}crossVectors(t,e){const n=t.x,i=t.y,r=t.z,s=e.x,a=e.y,o=e.z;return this.x=i*o-r*a,this.y=r*s-n*o,this.z=n*a-i*s,this}projectOnVector(t){const e=t.lengthSq();if(0===e)return this.set(0,0,0);const n=t.dot(this)/e;return this.copy(t).multiplyScalar(n)}projectOnPlane(t){return Rt.copy(this).projectOnVector(t),this.sub(Rt)}reflect(t){return this.sub(Rt.copy(t).multiplyScalar(2*this.dot(t)))}angleTo(t){const e=Math.sqrt(this.lengthSq()*t.lengthSq());if(0===e)return Math.PI/2;const n=this.dot(t)/e;return Math.acos(ht(n,-1,1))}distanceTo(t){return Math.sqrt(this.distanceToSquared(t))}distanceToSquared(t){const e=this.x-t.x,n=this.y-t.y,i=this.z-t.z;return e*e+n*n+i*i}manhattanDistanceTo(t){return Math.abs(this.x-t.x)+Math.abs(this.y-t.y)+Math.abs(this.z-t.z)}setFromSpherical(t){return this.setFromSphericalCoords(t.radius,t.phi,t.theta)}setFromSphericalCoords(t,e,n){const i=Math.sin(e)*t;return this.x=i*Math.sin(n),this.y=Math.cos(e)*t,this.z=i*Math.cos(n),this}setFromCylindrical(t){return this.setFromCylindricalCoords(t.radius,t.theta,t.y)}setFromCylindricalCoords(t,e,n){return this.x=t*Math.sin(e),this.y=n,this.z=t*Math.cos(e),this}setFromMatrixPosition(t){const e=t.elements;return this.x=e[12],this.y=e[13],this.z=e[14],this}setFromMatrixScale(t){const e=this.setFromMatrixColumn(t,0).length(),n=this.setFromMatrixColumn(t,1).length(),i=this.setFromMatrixColumn(t,2).length();return this.x=e,this.y=n,this.z=i,this}setFromMatrixColumn(t,e){return this.fromArray(t.elements,4*e)}setFromMatrix3Column(t,e){return this.fromArray(t.elements,3*e)}equals(t){return t.x===this.x&&t.y===this.y&&t.z===this.z}fromArray(t,e=0){return this.x=t[e],this.y=t[e+1],this.z=t[e+2],this}toArray(t=[],e=0){return t[e]=this.x,t[e+1]=this.y,t[e+2]=this.z,t}fromBufferAttribute(t,e,n){return void 0!==n&&console.warn("THREE.Vector3: offset has been removed from .fromBufferAttribute()."),this.x=t.getX(e),this.y=t.getY(e),this.z=t.getZ(e),this}random(){return this.x=Math.random(),this.y=Math.random(),this.z=Math.random(),this}}Lt.prototype.isVector3=!0;const Rt=new Lt,Ct=new At;class Pt{constructor(t=new Lt(1/0,1/0,1/0),e=new Lt(-1/0,-1/0,-1/0)){this.min=t,this.max=e}set(t,e){return this.min.copy(t),this.max.copy(e),this}setFromArray(t){let e=1/0,n=1/0,i=1/0,r=-1/0,s=-1/0,a=-1/0;for(let o=0,l=t.length;or&&(r=l),c>s&&(s=c),h>a&&(a=h)}return this.min.set(e,n,i),this.max.set(r,s,a),this}setFromBufferAttribute(t){let e=1/0,n=1/0,i=1/0,r=-1/0,s=-1/0,a=-1/0;for(let o=0,l=t.count;or&&(r=l),c>s&&(s=c),h>a&&(a=h)}return this.min.set(e,n,i),this.max.set(r,s,a),this}setFromPoints(t){this.makeEmpty();for(let e=0,n=t.length;ethis.max.x||t.ythis.max.y||t.zthis.max.z)}containsBox(t){return this.min.x<=t.min.x&&t.max.x<=this.max.x&&this.min.y<=t.min.y&&t.max.y<=this.max.y&&this.min.z<=t.min.z&&t.max.z<=this.max.z}getParameter(t,e){return void 0===e&&(console.warn("THREE.Box3: .getParameter() target is now required"),e=new Lt),e.set((t.x-this.min.x)/(this.max.x-this.min.x),(t.y-this.min.y)/(this.max.y-this.min.y),(t.z-this.min.z)/(this.max.z-this.min.z))}intersectsBox(t){return!(t.max.xthis.max.x||t.max.ythis.max.y||t.max.zthis.max.z)}intersectsSphere(t){return this.clampPoint(t.center,It),It.distanceToSquared(t.center)<=t.radius*t.radius}intersectsPlane(t){let e,n;return t.normal.x>0?(e=t.normal.x*this.min.x,n=t.normal.x*this.max.x):(e=t.normal.x*this.max.x,n=t.normal.x*this.min.x),t.normal.y>0?(e+=t.normal.y*this.min.y,n+=t.normal.y*this.max.y):(e+=t.normal.y*this.max.y,n+=t.normal.y*this.min.y),t.normal.z>0?(e+=t.normal.z*this.min.z,n+=t.normal.z*this.max.z):(e+=t.normal.z*this.max.z,n+=t.normal.z*this.min.z),e<=-t.constant&&n>=-t.constant}intersectsTriangle(t){if(this.isEmpty())return!1;this.getCenter(Ut),kt.subVectors(this.max,Ut),Bt.subVectors(t.a,Ut),zt.subVectors(t.b,Ut),Ft.subVectors(t.c,Ut),Ot.subVectors(zt,Bt),Ht.subVectors(Ft,zt),Gt.subVectors(Bt,Ft);let e=[0,-Ot.z,Ot.y,0,-Ht.z,Ht.y,0,-Gt.z,Gt.y,Ot.z,0,-Ot.x,Ht.z,0,-Ht.x,Gt.z,0,-Gt.x,-Ot.y,Ot.x,0,-Ht.y,Ht.x,0,-Gt.y,Gt.x,0];return!!jt(e,Bt,zt,Ft,kt)&&(e=[1,0,0,0,1,0,0,0,1],!!jt(e,Bt,zt,Ft,kt)&&(Vt.crossVectors(Ot,Ht),e=[Vt.x,Vt.y,Vt.z],jt(e,Bt,zt,Ft,kt)))}clampPoint(t,e){return void 0===e&&(console.warn("THREE.Box3: .clampPoint() target is now required"),e=new Lt),e.copy(t).clamp(this.min,this.max)}distanceToPoint(t){return It.copy(t).clamp(this.min,this.max).sub(t).length()}getBoundingSphere(t){return void 0===t&&console.error("THREE.Box3: .getBoundingSphere() target is now required"),this.getCenter(t.center),t.radius=.5*this.getSize(It).length(),t}intersect(t){return this.min.max(t.min),this.max.min(t.max),this.isEmpty()&&this.makeEmpty(),this}union(t){return this.min.min(t.min),this.max.max(t.max),this}applyMatrix4(t){return this.isEmpty()||(Dt[0].set(this.min.x,this.min.y,this.min.z).applyMatrix4(t),Dt[1].set(this.min.x,this.min.y,this.max.z).applyMatrix4(t),Dt[2].set(this.min.x,this.max.y,this.min.z).applyMatrix4(t),Dt[3].set(this.min.x,this.max.y,this.max.z).applyMatrix4(t),Dt[4].set(this.max.x,this.min.y,this.min.z).applyMatrix4(t),Dt[5].set(this.max.x,this.min.y,this.max.z).applyMatrix4(t),Dt[6].set(this.max.x,this.max.y,this.min.z).applyMatrix4(t),Dt[7].set(this.max.x,this.max.y,this.max.z).applyMatrix4(t),this.setFromPoints(Dt)),this}translate(t){return this.min.add(t),this.max.add(t),this}equals(t){return t.min.equals(this.min)&&t.max.equals(this.max)}}Pt.prototype.isBox3=!0;const Dt=[new Lt,new Lt,new Lt,new Lt,new Lt,new Lt,new Lt,new Lt],It=new Lt,Nt=new Pt,Bt=new Lt,zt=new Lt,Ft=new Lt,Ot=new Lt,Ht=new Lt,Gt=new Lt,Ut=new Lt,kt=new Lt,Vt=new Lt,Wt=new Lt;function jt(t,e,n,i,r){for(let s=0,a=t.length-3;s<=a;s+=3){Wt.fromArray(t,s);const a=r.x*Math.abs(Wt.x)+r.y*Math.abs(Wt.y)+r.z*Math.abs(Wt.z),o=e.dot(Wt),l=n.dot(Wt),c=i.dot(Wt);if(Math.max(-Math.max(o,l,c),Math.min(o,l,c))>a)return!1}return!0}const qt=new Pt,Xt=new Lt,Yt=new Lt,Zt=new Lt;class Jt{constructor(t=new Lt,e=-1){this.center=t,this.radius=e}set(t,e){return this.center.copy(t),this.radius=e,this}setFromPoints(t,e){const n=this.center;void 0!==e?n.copy(e):qt.setFromPoints(t).getCenter(n);let i=0;for(let e=0,r=t.length;ethis.radius*this.radius&&(e.sub(this.center).normalize(),e.multiplyScalar(this.radius).add(this.center)),e}getBoundingBox(t){return void 0===t&&(console.warn("THREE.Sphere: .getBoundingBox() target is now required"),t=new Pt),this.isEmpty()?(t.makeEmpty(),t):(t.set(this.center,this.center),t.expandByScalar(this.radius),t)}applyMatrix4(t){return this.center.applyMatrix4(t),this.radius=this.radius*t.getMaxScaleOnAxis(),this}translate(t){return this.center.add(t),this}expandByPoint(t){Zt.subVectors(t,this.center);const e=Zt.lengthSq();if(e>this.radius*this.radius){const t=Math.sqrt(e),n=.5*(t-this.radius);this.center.add(Zt.multiplyScalar(n/t)),this.radius+=n}return this}union(t){return Yt.subVectors(t.center,this.center).normalize().multiplyScalar(t.radius),this.expandByPoint(Xt.copy(t.center).add(Yt)),this.expandByPoint(Xt.copy(t.center).sub(Yt)),this}equals(t){return t.center.equals(this.center)&&t.radius===this.radius}clone(){return(new this.constructor).copy(this)}}const Qt=new Lt,Kt=new Lt,$t=new Lt,te=new Lt,ee=new Lt,ne=new Lt,ie=new Lt;class re{constructor(t=new Lt,e=new Lt(0,0,-1)){this.origin=t,this.direction=e}set(t,e){return this.origin.copy(t),this.direction.copy(e),this}copy(t){return this.origin.copy(t.origin),this.direction.copy(t.direction),this}at(t,e){return void 0===e&&(console.warn("THREE.Ray: .at() target is now required"),e=new Lt),e.copy(this.direction).multiplyScalar(t).add(this.origin)}lookAt(t){return this.direction.copy(t).sub(this.origin).normalize(),this}recast(t){return this.origin.copy(this.at(t,Qt)),this}closestPointToPoint(t,e){void 0===e&&(console.warn("THREE.Ray: .closestPointToPoint() target is now required"),e=new Lt),e.subVectors(t,this.origin);const n=e.dot(this.direction);return n<0?e.copy(this.origin):e.copy(this.direction).multiplyScalar(n).add(this.origin)}distanceToPoint(t){return Math.sqrt(this.distanceSqToPoint(t))}distanceSqToPoint(t){const e=Qt.subVectors(t,this.origin).dot(this.direction);return e<0?this.origin.distanceToSquared(t):(Qt.copy(this.direction).multiplyScalar(e).add(this.origin),Qt.distanceToSquared(t))}distanceSqToSegment(t,e,n,i){Kt.copy(t).add(e).multiplyScalar(.5),$t.copy(e).sub(t).normalize(),te.copy(this.origin).sub(Kt);const r=.5*t.distanceTo(e),s=-this.direction.dot($t),a=te.dot(this.direction),o=-te.dot($t),l=te.lengthSq(),c=Math.abs(1-s*s);let h,u,d,p;if(c>0)if(h=s*o-a,u=s*a-o,p=r*c,h>=0)if(u>=-p)if(u<=p){const t=1/c;h*=t,u*=t,d=h*(h+s*u+2*a)+u*(s*h+u+2*o)+l}else u=r,h=Math.max(0,-(s*u+a)),d=-h*h+u*(u+2*o)+l;else u=-r,h=Math.max(0,-(s*u+a)),d=-h*h+u*(u+2*o)+l;else u<=-p?(h=Math.max(0,-(-s*r+a)),u=h>0?-r:Math.min(Math.max(-r,-o),r),d=-h*h+u*(u+2*o)+l):u<=p?(h=0,u=Math.min(Math.max(-r,-o),r),d=u*(u+2*o)+l):(h=Math.max(0,-(s*r+a)),u=h>0?r:Math.min(Math.max(-r,-o),r),d=-h*h+u*(u+2*o)+l);else u=s>0?-r:r,h=Math.max(0,-(s*u+a)),d=-h*h+u*(u+2*o)+l;return n&&n.copy(this.direction).multiplyScalar(h).add(this.origin),i&&i.copy($t).multiplyScalar(u).add(Kt),d}intersectSphere(t,e){Qt.subVectors(t.center,this.origin);const n=Qt.dot(this.direction),i=Qt.dot(Qt)-n*n,r=t.radius*t.radius;if(i>r)return null;const s=Math.sqrt(r-i),a=n-s,o=n+s;return a<0&&o<0?null:a<0?this.at(o,e):this.at(a,e)}intersectsSphere(t){return this.distanceSqToPoint(t.center)<=t.radius*t.radius}distanceToPlane(t){const e=t.normal.dot(this.direction);if(0===e)return 0===t.distanceToPoint(this.origin)?0:null;const n=-(this.origin.dot(t.normal)+t.constant)/e;return n>=0?n:null}intersectPlane(t,e){const n=this.distanceToPlane(t);return null===n?null:this.at(n,e)}intersectsPlane(t){const e=t.distanceToPoint(this.origin);if(0===e)return!0;return t.normal.dot(this.direction)*e<0}intersectBox(t,e){let n,i,r,s,a,o;const l=1/this.direction.x,c=1/this.direction.y,h=1/this.direction.z,u=this.origin;return l>=0?(n=(t.min.x-u.x)*l,i=(t.max.x-u.x)*l):(n=(t.max.x-u.x)*l,i=(t.min.x-u.x)*l),c>=0?(r=(t.min.y-u.y)*c,s=(t.max.y-u.y)*c):(r=(t.max.y-u.y)*c,s=(t.min.y-u.y)*c),n>s||r>i?null:((r>n||n!=n)&&(n=r),(s=0?(a=(t.min.z-u.z)*h,o=(t.max.z-u.z)*h):(a=(t.max.z-u.z)*h,o=(t.min.z-u.z)*h),n>o||a>i?null:((a>n||n!=n)&&(n=a),(o=0?n:i,e)))}intersectsBox(t){return null!==this.intersectBox(t,Qt)}intersectTriangle(t,e,n,i,r){ee.subVectors(e,t),ne.subVectors(n,t),ie.crossVectors(ee,ne);let s,a=this.direction.dot(ie);if(a>0){if(i)return null;s=1}else{if(!(a<0))return null;s=-1,a=-a}te.subVectors(this.origin,t);const o=s*this.direction.dot(ne.crossVectors(te,ne));if(o<0)return null;const l=s*this.direction.dot(ee.cross(te));if(l<0)return null;if(o+l>a)return null;const c=-s*te.dot(ie);return c<0?null:this.at(c/a,r)}applyMatrix4(t){return this.origin.applyMatrix4(t),this.direction.transformDirection(t),this}equals(t){return t.origin.equals(this.origin)&&t.direction.equals(this.direction)}clone(){return(new this.constructor).copy(this)}}class se{constructor(){this.elements=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],arguments.length>0&&console.error("THREE.Matrix4: the constructor no longer reads arguments. use .set() instead.")}set(t,e,n,i,r,s,a,o,l,c,h,u,d,p,m,f){const g=this.elements;return g[0]=t,g[4]=e,g[8]=n,g[12]=i,g[1]=r,g[5]=s,g[9]=a,g[13]=o,g[2]=l,g[6]=c,g[10]=h,g[14]=u,g[3]=d,g[7]=p,g[11]=m,g[15]=f,this}identity(){return this.set(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1),this}clone(){return(new se).fromArray(this.elements)}copy(t){const e=this.elements,n=t.elements;return e[0]=n[0],e[1]=n[1],e[2]=n[2],e[3]=n[3],e[4]=n[4],e[5]=n[5],e[6]=n[6],e[7]=n[7],e[8]=n[8],e[9]=n[9],e[10]=n[10],e[11]=n[11],e[12]=n[12],e[13]=n[13],e[14]=n[14],e[15]=n[15],this}copyPosition(t){const e=this.elements,n=t.elements;return e[12]=n[12],e[13]=n[13],e[14]=n[14],this}setFromMatrix3(t){const e=t.elements;return this.set(e[0],e[3],e[6],0,e[1],e[4],e[7],0,e[2],e[5],e[8],0,0,0,0,1),this}extractBasis(t,e,n){return t.setFromMatrixColumn(this,0),e.setFromMatrixColumn(this,1),n.setFromMatrixColumn(this,2),this}makeBasis(t,e,n){return this.set(t.x,e.x,n.x,0,t.y,e.y,n.y,0,t.z,e.z,n.z,0,0,0,0,1),this}extractRotation(t){const e=this.elements,n=t.elements,i=1/ae.setFromMatrixColumn(t,0).length(),r=1/ae.setFromMatrixColumn(t,1).length(),s=1/ae.setFromMatrixColumn(t,2).length();return e[0]=n[0]*i,e[1]=n[1]*i,e[2]=n[2]*i,e[3]=0,e[4]=n[4]*r,e[5]=n[5]*r,e[6]=n[6]*r,e[7]=0,e[8]=n[8]*s,e[9]=n[9]*s,e[10]=n[10]*s,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,this}makeRotationFromEuler(t){t&&t.isEuler||console.error("THREE.Matrix4: .makeRotationFromEuler() now expects a Euler rotation rather than a Vector3 and order.");const e=this.elements,n=t.x,i=t.y,r=t.z,s=Math.cos(n),a=Math.sin(n),o=Math.cos(i),l=Math.sin(i),c=Math.cos(r),h=Math.sin(r);if("XYZ"===t.order){const t=s*c,n=s*h,i=a*c,r=a*h;e[0]=o*c,e[4]=-o*h,e[8]=l,e[1]=n+i*l,e[5]=t-r*l,e[9]=-a*o,e[2]=r-t*l,e[6]=i+n*l,e[10]=s*o}else if("YXZ"===t.order){const t=o*c,n=o*h,i=l*c,r=l*h;e[0]=t+r*a,e[4]=i*a-n,e[8]=s*l,e[1]=s*h,e[5]=s*c,e[9]=-a,e[2]=n*a-i,e[6]=r+t*a,e[10]=s*o}else if("ZXY"===t.order){const t=o*c,n=o*h,i=l*c,r=l*h;e[0]=t-r*a,e[4]=-s*h,e[8]=i+n*a,e[1]=n+i*a,e[5]=s*c,e[9]=r-t*a,e[2]=-s*l,e[6]=a,e[10]=s*o}else if("ZYX"===t.order){const t=s*c,n=s*h,i=a*c,r=a*h;e[0]=o*c,e[4]=i*l-n,e[8]=t*l+r,e[1]=o*h,e[5]=r*l+t,e[9]=n*l-i,e[2]=-l,e[6]=a*o,e[10]=s*o}else if("YZX"===t.order){const t=s*o,n=s*l,i=a*o,r=a*l;e[0]=o*c,e[4]=r-t*h,e[8]=i*h+n,e[1]=h,e[5]=s*c,e[9]=-a*c,e[2]=-l*c,e[6]=n*h+i,e[10]=t-r*h}else if("XZY"===t.order){const t=s*o,n=s*l,i=a*o,r=a*l;e[0]=o*c,e[4]=-h,e[8]=l*c,e[1]=t*h+r,e[5]=s*c,e[9]=n*h-i,e[2]=i*h-n,e[6]=a*c,e[10]=r*h+t}return e[3]=0,e[7]=0,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,this}makeRotationFromQuaternion(t){return this.compose(le,t,ce)}lookAt(t,e,n){const i=this.elements;return de.subVectors(t,e),0===de.lengthSq()&&(de.z=1),de.normalize(),he.crossVectors(n,de),0===he.lengthSq()&&(1===Math.abs(n.z)?de.x+=1e-4:de.z+=1e-4,de.normalize(),he.crossVectors(n,de)),he.normalize(),ue.crossVectors(de,he),i[0]=he.x,i[4]=ue.x,i[8]=de.x,i[1]=he.y,i[5]=ue.y,i[9]=de.y,i[2]=he.z,i[6]=ue.z,i[10]=de.z,this}multiply(t,e){return void 0!==e?(console.warn("THREE.Matrix4: .multiply() now only accepts one argument. Use .multiplyMatrices( a, b ) instead."),this.multiplyMatrices(t,e)):this.multiplyMatrices(this,t)}premultiply(t){return this.multiplyMatrices(t,this)}multiplyMatrices(t,e){const n=t.elements,i=e.elements,r=this.elements,s=n[0],a=n[4],o=n[8],l=n[12],c=n[1],h=n[5],u=n[9],d=n[13],p=n[2],m=n[6],f=n[10],g=n[14],v=n[3],y=n[7],x=n[11],_=n[15],w=i[0],b=i[4],M=i[8],S=i[12],T=i[1],E=i[5],A=i[9],L=i[13],R=i[2],C=i[6],P=i[10],D=i[14],I=i[3],N=i[7],B=i[11],z=i[15];return r[0]=s*w+a*T+o*R+l*I,r[4]=s*b+a*E+o*C+l*N,r[8]=s*M+a*A+o*P+l*B,r[12]=s*S+a*L+o*D+l*z,r[1]=c*w+h*T+u*R+d*I,r[5]=c*b+h*E+u*C+d*N,r[9]=c*M+h*A+u*P+d*B,r[13]=c*S+h*L+u*D+d*z,r[2]=p*w+m*T+f*R+g*I,r[6]=p*b+m*E+f*C+g*N,r[10]=p*M+m*A+f*P+g*B,r[14]=p*S+m*L+f*D+g*z,r[3]=v*w+y*T+x*R+_*I,r[7]=v*b+y*E+x*C+_*N,r[11]=v*M+y*A+x*P+_*B,r[15]=v*S+y*L+x*D+_*z,this}multiplyScalar(t){const e=this.elements;return e[0]*=t,e[4]*=t,e[8]*=t,e[12]*=t,e[1]*=t,e[5]*=t,e[9]*=t,e[13]*=t,e[2]*=t,e[6]*=t,e[10]*=t,e[14]*=t,e[3]*=t,e[7]*=t,e[11]*=t,e[15]*=t,this}determinant(){const t=this.elements,e=t[0],n=t[4],i=t[8],r=t[12],s=t[1],a=t[5],o=t[9],l=t[13],c=t[2],h=t[6],u=t[10],d=t[14];return t[3]*(+r*o*h-i*l*h-r*a*u+n*l*u+i*a*d-n*o*d)+t[7]*(+e*o*d-e*l*u+r*s*u-i*s*d+i*l*c-r*o*c)+t[11]*(+e*l*h-e*a*d-r*s*h+n*s*d+r*a*c-n*l*c)+t[15]*(-i*a*c-e*o*h+e*a*u+i*s*h-n*s*u+n*o*c)}transpose(){const t=this.elements;let e;return e=t[1],t[1]=t[4],t[4]=e,e=t[2],t[2]=t[8],t[8]=e,e=t[6],t[6]=t[9],t[9]=e,e=t[3],t[3]=t[12],t[12]=e,e=t[7],t[7]=t[13],t[13]=e,e=t[11],t[11]=t[14],t[14]=e,this}setPosition(t,e,n){const i=this.elements;return t.isVector3?(i[12]=t.x,i[13]=t.y,i[14]=t.z):(i[12]=t,i[13]=e,i[14]=n),this}invert(){const t=this.elements,e=t[0],n=t[1],i=t[2],r=t[3],s=t[4],a=t[5],o=t[6],l=t[7],c=t[8],h=t[9],u=t[10],d=t[11],p=t[12],m=t[13],f=t[14],g=t[15],v=h*f*l-m*u*l+m*o*d-a*f*d-h*o*g+a*u*g,y=p*u*l-c*f*l-p*o*d+s*f*d+c*o*g-s*u*g,x=c*m*l-p*h*l+p*a*d-s*m*d-c*a*g+s*h*g,_=p*h*o-c*m*o-p*a*u+s*m*u+c*a*f-s*h*f,w=e*v+n*y+i*x+r*_;if(0===w)return this.set(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);const b=1/w;return t[0]=v*b,t[1]=(m*u*r-h*f*r-m*i*d+n*f*d+h*i*g-n*u*g)*b,t[2]=(a*f*r-m*o*r+m*i*l-n*f*l-a*i*g+n*o*g)*b,t[3]=(h*o*r-a*u*r-h*i*l+n*u*l+a*i*d-n*o*d)*b,t[4]=y*b,t[5]=(c*f*r-p*u*r+p*i*d-e*f*d-c*i*g+e*u*g)*b,t[6]=(p*o*r-s*f*r-p*i*l+e*f*l+s*i*g-e*o*g)*b,t[7]=(s*u*r-c*o*r+c*i*l-e*u*l-s*i*d+e*o*d)*b,t[8]=x*b,t[9]=(p*h*r-c*m*r-p*n*d+e*m*d+c*n*g-e*h*g)*b,t[10]=(s*m*r-p*a*r+p*n*l-e*m*l-s*n*g+e*a*g)*b,t[11]=(c*a*r-s*h*r-c*n*l+e*h*l+s*n*d-e*a*d)*b,t[12]=_*b,t[13]=(c*m*i-p*h*i+p*n*u-e*m*u-c*n*f+e*h*f)*b,t[14]=(p*a*i-s*m*i-p*n*o+e*m*o+s*n*f-e*a*f)*b,t[15]=(s*h*i-c*a*i+c*n*o-e*h*o-s*n*u+e*a*u)*b,this}scale(t){const e=this.elements,n=t.x,i=t.y,r=t.z;return e[0]*=n,e[4]*=i,e[8]*=r,e[1]*=n,e[5]*=i,e[9]*=r,e[2]*=n,e[6]*=i,e[10]*=r,e[3]*=n,e[7]*=i,e[11]*=r,this}getMaxScaleOnAxis(){const t=this.elements,e=t[0]*t[0]+t[1]*t[1]+t[2]*t[2],n=t[4]*t[4]+t[5]*t[5]+t[6]*t[6],i=t[8]*t[8]+t[9]*t[9]+t[10]*t[10];return Math.sqrt(Math.max(e,n,i))}makeTranslation(t,e,n){return this.set(1,0,0,t,0,1,0,e,0,0,1,n,0,0,0,1),this}makeRotationX(t){const e=Math.cos(t),n=Math.sin(t);return this.set(1,0,0,0,0,e,-n,0,0,n,e,0,0,0,0,1),this}makeRotationY(t){const e=Math.cos(t),n=Math.sin(t);return this.set(e,0,n,0,0,1,0,0,-n,0,e,0,0,0,0,1),this}makeRotationZ(t){const e=Math.cos(t),n=Math.sin(t);return this.set(e,-n,0,0,n,e,0,0,0,0,1,0,0,0,0,1),this}makeRotationAxis(t,e){const n=Math.cos(e),i=Math.sin(e),r=1-n,s=t.x,a=t.y,o=t.z,l=r*s,c=r*a;return this.set(l*s+n,l*a-i*o,l*o+i*a,0,l*a+i*o,c*a+n,c*o-i*s,0,l*o-i*a,c*o+i*s,r*o*o+n,0,0,0,0,1),this}makeScale(t,e,n){return this.set(t,0,0,0,0,e,0,0,0,0,n,0,0,0,0,1),this}makeShear(t,e,n){return this.set(1,e,n,0,t,1,n,0,t,e,1,0,0,0,0,1),this}compose(t,e,n){const i=this.elements,r=e._x,s=e._y,a=e._z,o=e._w,l=r+r,c=s+s,h=a+a,u=r*l,d=r*c,p=r*h,m=s*c,f=s*h,g=a*h,v=o*l,y=o*c,x=o*h,_=n.x,w=n.y,b=n.z;return i[0]=(1-(m+g))*_,i[1]=(d+x)*_,i[2]=(p-y)*_,i[3]=0,i[4]=(d-x)*w,i[5]=(1-(u+g))*w,i[6]=(f+v)*w,i[7]=0,i[8]=(p+y)*b,i[9]=(f-v)*b,i[10]=(1-(u+m))*b,i[11]=0,i[12]=t.x,i[13]=t.y,i[14]=t.z,i[15]=1,this}decompose(t,e,n){const i=this.elements;let r=ae.set(i[0],i[1],i[2]).length();const s=ae.set(i[4],i[5],i[6]).length(),a=ae.set(i[8],i[9],i[10]).length();this.determinant()<0&&(r=-r),t.x=i[12],t.y=i[13],t.z=i[14],oe.copy(this);const o=1/r,l=1/s,c=1/a;return oe.elements[0]*=o,oe.elements[1]*=o,oe.elements[2]*=o,oe.elements[4]*=l,oe.elements[5]*=l,oe.elements[6]*=l,oe.elements[8]*=c,oe.elements[9]*=c,oe.elements[10]*=c,e.setFromRotationMatrix(oe),n.x=r,n.y=s,n.z=a,this}makePerspective(t,e,n,i,r,s){void 0===s&&console.warn("THREE.Matrix4: .makePerspective() has been redefined and has a new signature. Please check the docs.");const a=this.elements,o=2*r/(e-t),l=2*r/(n-i),c=(e+t)/(e-t),h=(n+i)/(n-i),u=-(s+r)/(s-r),d=-2*s*r/(s-r);return a[0]=o,a[4]=0,a[8]=c,a[12]=0,a[1]=0,a[5]=l,a[9]=h,a[13]=0,a[2]=0,a[6]=0,a[10]=u,a[14]=d,a[3]=0,a[7]=0,a[11]=-1,a[15]=0,this}makeOrthographic(t,e,n,i,r,s){const a=this.elements,o=1/(e-t),l=1/(n-i),c=1/(s-r),h=(e+t)*o,u=(n+i)*l,d=(s+r)*c;return a[0]=2*o,a[4]=0,a[8]=0,a[12]=-h,a[1]=0,a[5]=2*l,a[9]=0,a[13]=-u,a[2]=0,a[6]=0,a[10]=-2*c,a[14]=-d,a[3]=0,a[7]=0,a[11]=0,a[15]=1,this}equals(t){const e=this.elements,n=t.elements;for(let t=0;t<16;t++)if(e[t]!==n[t])return!1;return!0}fromArray(t,e=0){for(let n=0;n<16;n++)this.elements[n]=t[n+e];return this}toArray(t=[],e=0){const n=this.elements;return t[e]=n[0],t[e+1]=n[1],t[e+2]=n[2],t[e+3]=n[3],t[e+4]=n[4],t[e+5]=n[5],t[e+6]=n[6],t[e+7]=n[7],t[e+8]=n[8],t[e+9]=n[9],t[e+10]=n[10],t[e+11]=n[11],t[e+12]=n[12],t[e+13]=n[13],t[e+14]=n[14],t[e+15]=n[15],t}}se.prototype.isMatrix4=!0;const ae=new Lt,oe=new se,le=new Lt(0,0,0),ce=new Lt(1,1,1),he=new Lt,ue=new Lt,de=new Lt,pe=new se,me=new At;class fe{constructor(t=0,e=0,n=0,i=fe.DefaultOrder){this._x=t,this._y=e,this._z=n,this._order=i}get x(){return this._x}set x(t){this._x=t,this._onChangeCallback()}get y(){return this._y}set y(t){this._y=t,this._onChangeCallback()}get z(){return this._z}set z(t){this._z=t,this._onChangeCallback()}get order(){return this._order}set order(t){this._order=t,this._onChangeCallback()}set(t,e,n,i){return this._x=t,this._y=e,this._z=n,this._order=i||this._order,this._onChangeCallback(),this}clone(){return new this.constructor(this._x,this._y,this._z,this._order)}copy(t){return this._x=t._x,this._y=t._y,this._z=t._z,this._order=t._order,this._onChangeCallback(),this}setFromRotationMatrix(t,e,n){const i=t.elements,r=i[0],s=i[4],a=i[8],o=i[1],l=i[5],c=i[9],h=i[2],u=i[6],d=i[10];switch(e=e||this._order){case"XYZ":this._y=Math.asin(ht(a,-1,1)),Math.abs(a)<.9999999?(this._x=Math.atan2(-c,d),this._z=Math.atan2(-s,r)):(this._x=Math.atan2(u,l),this._z=0);break;case"YXZ":this._x=Math.asin(-ht(c,-1,1)),Math.abs(c)<.9999999?(this._y=Math.atan2(a,d),this._z=Math.atan2(o,l)):(this._y=Math.atan2(-h,r),this._z=0);break;case"ZXY":this._x=Math.asin(ht(u,-1,1)),Math.abs(u)<.9999999?(this._y=Math.atan2(-h,d),this._z=Math.atan2(-s,l)):(this._y=0,this._z=Math.atan2(o,r));break;case"ZYX":this._y=Math.asin(-ht(h,-1,1)),Math.abs(h)<.9999999?(this._x=Math.atan2(u,d),this._z=Math.atan2(o,r)):(this._x=0,this._z=Math.atan2(-s,l));break;case"YZX":this._z=Math.asin(ht(o,-1,1)),Math.abs(o)<.9999999?(this._x=Math.atan2(-c,l),this._y=Math.atan2(-h,r)):(this._x=0,this._y=Math.atan2(a,d));break;case"XZY":this._z=Math.asin(-ht(s,-1,1)),Math.abs(s)<.9999999?(this._x=Math.atan2(u,l),this._y=Math.atan2(a,r)):(this._x=Math.atan2(-c,d),this._y=0);break;default:console.warn("THREE.Euler: .setFromRotationMatrix() encountered an unknown order: "+e)}return this._order=e,!1!==n&&this._onChangeCallback(),this}setFromQuaternion(t,e,n){return pe.makeRotationFromQuaternion(t),this.setFromRotationMatrix(pe,e,n)}setFromVector3(t,e){return this.set(t.x,t.y,t.z,e||this._order)}reorder(t){return me.setFromEuler(this),this.setFromQuaternion(me,t)}equals(t){return t._x===this._x&&t._y===this._y&&t._z===this._z&&t._order===this._order}fromArray(t){return this._x=t[0],this._y=t[1],this._z=t[2],void 0!==t[3]&&(this._order=t[3]),this._onChangeCallback(),this}toArray(t=[],e=0){return t[e]=this._x,t[e+1]=this._y,t[e+2]=this._z,t[e+3]=this._order,t}toVector3(t){return t?t.set(this._x,this._y,this._z):new Lt(this._x,this._y,this._z)}_onChange(t){return this._onChangeCallback=t,this}_onChangeCallback(){}}fe.prototype.isEuler=!0,fe.DefaultOrder="XYZ",fe.RotationOrders=["XYZ","YZX","ZXY","XZY","YXZ","ZYX"];class ge{constructor(){this.mask=1}set(t){this.mask=1<1){for(let t=0;t1){for(let t=0;t0){i.children=[];for(let e=0;e0){i.animations=[];for(let e=0;e0&&(n.geometries=e),i.length>0&&(n.materials=i),r.length>0&&(n.textures=r),a.length>0&&(n.images=a),o.length>0&&(n.shapes=o),l.length>0&&(n.skeletons=l),c.length>0&&(n.animations=c)}return n.object=i,n;function s(t){const e=[];for(const n in t){const i=t[n];delete i.metadata,e.push(i)}return e}}clone(t){return(new this.constructor).copy(this,t)}copy(t,e=!0){if(this.name=t.name,this.up.copy(t.up),this.position.copy(t.position),this.rotation.order=t.rotation.order,this.quaternion.copy(t.quaternion),this.scale.copy(t.scale),this.matrix.copy(t.matrix),this.matrixWorld.copy(t.matrixWorld),this.matrixAutoUpdate=t.matrixAutoUpdate,this.matrixWorldNeedsUpdate=t.matrixWorldNeedsUpdate,this.layers.mask=t.layers.mask,this.visible=t.visible,this.castShadow=t.castShadow,this.receiveShadow=t.receiveShadow,this.frustumCulled=t.frustumCulled,this.renderOrder=t.renderOrder,this.userData=JSON.parse(JSON.stringify(t.userData)),!0===e)for(let e=0;e1?null:e.copy(n).multiplyScalar(r).add(t.start)}intersectsLine(t){const e=this.distanceToPoint(t.start),n=this.distanceToPoint(t.end);return e<0&&n>0||n<0&&e>0}intersectsBox(t){return t.intersectsPlane(this)}intersectsSphere(t){return t.intersectsPlane(this)}coplanarPoint(t){return void 0===t&&(console.warn("THREE.Plane: .coplanarPoint() target is now required"),t=new Lt),t.copy(this.normal).multiplyScalar(-this.constant)}applyMatrix4(t,e){const n=e||Ie.getNormalMatrix(t),i=this.coplanarPoint(Pe).applyMatrix4(t),r=this.normal.applyMatrix3(n).normalize();return this.constant=-i.dot(r),this}translate(t){return this.constant-=t.dot(this.normal),this}equals(t){return t.normal.equals(this.normal)&&t.constant===this.constant}clone(){return(new this.constructor).copy(this)}}Ne.prototype.isPlane=!0;const Be=new Lt,ze=new Lt,Fe=new Lt,Oe=new Lt,He=new Lt,Ge=new Lt,Ue=new Lt,ke=new Lt,Ve=new Lt,We=new Lt;class je{constructor(t=new Lt,e=new Lt,n=new Lt){this.a=t,this.b=e,this.c=n}static getNormal(t,e,n,i){void 0===i&&(console.warn("THREE.Triangle: .getNormal() target is now required"),i=new Lt),i.subVectors(n,e),Be.subVectors(t,e),i.cross(Be);const r=i.lengthSq();return r>0?i.multiplyScalar(1/Math.sqrt(r)):i.set(0,0,0)}static getBarycoord(t,e,n,i,r){Be.subVectors(i,e),ze.subVectors(n,e),Fe.subVectors(t,e);const s=Be.dot(Be),a=Be.dot(ze),o=Be.dot(Fe),l=ze.dot(ze),c=ze.dot(Fe),h=s*l-a*a;if(void 0===r&&(console.warn("THREE.Triangle: .getBarycoord() target is now required"),r=new Lt),0===h)return r.set(-2,-1,-1);const u=1/h,d=(l*o-a*c)*u,p=(s*c-a*o)*u;return r.set(1-d-p,p,d)}static containsPoint(t,e,n,i){return this.getBarycoord(t,e,n,i,Oe),Oe.x>=0&&Oe.y>=0&&Oe.x+Oe.y<=1}static getUV(t,e,n,i,r,s,a,o){return this.getBarycoord(t,e,n,i,Oe),o.set(0,0),o.addScaledVector(r,Oe.x),o.addScaledVector(s,Oe.y),o.addScaledVector(a,Oe.z),o}static isFrontFacing(t,e,n,i){return Be.subVectors(n,e),ze.subVectors(t,e),Be.cross(ze).dot(i)<0}set(t,e,n){return this.a.copy(t),this.b.copy(e),this.c.copy(n),this}setFromPointsAndIndices(t,e,n,i){return this.a.copy(t[e]),this.b.copy(t[n]),this.c.copy(t[i]),this}clone(){return(new this.constructor).copy(this)}copy(t){return this.a.copy(t.a),this.b.copy(t.b),this.c.copy(t.c),this}getArea(){return Be.subVectors(this.c,this.b),ze.subVectors(this.a,this.b),.5*Be.cross(ze).length()}getMidpoint(t){return void 0===t&&(console.warn("THREE.Triangle: .getMidpoint() target is now required"),t=new Lt),t.addVectors(this.a,this.b).add(this.c).multiplyScalar(1/3)}getNormal(t){return je.getNormal(this.a,this.b,this.c,t)}getPlane(t){return void 0===t&&(console.warn("THREE.Triangle: .getPlane() target is now required"),t=new Ne),t.setFromCoplanarPoints(this.a,this.b,this.c)}getBarycoord(t,e){return je.getBarycoord(t,this.a,this.b,this.c,e)}getUV(t,e,n,i,r){return je.getUV(t,this.a,this.b,this.c,e,n,i,r)}containsPoint(t){return je.containsPoint(t,this.a,this.b,this.c)}isFrontFacing(t){return je.isFrontFacing(this.a,this.b,this.c,t)}intersectsBox(t){return t.intersectsTriangle(this)}closestPointToPoint(t,e){void 0===e&&(console.warn("THREE.Triangle: .closestPointToPoint() target is now required"),e=new Lt);const n=this.a,i=this.b,r=this.c;let s,a;He.subVectors(i,n),Ge.subVectors(r,n),ke.subVectors(t,n);const o=He.dot(ke),l=Ge.dot(ke);if(o<=0&&l<=0)return e.copy(n);Ve.subVectors(t,i);const c=He.dot(Ve),h=Ge.dot(Ve);if(c>=0&&h<=c)return e.copy(i);const u=o*h-c*l;if(u<=0&&o>=0&&c<=0)return s=o/(o-c),e.copy(n).addScaledVector(He,s);We.subVectors(t,r);const d=He.dot(We),p=Ge.dot(We);if(p>=0&&d<=p)return e.copy(r);const m=d*l-o*p;if(m<=0&&l>=0&&p<=0)return a=l/(l-p),e.copy(n).addScaledVector(Ge,a);const f=c*p-d*h;if(f<=0&&h-c>=0&&d-p>=0)return Ue.subVectors(r,i),a=(h-c)/(h-c+(d-p)),e.copy(i).addScaledVector(Ue,a);const g=1/(f+m+u);return s=m*g,a=u*g,e.copy(n).addScaledVector(He,s).addScaledVector(Ge,a)}equals(t){return t.a.equals(this.a)&&t.b.equals(this.b)&&t.c.equals(this.c)}}let qe=0;function Xe(){Object.defineProperty(this,"id",{value:qe++}),this.uuid=ct(),this.name="",this.type="Material",this.fog=!0,this.blending=1,this.side=0,this.vertexColors=!1,this.opacity=1,this.transparent=!1,this.blendSrc=204,this.blendDst=205,this.blendEquation=n,this.blendSrcAlpha=null,this.blendDstAlpha=null,this.blendEquationAlpha=null,this.depthFunc=3,this.depthTest=!0,this.depthWrite=!0,this.stencilWriteMask=255,this.stencilFunc=519,this.stencilRef=0,this.stencilFuncMask=255,this.stencilFail=tt,this.stencilZFail=tt,this.stencilZPass=tt,this.stencilWrite=!1,this.clippingPlanes=null,this.clipIntersection=!1,this.clipShadows=!1,this.shadowSide=null,this.colorWrite=!0,this.precision=null,this.polygonOffset=!1,this.polygonOffsetFactor=0,this.polygonOffsetUnits=0,this.dithering=!1,this.alphaTest=0,this.alphaToCoverage=!1,this.premultipliedAlpha=!1,this.visible=!0,this.toneMapped=!0,this.userData={},this.version=0}Xe.prototype=Object.assign(Object.create(rt.prototype),{constructor:Xe,isMaterial:!0,onBuild:function(){},onBeforeCompile:function(){},customProgramCacheKey:function(){return this.onBeforeCompile.toString()},setValues:function(t){if(void 0!==t)for(const e in t){const n=t[e];if(void 0===n){console.warn("THREE.Material: '"+e+"' parameter is undefined.");continue}if("shading"===e){console.warn("THREE."+this.type+": .shading has been removed. Use the boolean .flatShading instead."),this.flatShading=1===n;continue}const i=this[e];void 0!==i?i&&i.isColor?i.set(n):i&&i.isVector3&&n&&n.isVector3?i.copy(n):this[e]=n:console.warn("THREE."+this.type+": '"+e+"' is not a property of this material.")}},toJSON:function(t){const e=void 0===t||"string"==typeof t;e&&(t={textures:{},images:{}});const n={metadata:{version:4.5,type:"Material",generator:"Material.toJSON"}};function i(t){const e=[];for(const n in t){const i=t[n];delete i.metadata,e.push(i)}return e}if(n.uuid=this.uuid,n.type=this.type,""!==this.name&&(n.name=this.name),this.color&&this.color.isColor&&(n.color=this.color.getHex()),void 0!==this.roughness&&(n.roughness=this.roughness),void 0!==this.metalness&&(n.metalness=this.metalness),this.sheen&&this.sheen.isColor&&(n.sheen=this.sheen.getHex()),this.emissive&&this.emissive.isColor&&(n.emissive=this.emissive.getHex()),this.emissiveIntensity&&1!==this.emissiveIntensity&&(n.emissiveIntensity=this.emissiveIntensity),this.specular&&this.specular.isColor&&(n.specular=this.specular.getHex()),void 0!==this.shininess&&(n.shininess=this.shininess),void 0!==this.clearcoat&&(n.clearcoat=this.clearcoat),void 0!==this.clearcoatRoughness&&(n.clearcoatRoughness=this.clearcoatRoughness),this.clearcoatMap&&this.clearcoatMap.isTexture&&(n.clearcoatMap=this.clearcoatMap.toJSON(t).uuid),this.clearcoatRoughnessMap&&this.clearcoatRoughnessMap.isTexture&&(n.clearcoatRoughnessMap=this.clearcoatRoughnessMap.toJSON(t).uuid),this.clearcoatNormalMap&&this.clearcoatNormalMap.isTexture&&(n.clearcoatNormalMap=this.clearcoatNormalMap.toJSON(t).uuid,n.clearcoatNormalScale=this.clearcoatNormalScale.toArray()),this.map&&this.map.isTexture&&(n.map=this.map.toJSON(t).uuid),this.matcap&&this.matcap.isTexture&&(n.matcap=this.matcap.toJSON(t).uuid),this.alphaMap&&this.alphaMap.isTexture&&(n.alphaMap=this.alphaMap.toJSON(t).uuid),this.lightMap&&this.lightMap.isTexture&&(n.lightMap=this.lightMap.toJSON(t).uuid,n.lightMapIntensity=this.lightMapIntensity),this.aoMap&&this.aoMap.isTexture&&(n.aoMap=this.aoMap.toJSON(t).uuid,n.aoMapIntensity=this.aoMapIntensity),this.bumpMap&&this.bumpMap.isTexture&&(n.bumpMap=this.bumpMap.toJSON(t).uuid,n.bumpScale=this.bumpScale),this.normalMap&&this.normalMap.isTexture&&(n.normalMap=this.normalMap.toJSON(t).uuid,n.normalMapType=this.normalMapType,n.normalScale=this.normalScale.toArray()),this.displacementMap&&this.displacementMap.isTexture&&(n.displacementMap=this.displacementMap.toJSON(t).uuid,n.displacementScale=this.displacementScale,n.displacementBias=this.displacementBias),this.roughnessMap&&this.roughnessMap.isTexture&&(n.roughnessMap=this.roughnessMap.toJSON(t).uuid),this.metalnessMap&&this.metalnessMap.isTexture&&(n.metalnessMap=this.metalnessMap.toJSON(t).uuid),this.emissiveMap&&this.emissiveMap.isTexture&&(n.emissiveMap=this.emissiveMap.toJSON(t).uuid),this.specularMap&&this.specularMap.isTexture&&(n.specularMap=this.specularMap.toJSON(t).uuid),this.envMap&&this.envMap.isTexture&&(n.envMap=this.envMap.toJSON(t).uuid,void 0!==this.combine&&(n.combine=this.combine)),void 0!==this.envMapIntensity&&(n.envMapIntensity=this.envMapIntensity),void 0!==this.reflectivity&&(n.reflectivity=this.reflectivity),void 0!==this.refractionRatio&&(n.refractionRatio=this.refractionRatio),this.gradientMap&&this.gradientMap.isTexture&&(n.gradientMap=this.gradientMap.toJSON(t).uuid),void 0!==this.size&&(n.size=this.size),null!==this.shadowSide&&(n.shadowSide=this.shadowSide),void 0!==this.sizeAttenuation&&(n.sizeAttenuation=this.sizeAttenuation),1!==this.blending&&(n.blending=this.blending),0!==this.side&&(n.side=this.side),this.vertexColors&&(n.vertexColors=!0),this.opacity<1&&(n.opacity=this.opacity),!0===this.transparent&&(n.transparent=this.transparent),n.depthFunc=this.depthFunc,n.depthTest=this.depthTest,n.depthWrite=this.depthWrite,n.colorWrite=this.colorWrite,n.stencilWrite=this.stencilWrite,n.stencilWriteMask=this.stencilWriteMask,n.stencilFunc=this.stencilFunc,n.stencilRef=this.stencilRef,n.stencilFuncMask=this.stencilFuncMask,n.stencilFail=this.stencilFail,n.stencilZFail=this.stencilZFail,n.stencilZPass=this.stencilZPass,this.rotation&&0!==this.rotation&&(n.rotation=this.rotation),!0===this.polygonOffset&&(n.polygonOffset=!0),0!==this.polygonOffsetFactor&&(n.polygonOffsetFactor=this.polygonOffsetFactor),0!==this.polygonOffsetUnits&&(n.polygonOffsetUnits=this.polygonOffsetUnits),this.linewidth&&1!==this.linewidth&&(n.linewidth=this.linewidth),void 0!==this.dashSize&&(n.dashSize=this.dashSize),void 0!==this.gapSize&&(n.gapSize=this.gapSize),void 0!==this.scale&&(n.scale=this.scale),!0===this.dithering&&(n.dithering=!0),this.alphaTest>0&&(n.alphaTest=this.alphaTest),!0===this.alphaToCoverage&&(n.alphaToCoverage=this.alphaToCoverage),!0===this.premultipliedAlpha&&(n.premultipliedAlpha=this.premultipliedAlpha),!0===this.wireframe&&(n.wireframe=this.wireframe),this.wireframeLinewidth>1&&(n.wireframeLinewidth=this.wireframeLinewidth),"round"!==this.wireframeLinecap&&(n.wireframeLinecap=this.wireframeLinecap),"round"!==this.wireframeLinejoin&&(n.wireframeLinejoin=this.wireframeLinejoin),!0===this.morphTargets&&(n.morphTargets=!0),!0===this.morphNormals&&(n.morphNormals=!0),!0===this.skinning&&(n.skinning=!0),!0===this.flatShading&&(n.flatShading=this.flatShading),!1===this.visible&&(n.visible=!1),!1===this.toneMapped&&(n.toneMapped=!1),"{}"!==JSON.stringify(this.userData)&&(n.userData=this.userData),e){const e=i(t.textures),r=i(t.images);e.length>0&&(n.textures=e),r.length>0&&(n.images=r)}return n},clone:function(){return(new this.constructor).copy(this)},copy:function(t){this.name=t.name,this.fog=t.fog,this.blending=t.blending,this.side=t.side,this.vertexColors=t.vertexColors,this.opacity=t.opacity,this.transparent=t.transparent,this.blendSrc=t.blendSrc,this.blendDst=t.blendDst,this.blendEquation=t.blendEquation,this.blendSrcAlpha=t.blendSrcAlpha,this.blendDstAlpha=t.blendDstAlpha,this.blendEquationAlpha=t.blendEquationAlpha,this.depthFunc=t.depthFunc,this.depthTest=t.depthTest,this.depthWrite=t.depthWrite,this.stencilWriteMask=t.stencilWriteMask,this.stencilFunc=t.stencilFunc,this.stencilRef=t.stencilRef,this.stencilFuncMask=t.stencilFuncMask,this.stencilFail=t.stencilFail,this.stencilZFail=t.stencilZFail,this.stencilZPass=t.stencilZPass,this.stencilWrite=t.stencilWrite;const e=t.clippingPlanes;let n=null;if(null!==e){const t=e.length;n=new Array(t);for(let i=0;i!==t;++i)n[i]=e[i].clone()}return this.clippingPlanes=n,this.clipIntersection=t.clipIntersection,this.clipShadows=t.clipShadows,this.shadowSide=t.shadowSide,this.colorWrite=t.colorWrite,this.precision=t.precision,this.polygonOffset=t.polygonOffset,this.polygonOffsetFactor=t.polygonOffsetFactor,this.polygonOffsetUnits=t.polygonOffsetUnits,this.dithering=t.dithering,this.alphaTest=t.alphaTest,this.alphaToCoverage=t.alphaToCoverage,this.premultipliedAlpha=t.premultipliedAlpha,this.visible=t.visible,this.toneMapped=t.toneMapped,this.userData=JSON.parse(JSON.stringify(t.userData)),this},dispose:function(){this.dispatchEvent({type:"dispose"})}}),Object.defineProperty(Xe.prototype,"needsUpdate",{set:function(t){!0===t&&this.version++}});const Ye={aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074},Ze={h:0,s:0,l:0},Je={h:0,s:0,l:0};function Qe(t,e,n){return n<0&&(n+=1),n>1&&(n-=1),n<1/6?t+6*(e-t)*n:n<.5?e:n<2/3?t+6*(e-t)*(2/3-n):t}function Ke(t){return t<.04045?.0773993808*t:Math.pow(.9478672986*t+.0521327014,2.4)}function $e(t){return t<.0031308?12.92*t:1.055*Math.pow(t,.41666)-.055}class tn{constructor(t,e,n){return void 0===e&&void 0===n?this.set(t):this.setRGB(t,e,n)}set(t){return t&&t.isColor?this.copy(t):"number"==typeof t?this.setHex(t):"string"==typeof t&&this.setStyle(t),this}setScalar(t){return this.r=t,this.g=t,this.b=t,this}setHex(t){return t=Math.floor(t),this.r=(t>>16&255)/255,this.g=(t>>8&255)/255,this.b=(255&t)/255,this}setRGB(t,e,n){return this.r=t,this.g=e,this.b=n,this}setHSL(t,e,n){if(t=ut(t,1),e=ht(e,0,1),n=ht(n,0,1),0===e)this.r=this.g=this.b=n;else{const i=n<=.5?n*(1+e):n+e-n*e,r=2*n-i;this.r=Qe(r,i,t+1/3),this.g=Qe(r,i,t),this.b=Qe(r,i,t-1/3)}return this}setStyle(t){function e(e){void 0!==e&&parseFloat(e)<1&&console.warn("THREE.Color: Alpha component of "+t+" will be ignored.")}let n;if(n=/^((?:rgb|hsl)a?)\(([^\)]*)\)/.exec(t)){let t;const i=n[1],r=n[2];switch(i){case"rgb":case"rgba":if(t=/^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(r))return this.r=Math.min(255,parseInt(t[1],10))/255,this.g=Math.min(255,parseInt(t[2],10))/255,this.b=Math.min(255,parseInt(t[3],10))/255,e(t[4]),this;if(t=/^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(r))return this.r=Math.min(100,parseInt(t[1],10))/100,this.g=Math.min(100,parseInt(t[2],10))/100,this.b=Math.min(100,parseInt(t[3],10))/100,e(t[4]),this;break;case"hsl":case"hsla":if(t=/^\s*(\d*\.?\d+)\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(r)){const n=parseFloat(t[1])/360,i=parseInt(t[2],10)/100,r=parseInt(t[3],10)/100;return e(t[4]),this.setHSL(n,i,r)}}}else if(n=/^\#([A-Fa-f\d]+)$/.exec(t)){const t=n[1],e=t.length;if(3===e)return this.r=parseInt(t.charAt(0)+t.charAt(0),16)/255,this.g=parseInt(t.charAt(1)+t.charAt(1),16)/255,this.b=parseInt(t.charAt(2)+t.charAt(2),16)/255,this;if(6===e)return this.r=parseInt(t.charAt(0)+t.charAt(1),16)/255,this.g=parseInt(t.charAt(2)+t.charAt(3),16)/255,this.b=parseInt(t.charAt(4)+t.charAt(5),16)/255,this}return t&&t.length>0?this.setColorName(t):this}setColorName(t){const e=Ye[t.toLowerCase()];return void 0!==e?this.setHex(e):console.warn("THREE.Color: Unknown color "+t),this}clone(){return new this.constructor(this.r,this.g,this.b)}copy(t){return this.r=t.r,this.g=t.g,this.b=t.b,this}copyGammaToLinear(t,e=2){return this.r=Math.pow(t.r,e),this.g=Math.pow(t.g,e),this.b=Math.pow(t.b,e),this}copyLinearToGamma(t,e=2){const n=e>0?1/e:1;return this.r=Math.pow(t.r,n),this.g=Math.pow(t.g,n),this.b=Math.pow(t.b,n),this}convertGammaToLinear(t){return this.copyGammaToLinear(this,t),this}convertLinearToGamma(t){return this.copyLinearToGamma(this,t),this}copySRGBToLinear(t){return this.r=Ke(t.r),this.g=Ke(t.g),this.b=Ke(t.b),this}copyLinearToSRGB(t){return this.r=$e(t.r),this.g=$e(t.g),this.b=$e(t.b),this}convertSRGBToLinear(){return this.copySRGBToLinear(this),this}convertLinearToSRGB(){return this.copyLinearToSRGB(this),this}getHex(){return 255*this.r<<16^255*this.g<<8^255*this.b<<0}getHexString(){return("000000"+this.getHex().toString(16)).slice(-6)}getHSL(t){void 0===t&&(console.warn("THREE.Color: .getHSL() target is now required"),t={h:0,s:0,l:0});const e=this.r,n=this.g,i=this.b,r=Math.max(e,n,i),s=Math.min(e,n,i);let a,o;const l=(s+r)/2;if(s===r)a=0,o=0;else{const t=r-s;switch(o=l<=.5?t/(r+s):t/(2-r-s),r){case e:a=(n-i)/t+(ne&&(e=t[n]);return e}const vn={Int8Array:Int8Array,Uint8Array:Uint8Array,Uint8ClampedArray:Uint8ClampedArray,Int16Array:Int16Array,Uint16Array:Uint16Array,Int32Array:Int32Array,Uint32Array:Uint32Array,Float32Array:Float32Array,Float64Array:Float64Array};function yn(t,e){return new vn[t](e)}let xn=0;const _n=new se,wn=new Ce,bn=new Lt,Mn=new Pt,Sn=new Pt,Tn=new Lt;class En extends rt{constructor(){super(),Object.defineProperty(this,"id",{value:xn++}),this.uuid=ct(),this.name="",this.type="BufferGeometry",this.index=null,this.attributes={},this.morphAttributes={},this.morphTargetsRelative=!1,this.groups=[],this.boundingBox=null,this.boundingSphere=null,this.drawRange={start:0,count:1/0},this.userData={}}getIndex(){return this.index}setIndex(t){return Array.isArray(t)?this.index=new(gn(t)>65535?dn:hn)(t,1):this.index=t,this}getAttribute(t){return this.attributes[t]}setAttribute(t,e){return this.attributes[t]=e,this}deleteAttribute(t){return delete this.attributes[t],this}hasAttribute(t){return void 0!==this.attributes[t]}addGroup(t,e,n=0){this.groups.push({start:t,count:e,materialIndex:n})}clearGroups(){this.groups=[]}setDrawRange(t,e){this.drawRange.start=t,this.drawRange.count=e}applyMatrix4(t){const e=this.attributes.position;void 0!==e&&(e.applyMatrix4(t),e.needsUpdate=!0);const n=this.attributes.normal;if(void 0!==n){const e=(new yt).getNormalMatrix(t);n.applyNormalMatrix(e),n.needsUpdate=!0}const i=this.attributes.tangent;return void 0!==i&&(i.transformDirection(t),i.needsUpdate=!0),null!==this.boundingBox&&this.computeBoundingBox(),null!==this.boundingSphere&&this.computeBoundingSphere(),this}rotateX(t){return _n.makeRotationX(t),this.applyMatrix4(_n),this}rotateY(t){return _n.makeRotationY(t),this.applyMatrix4(_n),this}rotateZ(t){return _n.makeRotationZ(t),this.applyMatrix4(_n),this}translate(t,e,n){return _n.makeTranslation(t,e,n),this.applyMatrix4(_n),this}scale(t,e,n){return _n.makeScale(t,e,n),this.applyMatrix4(_n),this}lookAt(t){return wn.lookAt(t),wn.updateMatrix(),this.applyMatrix4(wn.matrix),this}center(){return this.computeBoundingBox(),this.boundingBox.getCenter(bn).negate(),this.translate(bn.x,bn.y,bn.z),this}setFromPoints(t){const e=[];for(let n=0,i=t.length;n0&&(t.userData=this.userData),void 0!==this.parameters){const e=this.parameters;for(const n in e)void 0!==e[n]&&(t[n]=e[n]);return t}t.data={attributes:{}};const e=this.index;null!==e&&(t.data.index={type:e.array.constructor.name,array:Array.prototype.slice.call(e.array)});const n=this.attributes;for(const e in n){const i=n[e];t.data.attributes[e]=i.toJSON(t.data)}const i={};let r=!1;for(const e in this.morphAttributes){const n=this.morphAttributes[e],s=[];for(let e=0,i=n.length;e0&&(i[e]=s,r=!0)}r&&(t.data.morphAttributes=i,t.data.morphTargetsRelative=this.morphTargetsRelative);const s=this.groups;s.length>0&&(t.data.groups=JSON.parse(JSON.stringify(s)));const a=this.boundingSphere;return null!==a&&(t.data.boundingSphere={center:a.center.toArray(),radius:a.radius}),t}clone(){return(new En).copy(this)}copy(t){this.index=null,this.attributes={},this.morphAttributes={},this.groups=[],this.boundingBox=null,this.boundingSphere=null;const e={};this.name=t.name;const n=t.index;null!==n&&this.setIndex(n.clone(e));const i=t.attributes;for(const t in i){const n=i[t];this.setAttribute(t,n.clone(e))}const r=t.morphAttributes;for(const t in r){const n=[],i=r[t];for(let t=0,r=i.length;t0){const t=e[n[0]];if(void 0!==t){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let e=0,n=t.length;e0&&console.error("THREE.Mesh.updateMorphTargets() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.")}}raycast(t,e){const n=this.geometry,i=this.material,r=this.matrixWorld;if(void 0===i)return;if(null===n.boundingSphere&&n.computeBoundingSphere(),Rn.copy(n.boundingSphere),Rn.applyMatrix4(r),!1===t.ray.intersectsSphere(Rn))return;if(An.copy(r).invert(),Ln.copy(t.ray).applyMatrix4(An),null!==n.boundingBox&&!1===Ln.intersectsBox(n.boundingBox))return;let s;if(n.isBufferGeometry){const r=n.index,a=n.attributes.position,o=n.morphAttributes.position,l=n.morphTargetsRelative,c=n.attributes.uv,h=n.attributes.uv2,u=n.groups,d=n.drawRange;if(null!==r)if(Array.isArray(i))for(let n=0,p=u.length;nn.far?null:{distance:c,point:Vn.clone(),object:t}}(t,e,n,i,Cn,Pn,Dn,kn);if(p){o&&(Hn.fromBufferAttribute(o,c),Gn.fromBufferAttribute(o,h),Un.fromBufferAttribute(o,u),p.uv=je.getUV(kn,Cn,Pn,Dn,Hn,Gn,Un,new vt)),l&&(Hn.fromBufferAttribute(l,c),Gn.fromBufferAttribute(l,h),Un.fromBufferAttribute(l,u),p.uv2=je.getUV(kn,Cn,Pn,Dn,Hn,Gn,Un,new vt));const t={a:c,b:h,c:u,normal:new Lt,materialIndex:0};je.getNormal(Cn,Pn,Dn,t.normal),p.face=t}return p}Wn.prototype.isMesh=!0;class qn extends En{constructor(t=1,e=1,n=1,i=1,r=1,s=1){super(),this.type="BoxGeometry",this.parameters={width:t,height:e,depth:n,widthSegments:i,heightSegments:r,depthSegments:s};const a=this;i=Math.floor(i),r=Math.floor(r),s=Math.floor(s);const o=[],l=[],c=[],h=[];let u=0,d=0;function p(t,e,n,i,r,s,p,m,f,g,v){const y=s/f,x=p/g,_=s/2,w=p/2,b=m/2,M=f+1,S=g+1;let T=0,E=0;const A=new Lt;for(let s=0;s0?1:-1,c.push(A.x,A.y,A.z),h.push(o/f),h.push(1-s/g),T+=1}}for(let t=0;t0&&(e.defines=this.defines),e.vertexShader=this.vertexShader,e.fragmentShader=this.fragmentShader;const n={};for(const t in this.extensions)!0===this.extensions[t]&&(n[t]=!0);return Object.keys(n).length>0&&(e.extensions=n),e}}Jn.prototype.isShaderMaterial=!0;class Qn extends Ce{constructor(){super(),this.type="Camera",this.matrixWorldInverse=new se,this.projectionMatrix=new se,this.projectionMatrixInverse=new se}copy(t,e){return super.copy(t,e),this.matrixWorldInverse.copy(t.matrixWorldInverse),this.projectionMatrix.copy(t.projectionMatrix),this.projectionMatrixInverse.copy(t.projectionMatrixInverse),this}getWorldDirection(t){void 0===t&&(console.warn("THREE.Camera: .getWorldDirection() target is now required"),t=new Lt),this.updateWorldMatrix(!0,!1);const e=this.matrixWorld.elements;return t.set(-e[8],-e[9],-e[10]).normalize()}updateMatrixWorld(t){super.updateMatrixWorld(t),this.matrixWorldInverse.copy(this.matrixWorld).invert()}updateWorldMatrix(t,e){super.updateWorldMatrix(t,e),this.matrixWorldInverse.copy(this.matrixWorld).invert()}clone(){return(new this.constructor).copy(this)}}Qn.prototype.isCamera=!0;class Kn extends Qn{constructor(t=50,e=1,n=.1,i=2e3){super(),this.type="PerspectiveCamera",this.fov=t,this.zoom=1,this.near=n,this.far=i,this.focus=10,this.aspect=e,this.view=null,this.filmGauge=35,this.filmOffset=0,this.updateProjectionMatrix()}copy(t,e){return super.copy(t,e),this.fov=t.fov,this.zoom=t.zoom,this.near=t.near,this.far=t.far,this.focus=t.focus,this.aspect=t.aspect,this.view=null===t.view?null:Object.assign({},t.view),this.filmGauge=t.filmGauge,this.filmOffset=t.filmOffset,this}setFocalLength(t){const e=.5*this.getFilmHeight()/t;this.fov=2*lt*Math.atan(e),this.updateProjectionMatrix()}getFocalLength(){const t=Math.tan(.5*ot*this.fov);return.5*this.getFilmHeight()/t}getEffectiveFOV(){return 2*lt*Math.atan(Math.tan(.5*ot*this.fov)/this.zoom)}getFilmWidth(){return this.filmGauge*Math.min(this.aspect,1)}getFilmHeight(){return this.filmGauge/Math.max(this.aspect,1)}setViewOffset(t,e,n,i,r,s){this.aspect=t/e,null===this.view&&(this.view={enabled:!0,fullWidth:1,fullHeight:1,offsetX:0,offsetY:0,width:1,height:1}),this.view.enabled=!0,this.view.fullWidth=t,this.view.fullHeight=e,this.view.offsetX=n,this.view.offsetY=i,this.view.width=r,this.view.height=s,this.updateProjectionMatrix()}clearViewOffset(){null!==this.view&&(this.view.enabled=!1),this.updateProjectionMatrix()}updateProjectionMatrix(){const t=this.near;let e=t*Math.tan(.5*ot*this.fov)/this.zoom,n=2*e,i=this.aspect*n,r=-.5*i;const s=this.view;if(null!==this.view&&this.view.enabled){const t=s.fullWidth,a=s.fullHeight;r+=s.offsetX*i/t,e-=s.offsetY*n/a,i*=s.width/t,n*=s.height/a}const a=this.filmOffset;0!==a&&(r+=t*a/this.getFilmWidth()),this.projectionMatrix.makePerspective(r,r+i,e,e-n,t,this.far),this.projectionMatrixInverse.copy(this.projectionMatrix).invert()}toJSON(t){const e=super.toJSON(t);return e.object.fov=this.fov,e.object.zoom=this.zoom,e.object.near=this.near,e.object.far=this.far,e.object.focus=this.focus,e.object.aspect=this.aspect,null!==this.view&&(e.object.view=Object.assign({},this.view)),e.object.filmGauge=this.filmGauge,e.object.filmOffset=this.filmOffset,e}}Kn.prototype.isPerspectiveCamera=!0;const $n=90;class ti extends Ce{constructor(t,e,n){if(super(),this.type="CubeCamera",!0!==n.isWebGLCubeRenderTarget)return void console.error("THREE.CubeCamera: The constructor now expects an instance of WebGLCubeRenderTarget as third parameter.");this.renderTarget=n;const i=new Kn($n,1,t,e);i.layers=this.layers,i.up.set(0,-1,0),i.lookAt(new Lt(1,0,0)),this.add(i);const r=new Kn($n,1,t,e);r.layers=this.layers,r.up.set(0,-1,0),r.lookAt(new Lt(-1,0,0)),this.add(r);const s=new Kn($n,1,t,e);s.layers=this.layers,s.up.set(0,0,1),s.lookAt(new Lt(0,1,0)),this.add(s);const a=new Kn($n,1,t,e);a.layers=this.layers,a.up.set(0,0,-1),a.lookAt(new Lt(0,-1,0)),this.add(a);const o=new Kn($n,1,t,e);o.layers=this.layers,o.up.set(0,-1,0),o.lookAt(new Lt(0,0,1)),this.add(o);const l=new Kn($n,1,t,e);l.layers=this.layers,l.up.set(0,-1,0),l.lookAt(new Lt(0,0,-1)),this.add(l)}update(t,e){null===this.parent&&this.updateMatrixWorld();const n=this.renderTarget,[i,r,s,a,o,l]=this.children,c=t.xr.enabled,h=t.getRenderTarget();t.xr.enabled=!1;const u=n.texture.generateMipmaps;n.texture.generateMipmaps=!1,t.setRenderTarget(n,0),t.render(e,i),t.setRenderTarget(n,1),t.render(e,r),t.setRenderTarget(n,2),t.render(e,s),t.setRenderTarget(n,3),t.render(e,a),t.setRenderTarget(n,4),t.render(e,o),n.texture.generateMipmaps=u,t.setRenderTarget(n,5),t.render(e,l),t.setRenderTarget(h),t.xr.enabled=c}}class ei extends bt{constructor(t,e,n,i,s,a,o,l,c,h){super(t=void 0!==t?t:[],e=void 0!==e?e:r,n,i,s,a,o=void 0!==o?o:T,l,c,h),this._needsFlipEnvMap=!0,this.flipY=!1}get images(){return this.image}set images(t){this.image=t}}ei.prototype.isCubeTexture=!0;class ni extends Tt{constructor(t,e,n){Number.isInteger(e)&&(console.warn("THREE.WebGLCubeRenderTarget: constructor signature is now WebGLCubeRenderTarget( size, options )"),e=n),super(t,t,e),e=e||{},this.texture=new ei(void 0,e.mapping,e.wrapS,e.wrapT,e.magFilter,e.minFilter,e.format,e.type,e.anisotropy,e.encoding),this.texture.generateMipmaps=void 0!==e.generateMipmaps&&e.generateMipmaps,this.texture.minFilter=void 0!==e.minFilter?e.minFilter:g,this.texture._needsFlipEnvMap=!1}fromEquirectangularTexture(t,e){this.texture.type=e.type,this.texture.format=E,this.texture.encoding=e.encoding,this.texture.generateMipmaps=e.generateMipmaps,this.texture.minFilter=e.minFilter,this.texture.magFilter=e.magFilter;const n={uniforms:{tEquirect:{value:null}},vertexShader:"\n\n\t\t\t\tvarying vec3 vWorldDirection;\n\n\t\t\t\tvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\n\t\t\t\t\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n\n\t\t\t\t}\n\n\t\t\t\tvoid main() {\n\n\t\t\t\t\tvWorldDirection = transformDirection( position, modelMatrix );\n\n\t\t\t\t\t#include \n\t\t\t\t\t#include \n\n\t\t\t\t}\n\t\t\t",fragmentShader:"\n\n\t\t\t\tuniform sampler2D tEquirect;\n\n\t\t\t\tvarying vec3 vWorldDirection;\n\n\t\t\t\t#include \n\n\t\t\t\tvoid main() {\n\n\t\t\t\t\tvec3 direction = normalize( vWorldDirection );\n\n\t\t\t\t\tvec2 sampleUV = equirectUv( direction );\n\n\t\t\t\t\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\n\t\t\t\t}\n\t\t\t"},i=new qn(5,5,5),r=new Jn({name:"CubemapFromEquirect",uniforms:Xn(n.uniforms),vertexShader:n.vertexShader,fragmentShader:n.fragmentShader,side:1,blending:0});r.uniforms.tEquirect.value=e;const s=new Wn(i,r),a=e.minFilter;e.minFilter===y&&(e.minFilter=g);return new ti(1,10,this).update(t,s),e.minFilter=a,s.geometry.dispose(),s.material.dispose(),this}clear(t,e,n,i){const r=t.getRenderTarget();for(let r=0;r<6;r++)t.setRenderTarget(this,r),t.clear(e,n,i);t.setRenderTarget(r)}}ni.prototype.isWebGLCubeRenderTarget=!0;class ii extends bt{constructor(t,e,n,i,r,s,a,o,l,c,h,u){super(null,s,a,o,l,c,i,r,h,u),this.image={data:t||null,width:e||1,height:n||1},this.magFilter=void 0!==l?l:p,this.minFilter=void 0!==c?c:p,this.generateMipmaps=!1,this.flipY=!1,this.unpackAlignment=1,this.needsUpdate=!0}}ii.prototype.isDataTexture=!0;const ri=new Jt,si=new Lt;class ai{constructor(t=new Ne,e=new Ne,n=new Ne,i=new Ne,r=new Ne,s=new Ne){this.planes=[t,e,n,i,r,s]}set(t,e,n,i,r,s){const a=this.planes;return a[0].copy(t),a[1].copy(e),a[2].copy(n),a[3].copy(i),a[4].copy(r),a[5].copy(s),this}copy(t){const e=this.planes;for(let n=0;n<6;n++)e[n].copy(t.planes[n]);return this}setFromProjectionMatrix(t){const e=this.planes,n=t.elements,i=n[0],r=n[1],s=n[2],a=n[3],o=n[4],l=n[5],c=n[6],h=n[7],u=n[8],d=n[9],p=n[10],m=n[11],f=n[12],g=n[13],v=n[14],y=n[15];return e[0].setComponents(a-i,h-o,m-u,y-f).normalize(),e[1].setComponents(a+i,h+o,m+u,y+f).normalize(),e[2].setComponents(a+r,h+l,m+d,y+g).normalize(),e[3].setComponents(a-r,h-l,m-d,y-g).normalize(),e[4].setComponents(a-s,h-c,m-p,y-v).normalize(),e[5].setComponents(a+s,h+c,m+p,y+v).normalize(),this}intersectsObject(t){const e=t.geometry;return null===e.boundingSphere&&e.computeBoundingSphere(),ri.copy(e.boundingSphere).applyMatrix4(t.matrixWorld),this.intersectsSphere(ri)}intersectsSprite(t){return ri.center.set(0,0,0),ri.radius=.7071067811865476,ri.applyMatrix4(t.matrixWorld),this.intersectsSphere(ri)}intersectsSphere(t){const e=this.planes,n=t.center,i=-t.radius;for(let t=0;t<6;t++){if(e[t].distanceToPoint(n)0?t.max.x:t.min.x,si.y=i.normal.y>0?t.max.y:t.min.y,si.z=i.normal.z>0?t.max.z:t.min.z,i.distanceToPoint(si)<0)return!1}return!0}containsPoint(t){const e=this.planes;for(let n=0;n<6;n++)if(e[n].distanceToPoint(t)<0)return!1;return!0}clone(){return(new this.constructor).copy(this)}}function oi(){let t=null,e=!1,n=null,i=null;function r(e,s){n(e,s),i=t.requestAnimationFrame(r)}return{start:function(){!0!==e&&null!==n&&(i=t.requestAnimationFrame(r),e=!0)},stop:function(){t.cancelAnimationFrame(i),e=!1},setAnimationLoop:function(t){n=t},setContext:function(e){t=e}}}function li(t,e){const n=e.isWebGL2,i=new WeakMap;return{get:function(t){return t.isInterleavedBufferAttribute&&(t=t.data),i.get(t)},remove:function(e){e.isInterleavedBufferAttribute&&(e=e.data);const n=i.get(e);n&&(t.deleteBuffer(n.buffer),i.delete(e))},update:function(e,r){if(e.isGLBufferAttribute){const t=i.get(e);return void((!t||t.version 0.0 ) {\n\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t}\n\treturn distanceFalloff;\n#else\n\tif( cutoffDistance > 0.0 && decayExponent > 0.0 ) {\n\t\treturn pow( saturate( -lightDistance / cutoffDistance + 1.0 ), decayExponent );\n\t}\n\treturn 1.0;\n#endif\n}\nvec3 BRDF_Diffuse_Lambert( const in vec3 diffuseColor ) {\n\treturn RECIPROCAL_PI * diffuseColor;\n}\nvec3 F_Schlick( const in vec3 specularColor, const in float dotLH ) {\n\tfloat fresnel = exp2( ( -5.55473 * dotLH - 6.98316 ) * dotLH );\n\treturn ( 1.0 - specularColor ) * fresnel + specularColor;\n}\nvec3 F_Schlick_RoughnessDependent( const in vec3 F0, const in float dotNV, const in float roughness ) {\n\tfloat fresnel = exp2( ( -5.55473 * dotNV - 6.98316 ) * dotNV );\n\tvec3 Fr = max( vec3( 1.0 - roughness ), F0 ) - F0;\n\treturn Fr * fresnel + F0;\n}\nfloat G_GGX_Smith( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gl = dotNL + sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\tfloat gv = dotNV + sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\treturn 1.0 / ( gl * gv );\n}\nfloat G_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\tfloat gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\treturn 0.5 / max( gv + gl, EPSILON );\n}\nfloat D_GGX( const in float alpha, const in float dotNH ) {\n\tfloat a2 = pow2( alpha );\n\tfloat denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\n\treturn RECIPROCAL_PI * a2 / pow2( denom );\n}\nvec3 BRDF_Specular_GGX( const in IncidentLight incidentLight, const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float roughness ) {\n\tfloat alpha = pow2( roughness );\n\tvec3 halfDir = normalize( incidentLight.direction + viewDir );\n\tfloat dotNL = saturate( dot( normal, incidentLight.direction ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotLH = saturate( dot( incidentLight.direction, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, dotLH );\n\tfloat G = G_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\tfloat D = D_GGX( alpha, dotNH );\n\treturn F * ( G * D );\n}\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\n\tconst float LUT_SIZE = 64.0;\n\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n\tconst float LUT_BIAS = 0.5 / LUT_SIZE;\n\tfloat dotNV = saturate( dot( N, V ) );\n\tvec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\n\tuv = uv * LUT_SCALE + LUT_BIAS;\n\treturn uv;\n}\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\n\tfloat l = length( f );\n\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\n\tfloat x = dot( v1, v2 );\n\tfloat y = abs( x );\n\tfloat a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\n\tfloat b = 3.4175940 + ( 4.1616724 + y ) * y;\n\tfloat v = a / b;\n\tfloat theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn vec3( result );\n}\nvec3 BRDF_Specular_GGX_Environment( const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tvec2 brdf = integrateSpecularBRDF( dotNV, roughness );\n\treturn specularColor * brdf.x + brdf.y;\n}\nvoid BRDF_Specular_Multiscattering_Environment( const in GeometricContext geometry, const in vec3 specularColor, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\tvec3 F = F_Schlick_RoughnessDependent( specularColor, dotNV, roughness );\n\tvec2 brdf = integrateSpecularBRDF( dotNV, roughness );\n\tvec3 FssEss = F * brdf.x + brdf.y;\n\tfloat Ess = brdf.x + brdf.y;\n\tfloat Ems = 1.0 - Ess;\n\tvec3 Favg = specularColor + ( 1.0 - specularColor ) * 0.047619;\tvec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );\n\tsingleScatter += FssEss;\n\tmultiScatter += Fms * Ems;\n}\nfloat G_BlinnPhong_Implicit( ) {\n\treturn 0.25;\n}\nfloat D_BlinnPhong( const in float shininess, const in float dotNH ) {\n\treturn RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );\n}\nvec3 BRDF_Specular_BlinnPhong( const in IncidentLight incidentLight, const in GeometricContext geometry, const in vec3 specularColor, const in float shininess ) {\n\tvec3 halfDir = normalize( incidentLight.direction + geometry.viewDir );\n\tfloat dotNH = saturate( dot( geometry.normal, halfDir ) );\n\tfloat dotLH = saturate( dot( incidentLight.direction, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, dotLH );\n\tfloat G = G_BlinnPhong_Implicit( );\n\tfloat D = D_BlinnPhong( shininess, dotNH );\n\treturn F * ( G * D );\n}\nfloat GGXRoughnessToBlinnExponent( const in float ggxRoughness ) {\n\treturn ( 2.0 / pow2( ggxRoughness + 0.0001 ) - 2.0 );\n}\nfloat BlinnExponentToGGXRoughness( const in float blinnExponent ) {\n\treturn sqrt( 2.0 / ( blinnExponent + 2.0 ) );\n}\n#if defined( USE_SHEEN )\nfloat D_Charlie(float roughness, float NoH) {\n\tfloat invAlpha = 1.0 / roughness;\n\tfloat cos2h = NoH * NoH;\n\tfloat sin2h = max(1.0 - cos2h, 0.0078125);\treturn (2.0 + invAlpha) * pow(sin2h, invAlpha * 0.5) / (2.0 * PI);\n}\nfloat V_Neubelt(float NoV, float NoL) {\n\treturn saturate(1.0 / (4.0 * (NoL + NoV - NoL * NoV)));\n}\nvec3 BRDF_Specular_Sheen( const in float roughness, const in vec3 L, const in GeometricContext geometry, vec3 specularColor ) {\n\tvec3 N = geometry.normal;\n\tvec3 V = geometry.viewDir;\n\tvec3 H = normalize( V + L );\n\tfloat dotNH = saturate( dot( N, H ) );\n\treturn specularColor * D_Charlie( roughness, dotNH ) * V_Neubelt( dot(N, V), dot(N, L) );\n}\n#endif",bumpmap_pars_fragment:"#ifdef USE_BUMPMAP\n\tuniform sampler2D bumpMap;\n\tuniform float bumpScale;\n\tvec2 dHdxy_fwd() {\n\t\tvec2 dSTdx = dFdx( vUv );\n\t\tvec2 dSTdy = dFdy( vUv );\n\t\tfloat Hll = bumpScale * texture2D( bumpMap, vUv ).x;\n\t\tfloat dBx = bumpScale * texture2D( bumpMap, vUv + dSTdx ).x - Hll;\n\t\tfloat dBy = bumpScale * texture2D( bumpMap, vUv + dSTdy ).x - Hll;\n\t\treturn vec2( dBx, dBy );\n\t}\n\tvec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy, float faceDirection ) {\n\t\tvec3 vSigmaX = vec3( dFdx( surf_pos.x ), dFdx( surf_pos.y ), dFdx( surf_pos.z ) );\n\t\tvec3 vSigmaY = vec3( dFdy( surf_pos.x ), dFdy( surf_pos.y ), dFdy( surf_pos.z ) );\n\t\tvec3 vN = surf_norm;\n\t\tvec3 R1 = cross( vSigmaY, vN );\n\t\tvec3 R2 = cross( vN, vSigmaX );\n\t\tfloat fDet = dot( vSigmaX, R1 ) * faceDirection;\n\t\tvec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\n\t\treturn normalize( abs( fDet ) * surf_norm - vGrad );\n\t}\n#endif",clipping_planes_fragment:"#if NUM_CLIPPING_PLANES > 0\n\tvec4 plane;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\tplane = clippingPlanes[ i ];\n\t\tif ( dot( vClipPosition, plane.xyz ) > plane.w ) discard;\n\t}\n\t#pragma unroll_loop_end\n\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\tbool clipped = true;\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tclipped = ( dot( vClipPosition, plane.xyz ) > plane.w ) && clipped;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\tif ( clipped ) discard;\n\t#endif\n#endif",clipping_planes_pars_fragment:"#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif",clipping_planes_pars_vertex:"#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n#endif",clipping_planes_vertex:"#if NUM_CLIPPING_PLANES > 0\n\tvClipPosition = - mvPosition.xyz;\n#endif",color_fragment:"#if defined( USE_COLOR_ALPHA )\n\tdiffuseColor *= vColor;\n#elif defined( USE_COLOR )\n\tdiffuseColor.rgb *= vColor;\n#endif",color_pars_fragment:"#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR )\n\tvarying vec3 vColor;\n#endif",color_pars_vertex:"#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvarying vec3 vColor;\n#endif",color_vertex:"#if defined( USE_COLOR_ALPHA )\n\tvColor = vec4( 1.0 );\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvColor = vec3( 1.0 );\n#endif\n#ifdef USE_COLOR\n\tvColor *= color;\n#endif\n#ifdef USE_INSTANCING_COLOR\n\tvColor.xyz *= instanceColor.xyz;\n#endif",common:"#define PI 3.141592653589793\n#define PI2 6.283185307179586\n#define PI_HALF 1.5707963267948966\n#define RECIPROCAL_PI 0.3183098861837907\n#define RECIPROCAL_PI2 0.15915494309189535\n#define EPSILON 1e-6\n#ifndef saturate\n#define saturate(a) clamp( a, 0.0, 1.0 )\n#endif\n#define whiteComplement(a) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat average( const in vec3 color ) { return dot( color, vec3( 0.3333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract(sin(sn) * c);\n}\n#ifdef HIGH_PRECISION\n\tfloat precisionSafeLength( vec3 v ) { return length( v ); }\n#else\n\tfloat max3( vec3 v ) { return max( max( v.x, v.y ), v.z ); }\n\tfloat precisionSafeLength( vec3 v ) {\n\t\tfloat maxComponent = max3( abs( v ) );\n\t\treturn length( v / maxComponent ) * maxComponent;\n\t}\n#endif\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\nstruct GeometricContext {\n\tvec3 position;\n\tvec3 normal;\n\tvec3 viewDir;\n#ifdef CLEARCOAT\n\tvec3 clearcoatNormal;\n#endif\n};\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nvec3 projectOnPlane(in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\tfloat distance = dot( planeNormal, point - pointOnPlane );\n\treturn - distance * planeNormal + point;\n}\nfloat sideOfPlane( in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\treturn sign( dot( point - pointOnPlane, planeNormal ) );\n}\nvec3 linePlaneIntersect( in vec3 pointOnLine, in vec3 lineDirection, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\treturn lineDirection * ( dot( planeNormal, pointOnPlane - pointOnLine ) / dot( planeNormal, lineDirection ) ) + pointOnLine;\n}\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nfloat linearToRelativeLuminance( const in vec3 color ) {\n\tvec3 weights = vec3( 0.2126, 0.7152, 0.0722 );\n\treturn dot( weights, color.rgb );\n}\nbool isPerspectiveMatrix( mat4 m ) {\n\treturn m[ 2 ][ 3 ] == - 1.0;\n}\nvec2 equirectUv( in vec3 dir ) {\n\tfloat u = atan( dir.z, dir.x ) * RECIPROCAL_PI2 + 0.5;\n\tfloat v = asin( clamp( dir.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\treturn vec2( u, v );\n}",cube_uv_reflection_fragment:"#ifdef ENVMAP_TYPE_CUBE_UV\n\t#define cubeUV_maxMipLevel 8.0\n\t#define cubeUV_minMipLevel 4.0\n\t#define cubeUV_maxTileSize 256.0\n\t#define cubeUV_minTileSize 16.0\n\tfloat getFace( vec3 direction ) {\n\t\tvec3 absDirection = abs( direction );\n\t\tfloat face = - 1.0;\n\t\tif ( absDirection.x > absDirection.z ) {\n\t\t\tif ( absDirection.x > absDirection.y )\n\t\t\t\tface = direction.x > 0.0 ? 0.0 : 3.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t} else {\n\t\t\tif ( absDirection.z > absDirection.y )\n\t\t\t\tface = direction.z > 0.0 ? 2.0 : 5.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t}\n\t\treturn face;\n\t}\n\tvec2 getUV( vec3 direction, float face ) {\n\t\tvec2 uv;\n\t\tif ( face == 0.0 ) {\n\t\t\tuv = vec2( direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 1.0 ) {\n\t\t\tuv = vec2( - direction.x, - direction.z ) / abs( direction.y );\n\t\t} else if ( face == 2.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.y ) / abs( direction.z );\n\t\t} else if ( face == 3.0 ) {\n\t\t\tuv = vec2( - direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 4.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.z ) / abs( direction.y );\n\t\t} else {\n\t\t\tuv = vec2( direction.x, direction.y ) / abs( direction.z );\n\t\t}\n\t\treturn 0.5 * ( uv + 1.0 );\n\t}\n\tvec3 bilinearCubeUV( sampler2D envMap, vec3 direction, float mipInt ) {\n\t\tfloat face = getFace( direction );\n\t\tfloat filterInt = max( cubeUV_minMipLevel - mipInt, 0.0 );\n\t\tmipInt = max( mipInt, cubeUV_minMipLevel );\n\t\tfloat faceSize = exp2( mipInt );\n\t\tfloat texelSize = 1.0 / ( 3.0 * cubeUV_maxTileSize );\n\t\tvec2 uv = getUV( direction, face ) * ( faceSize - 1.0 );\n\t\tvec2 f = fract( uv );\n\t\tuv += 0.5 - f;\n\t\tif ( face > 2.0 ) {\n\t\t\tuv.y += faceSize;\n\t\t\tface -= 3.0;\n\t\t}\n\t\tuv.x += face * faceSize;\n\t\tif ( mipInt < cubeUV_maxMipLevel ) {\n\t\t\tuv.y += 2.0 * cubeUV_maxTileSize;\n\t\t}\n\t\tuv.y += filterInt * 2.0 * cubeUV_minTileSize;\n\t\tuv.x += 3.0 * max( 0.0, cubeUV_maxTileSize - 2.0 * faceSize );\n\t\tuv *= texelSize;\n\t\tvec3 tl = envMapTexelToLinear( texture2D( envMap, uv ) ).rgb;\n\t\tuv.x += texelSize;\n\t\tvec3 tr = envMapTexelToLinear( texture2D( envMap, uv ) ).rgb;\n\t\tuv.y += texelSize;\n\t\tvec3 br = envMapTexelToLinear( texture2D( envMap, uv ) ).rgb;\n\t\tuv.x -= texelSize;\n\t\tvec3 bl = envMapTexelToLinear( texture2D( envMap, uv ) ).rgb;\n\t\tvec3 tm = mix( tl, tr, f.x );\n\t\tvec3 bm = mix( bl, br, f.x );\n\t\treturn mix( tm, bm, f.y );\n\t}\n\t#define r0 1.0\n\t#define v0 0.339\n\t#define m0 - 2.0\n\t#define r1 0.8\n\t#define v1 0.276\n\t#define m1 - 1.0\n\t#define r4 0.4\n\t#define v4 0.046\n\t#define m4 2.0\n\t#define r5 0.305\n\t#define v5 0.016\n\t#define m5 3.0\n\t#define r6 0.21\n\t#define v6 0.0038\n\t#define m6 4.0\n\tfloat roughnessToMip( float roughness ) {\n\t\tfloat mip = 0.0;\n\t\tif ( roughness >= r1 ) {\n\t\t\tmip = ( r0 - roughness ) * ( m1 - m0 ) / ( r0 - r1 ) + m0;\n\t\t} else if ( roughness >= r4 ) {\n\t\t\tmip = ( r1 - roughness ) * ( m4 - m1 ) / ( r1 - r4 ) + m1;\n\t\t} else if ( roughness >= r5 ) {\n\t\t\tmip = ( r4 - roughness ) * ( m5 - m4 ) / ( r4 - r5 ) + m4;\n\t\t} else if ( roughness >= r6 ) {\n\t\t\tmip = ( r5 - roughness ) * ( m6 - m5 ) / ( r5 - r6 ) + m5;\n\t\t} else {\n\t\t\tmip = - 2.0 * log2( 1.16 * roughness );\t\t}\n\t\treturn mip;\n\t}\n\tvec4 textureCubeUV( sampler2D envMap, vec3 sampleDir, float roughness ) {\n\t\tfloat mip = clamp( roughnessToMip( roughness ), m0, cubeUV_maxMipLevel );\n\t\tfloat mipF = fract( mip );\n\t\tfloat mipInt = floor( mip );\n\t\tvec3 color0 = bilinearCubeUV( envMap, sampleDir, mipInt );\n\t\tif ( mipF == 0.0 ) {\n\t\t\treturn vec4( color0, 1.0 );\n\t\t} else {\n\t\t\tvec3 color1 = bilinearCubeUV( envMap, sampleDir, mipInt + 1.0 );\n\t\t\treturn vec4( mix( color0, color1, mipF ), 1.0 );\n\t\t}\n\t}\n#endif",defaultnormal_vertex:"vec3 transformedNormal = objectNormal;\n#ifdef USE_INSTANCING\n\tmat3 m = mat3( instanceMatrix );\n\ttransformedNormal /= vec3( dot( m[ 0 ], m[ 0 ] ), dot( m[ 1 ], m[ 1 ] ), dot( m[ 2 ], m[ 2 ] ) );\n\ttransformedNormal = m * transformedNormal;\n#endif\ntransformedNormal = normalMatrix * transformedNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif\n#ifdef USE_TANGENT\n\tvec3 transformedTangent = ( modelViewMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#ifdef FLIP_SIDED\n\t\ttransformedTangent = - transformedTangent;\n\t#endif\n#endif",displacementmap_pars_vertex:"#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif",displacementmap_vertex:"#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, vUv ).x * displacementScale + displacementBias );\n#endif",emissivemap_fragment:"#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vUv );\n\temissiveColor.rgb = emissiveMapTexelToLinear( emissiveColor ).rgb;\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif",emissivemap_pars_fragment:"#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif",encodings_fragment:"gl_FragColor = linearToOutputTexel( gl_FragColor );",encodings_pars_fragment:"\nvec4 LinearToLinear( in vec4 value ) {\n\treturn value;\n}\nvec4 GammaToLinear( in vec4 value, in float gammaFactor ) {\n\treturn vec4( pow( value.rgb, vec3( gammaFactor ) ), value.a );\n}\nvec4 LinearToGamma( in vec4 value, in float gammaFactor ) {\n\treturn vec4( pow( value.rgb, vec3( 1.0 / gammaFactor ) ), value.a );\n}\nvec4 sRGBToLinear( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.a );\n}\nvec4 LinearTosRGB( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );\n}\nvec4 RGBEToLinear( in vec4 value ) {\n\treturn vec4( value.rgb * exp2( value.a * 255.0 - 128.0 ), 1.0 );\n}\nvec4 LinearToRGBE( in vec4 value ) {\n\tfloat maxComponent = max( max( value.r, value.g ), value.b );\n\tfloat fExp = clamp( ceil( log2( maxComponent ) ), -128.0, 127.0 );\n\treturn vec4( value.rgb / exp2( fExp ), ( fExp + 128.0 ) / 255.0 );\n}\nvec4 RGBMToLinear( in vec4 value, in float maxRange ) {\n\treturn vec4( value.rgb * value.a * maxRange, 1.0 );\n}\nvec4 LinearToRGBM( in vec4 value, in float maxRange ) {\n\tfloat maxRGB = max( value.r, max( value.g, value.b ) );\n\tfloat M = clamp( maxRGB / maxRange, 0.0, 1.0 );\n\tM = ceil( M * 255.0 ) / 255.0;\n\treturn vec4( value.rgb / ( M * maxRange ), M );\n}\nvec4 RGBDToLinear( in vec4 value, in float maxRange ) {\n\treturn vec4( value.rgb * ( ( maxRange / 255.0 ) / value.a ), 1.0 );\n}\nvec4 LinearToRGBD( in vec4 value, in float maxRange ) {\n\tfloat maxRGB = max( value.r, max( value.g, value.b ) );\n\tfloat D = max( maxRange / maxRGB, 1.0 );\n\tD = clamp( floor( D ) / 255.0, 0.0, 1.0 );\n\treturn vec4( value.rgb * ( D * ( 255.0 / maxRange ) ), D );\n}\nconst mat3 cLogLuvM = mat3( 0.2209, 0.3390, 0.4184, 0.1138, 0.6780, 0.7319, 0.0102, 0.1130, 0.2969 );\nvec4 LinearToLogLuv( in vec4 value ) {\n\tvec3 Xp_Y_XYZp = cLogLuvM * value.rgb;\n\tXp_Y_XYZp = max( Xp_Y_XYZp, vec3( 1e-6, 1e-6, 1e-6 ) );\n\tvec4 vResult;\n\tvResult.xy = Xp_Y_XYZp.xy / Xp_Y_XYZp.z;\n\tfloat Le = 2.0 * log2(Xp_Y_XYZp.y) + 127.0;\n\tvResult.w = fract( Le );\n\tvResult.z = ( Le - ( floor( vResult.w * 255.0 ) ) / 255.0 ) / 255.0;\n\treturn vResult;\n}\nconst mat3 cLogLuvInverseM = mat3( 6.0014, -2.7008, -1.7996, -1.3320, 3.1029, -5.7721, 0.3008, -1.0882, 5.6268 );\nvec4 LogLuvToLinear( in vec4 value ) {\n\tfloat Le = value.z * 255.0 + value.w;\n\tvec3 Xp_Y_XYZp;\n\tXp_Y_XYZp.y = exp2( ( Le - 127.0 ) / 2.0 );\n\tXp_Y_XYZp.z = Xp_Y_XYZp.y / value.y;\n\tXp_Y_XYZp.x = value.x * Xp_Y_XYZp.z;\n\tvec3 vRGB = cLogLuvInverseM * Xp_Y_XYZp.rgb;\n\treturn vec4( max( vRGB, 0.0 ), 1.0 );\n}",envmap_fragment:"#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvec3 cameraToFrag;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToFrag = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToFrag = normalize( vWorldPosition - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToFrag, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToFrag, worldNormal, refractionRatio );\n\t\t#endif\n\t#else\n\t\tvec3 reflectVec = vReflect;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\tvec4 envColor = textureCubeUV( envMap, reflectVec, 0.0 );\n\t#else\n\t\tvec4 envColor = vec4( 0.0 );\n\t#endif\n\t#ifndef ENVMAP_TYPE_CUBE_UV\n\t\tenvColor = envMapTexelToLinear( envColor );\n\t#endif\n\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_MIX )\n\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_ADD )\n\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\t#endif\n#endif",envmap_common_pars_fragment:"#ifdef USE_ENVMAP\n\tuniform float envMapIntensity;\n\tuniform float flipEnvMap;\n\tuniform int maxMipLevel;\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\t\n#endif",envmap_pars_fragment:"#ifdef USE_ENVMAP\n\tuniform float reflectivity;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\tvarying vec3 vWorldPosition;\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif",envmap_pars_vertex:"#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) ||defined( PHONG )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\t\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif",envmap_physical_pars_fragment:"#if defined( USE_ENVMAP )\n\t#ifdef ENVMAP_MODE_REFRACTION\n\t\tuniform float refractionRatio;\n\t#endif\n\tvec3 getLightProbeIndirectIrradiance( const in GeometricContext geometry, const in int maxMIPLevel ) {\n\t\tvec3 worldNormal = inverseTransformDirection( geometry.normal, viewMatrix );\n\t\t#ifdef ENVMAP_TYPE_CUBE\n\t\t\tvec3 queryVec = vec3( flipEnvMap * worldNormal.x, worldNormal.yz );\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = textureCubeLodEXT( envMap, queryVec, float( maxMIPLevel ) );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = textureCube( envMap, queryVec, float( maxMIPLevel ) );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, worldNormal, 1.0 );\n\t\t#else\n\t\t\tvec4 envMapColor = vec4( 0.0 );\n\t\t#endif\n\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t}\n\tfloat getSpecularMIPLevel( const in float roughness, const in int maxMIPLevel ) {\n\t\tfloat maxMIPLevelScalar = float( maxMIPLevel );\n\t\tfloat sigma = PI * roughness * roughness / ( 1.0 + roughness );\n\t\tfloat desiredMIPLevel = maxMIPLevelScalar + log2( sigma );\n\t\treturn clamp( desiredMIPLevel, 0.0, maxMIPLevelScalar );\n\t}\n\tvec3 getLightProbeIndirectRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness, const in int maxMIPLevel ) {\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( -viewDir, normal );\n\t\t\treflectVec = normalize( mix( reflectVec, normal, roughness * roughness) );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( -viewDir, normal, refractionRatio );\n\t\t#endif\n\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\tfloat specularMIPLevel = getSpecularMIPLevel( roughness, maxMIPLevel );\n\t\t#ifdef ENVMAP_TYPE_CUBE\n\t\t\tvec3 queryReflectVec = vec3( flipEnvMap * reflectVec.x, reflectVec.yz );\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = textureCubeLodEXT( envMap, queryReflectVec, specularMIPLevel );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = textureCube( envMap, queryReflectVec, specularMIPLevel );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, reflectVec, roughness );\n\t\t#endif\n\t\treturn envMapColor.rgb * envMapIntensity;\n\t}\n#endif",envmap_vertex:"#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToVertex = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvReflect = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#endif\n#endif",fog_vertex:"#ifdef USE_FOG\n\tfogDepth = - mvPosition.z;\n#endif",fog_pars_vertex:"#ifdef USE_FOG\n\tvarying float fogDepth;\n#endif",fog_fragment:"#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = 1.0 - exp( - fogDensity * fogDensity * fogDepth * fogDepth );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, fogDepth );\n\t#endif\n\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif",fog_pars_fragment:"#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float fogDepth;\n\t#ifdef FOG_EXP2\n\t\tuniform float fogDensity;\n\t#else\n\t\tuniform float fogNear;\n\t\tuniform float fogFar;\n\t#endif\n#endif",gradientmap_pars_fragment:"#ifdef USE_GRADIENTMAP\n\tuniform sampler2D gradientMap;\n#endif\nvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\tfloat dotNL = dot( normal, lightDirection );\n\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t#ifdef USE_GRADIENTMAP\n\t\treturn texture2D( gradientMap, coord ).rgb;\n\t#else\n\t\treturn ( coord.x < 0.7 ) ? vec3( 0.7 ) : vec3( 1.0 );\n\t#endif\n}",lightmap_fragment:"#ifdef USE_LIGHTMAP\n\tvec4 lightMapTexel= texture2D( lightMap, vUv2 );\n\treflectedLight.indirectDiffuse += PI * lightMapTexelToLinear( lightMapTexel ).rgb * lightMapIntensity;\n#endif",lightmap_pars_fragment:"#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif",lights_lambert_vertex:"vec3 diffuse = vec3( 1.0 );\nGeometricContext geometry;\ngeometry.position = mvPosition.xyz;\ngeometry.normal = normalize( transformedNormal );\ngeometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( -mvPosition.xyz );\nGeometricContext backGeometry;\nbackGeometry.position = geometry.position;\nbackGeometry.normal = -geometry.normal;\nbackGeometry.viewDir = geometry.viewDir;\nvLightFront = vec3( 0.0 );\nvIndirectFront = vec3( 0.0 );\n#ifdef DOUBLE_SIDED\n\tvLightBack = vec3( 0.0 );\n\tvIndirectBack = vec3( 0.0 );\n#endif\nIncidentLight directLight;\nfloat dotNL;\nvec3 directLightColor_Diffuse;\nvIndirectFront += getAmbientLightIrradiance( ambientLightColor );\nvIndirectFront += getLightProbeIrradiance( lightProbe, geometry );\n#ifdef DOUBLE_SIDED\n\tvIndirectBack += getAmbientLightIrradiance( ambientLightColor );\n\tvIndirectBack += getLightProbeIrradiance( lightProbe, backGeometry );\n#endif\n#if NUM_POINT_LIGHTS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tgetPointDirectLightIrradiance( pointLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = PI * directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tgetSpotDirectLightIrradiance( spotLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = PI * directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if NUM_DIR_LIGHTS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tgetDirectionalDirectLightIrradiance( directionalLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = PI * directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\tvIndirectFront += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry );\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvIndirectBack += getHemisphereLightIrradiance( hemisphereLights[ i ], backGeometry );\n\t\t#endif\n\t}\n\t#pragma unroll_loop_end\n#endif",lights_pars_begin:"uniform bool receiveShadow;\nuniform vec3 ambientLightColor;\nuniform vec3 lightProbe[ 9 ];\nvec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {\n\tfloat x = normal.x, y = normal.y, z = normal.z;\n\tvec3 result = shCoefficients[ 0 ] * 0.886227;\n\tresult += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;\n\tresult += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;\n\tresult += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;\n\tresult += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;\n\tresult += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;\n\tresult += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );\n\tresult += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;\n\tresult += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );\n\treturn result;\n}\nvec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in GeometricContext geometry ) {\n\tvec3 worldNormal = inverseTransformDirection( geometry.normal, viewMatrix );\n\tvec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );\n\treturn irradiance;\n}\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\treturn irradiance;\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalDirectLightIrradiance( const in DirectionalLight directionalLight, const in GeometricContext geometry, out IncidentLight directLight ) {\n\t\tdirectLight.color = directionalLight.color;\n\t\tdirectLight.direction = directionalLight.direction;\n\t\tdirectLight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointDirectLightIrradiance( const in PointLight pointLight, const in GeometricContext geometry, out IncidentLight directLight ) {\n\t\tvec3 lVector = pointLight.position - geometry.position;\n\t\tdirectLight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tdirectLight.color = pointLight.color;\n\t\tdirectLight.color *= punctualLightIntensityToIrradianceFactor( lightDistance, pointLight.distance, pointLight.decay );\n\t\tdirectLight.visible = ( directLight.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotDirectLightIrradiance( const in SpotLight spotLight, const in GeometricContext geometry, out IncidentLight directLight ) {\n\t\tvec3 lVector = spotLight.position - geometry.position;\n\t\tdirectLight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tfloat angleCos = dot( directLight.direction, spotLight.direction );\n\t\tif ( angleCos > spotLight.coneCos ) {\n\t\t\tfloat spotEffect = smoothstep( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\t\tdirectLight.color = spotLight.color;\n\t\t\tdirectLight.color *= spotEffect * punctualLightIntensityToIrradianceFactor( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tdirectLight.visible = true;\n\t\t} else {\n\t\t\tdirectLight.color = vec3( 0.0 );\n\t\t\tdirectLight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltc_1;\tuniform sampler2D ltc_2;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in GeometricContext geometry ) {\n\t\tfloat dotNL = dot( geometry.normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\t\tirradiance *= PI;\n\t\t#endif\n\t\treturn irradiance;\n\t}\n#endif",lights_toon_fragment:"ToonMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;",lights_toon_pars_fragment:"varying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\nstruct ToonMaterial {\n\tvec3 diffuseColor;\n};\nvoid RE_Direct_Toon( const in IncidentLight directLight, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\tvec3 irradiance = getGradientIrradiance( geometry.normal, directLight.direction ) * directLight.color;\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\treflectedLight.directDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Toon( const in vec3 irradiance, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Toon\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Toon\n#define Material_LightProbeLOD( material )\t(0)",lights_phong_fragment:"BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;",lights_phong_pars_fragment:"varying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\nstruct BlinnPhongMaterial {\n\tvec3 diffuseColor;\n\tvec3 specularColor;\n\tfloat specularShininess;\n\tfloat specularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\treflectedLight.directDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_Specular_BlinnPhong( directLight, geometry, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_BlinnPhong\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_BlinnPhong\n#define Material_LightProbeLOD( material )\t(0)",lights_physical_fragment:"PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nvec3 dxy = max( abs( dFdx( geometryNormal ) ), abs( dFdy( geometryNormal ) ) );\nfloat geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );\nmaterial.specularRoughness = max( roughnessFactor, 0.0525 );material.specularRoughness += geometryRoughness;\nmaterial.specularRoughness = min( material.specularRoughness, 1.0 );\n#ifdef REFLECTIVITY\n\tmaterial.specularColor = mix( vec3( MAXIMUM_SPECULAR_COEFFICIENT * pow2( reflectivity ) ), diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( DEFAULT_SPECULAR_COEFFICIENT ), diffuseColor.rgb, metalnessFactor );\n#endif\n#ifdef CLEARCOAT\n\tmaterial.clearcoat = clearcoat;\n\tmaterial.clearcoatRoughness = clearcoatRoughness;\n\t#ifdef USE_CLEARCOATMAP\n\t\tmaterial.clearcoat *= texture2D( clearcoatMap, vUv ).x;\n\t#endif\n\t#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\t\tmaterial.clearcoatRoughness *= texture2D( clearcoatRoughnessMap, vUv ).y;\n\t#endif\n\tmaterial.clearcoat = saturate( material.clearcoat );\tmaterial.clearcoatRoughness = max( material.clearcoatRoughness, 0.0525 );\n\tmaterial.clearcoatRoughness += geometryRoughness;\n\tmaterial.clearcoatRoughness = min( material.clearcoatRoughness, 1.0 );\n#endif\n#ifdef USE_SHEEN\n\tmaterial.sheenColor = sheen;\n#endif",lights_physical_pars_fragment:"struct PhysicalMaterial {\n\tvec3 diffuseColor;\n\tfloat specularRoughness;\n\tvec3 specularColor;\n#ifdef CLEARCOAT\n\tfloat clearcoat;\n\tfloat clearcoatRoughness;\n#endif\n#ifdef USE_SHEEN\n\tvec3 sheenColor;\n#endif\n};\n#define MAXIMUM_SPECULAR_COEFFICIENT 0.16\n#define DEFAULT_SPECULAR_COEFFICIENT 0.04\nfloat clearcoatDHRApprox( const in float roughness, const in float dotNL ) {\n\treturn DEFAULT_SPECULAR_COEFFICIENT + ( 1.0 - DEFAULT_SPECULAR_COEFFICIENT ) * ( pow( 1.0 - dotNL, 5.0 ) * pow( 1.0 - roughness, 2.0 ) );\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometry.normal;\n\t\tvec3 viewDir = geometry.viewDir;\n\t\tvec3 position = geometry.position;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.specularRoughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos + halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos - halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos - halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos + halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tvec4 t1 = texture2D( ltc_1, uv );\n\t\tvec4 t2 = texture2D( ltc_2, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( t1.x, 0, t1.y ),\n\t\t\tvec3(\t\t0, 1,\t\t0 ),\n\t\t\tvec3( t1.z, 0, t1.w )\n\t\t);\n\t\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\n\t\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\t#ifdef CLEARCOAT\n\t\tfloat ccDotNL = saturate( dot( geometry.clearcoatNormal, directLight.direction ) );\n\t\tvec3 ccIrradiance = ccDotNL * directLight.color;\n\t\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\t\tccIrradiance *= PI;\n\t\t#endif\n\t\tfloat clearcoatDHR = material.clearcoat * clearcoatDHRApprox( material.clearcoatRoughness, ccDotNL );\n\t\treflectedLight.directSpecular += ccIrradiance * material.clearcoat * BRDF_Specular_GGX( directLight, geometry.viewDir, geometry.clearcoatNormal, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearcoatRoughness );\n\t#else\n\t\tfloat clearcoatDHR = 0.0;\n\t#endif\n\t#ifdef USE_SHEEN\n\t\treflectedLight.directSpecular += ( 1.0 - clearcoatDHR ) * irradiance * BRDF_Specular_Sheen(\n\t\t\tmaterial.specularRoughness,\n\t\t\tdirectLight.direction,\n\t\t\tgeometry,\n\t\t\tmaterial.sheenColor\n\t\t);\n\t#else\n\t\treflectedLight.directSpecular += ( 1.0 - clearcoatDHR ) * irradiance * BRDF_Specular_GGX( directLight, geometry.viewDir, geometry.normal, material.specularColor, material.specularRoughness);\n\t#endif\n\treflectedLight.directDiffuse += ( 1.0 - clearcoatDHR ) * irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n\t#ifdef CLEARCOAT\n\t\tfloat ccDotNV = saturate( dot( geometry.clearcoatNormal, geometry.viewDir ) );\n\t\treflectedLight.indirectSpecular += clearcoatRadiance * material.clearcoat * BRDF_Specular_GGX_Environment( geometry.viewDir, geometry.clearcoatNormal, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearcoatRoughness );\n\t\tfloat ccDotNL = ccDotNV;\n\t\tfloat clearcoatDHR = material.clearcoat * clearcoatDHRApprox( material.clearcoatRoughness, ccDotNL );\n\t#else\n\t\tfloat clearcoatDHR = 0.0;\n\t#endif\n\tfloat clearcoatInv = 1.0 - clearcoatDHR;\n\tvec3 singleScattering = vec3( 0.0 );\n\tvec3 multiScattering = vec3( 0.0 );\n\tvec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n\tBRDF_Specular_Multiscattering_Environment( geometry, material.specularColor, material.specularRoughness, singleScattering, multiScattering );\n\tvec3 diffuse = material.diffuseColor * ( 1.0 - ( singleScattering + multiScattering ) );\n\treflectedLight.indirectSpecular += clearcoatInv * radiance * singleScattering;\n\treflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;\n\treflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}",lights_fragment_begin:"\nGeometricContext geometry;\ngeometry.position = - vViewPosition;\ngeometry.normal = normal;\ngeometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );\n#ifdef CLEARCOAT\n\tgeometry.clearcoatNormal = clearcoatNormal;\n#endif\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointDirectLightIrradiance( pointLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )\n\t\tpointLightShadow = pointLightShadows[ i ];\n\t\tdirectLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotDirectLightIrradiance( spotLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\tspotLightShadow = spotLightShadows[ i ];\n\t\tdirectLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalDirectLightIrradiance( directionalLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )\n\t\tdirectionalLightShadow = directionalLightShadows[ i ];\n\t\tdirectLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 iblIrradiance = vec3( 0.0 );\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\tirradiance += getLightProbeIrradiance( lightProbe, geometry );\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry );\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearcoatRadiance = vec3( 0.0 );\n#endif",lights_fragment_maps:"#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel= texture2D( lightMap, vUv2 );\n\t\tvec3 lightMapIrradiance = lightMapTexelToLinear( lightMapTexel ).rgb * lightMapIntensity;\n\t\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\t\tlightMapIrradiance *= PI;\n\t\t#endif\n\t\tirradiance += lightMapIrradiance;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tiblIrradiance += getLightProbeIndirectIrradiance( geometry, maxMipLevel );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\tradiance += getLightProbeIndirectRadiance( geometry.viewDir, geometry.normal, material.specularRoughness, maxMipLevel );\n\t#ifdef CLEARCOAT\n\t\tclearcoatRadiance += getLightProbeIndirectRadiance( geometry.viewDir, geometry.clearcoatNormal, material.clearcoatRoughness, maxMipLevel );\n\t#endif\n#endif",lights_fragment_end:"#if defined( RE_IndirectDiffuse )\n\tRE_IndirectDiffuse( irradiance, geometry, material, reflectedLight );\n#endif\n#if defined( RE_IndirectSpecular )\n\tRE_IndirectSpecular( radiance, iblIrradiance, clearcoatRadiance, geometry, material, reflectedLight );\n#endif",logdepthbuf_fragment:"#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tgl_FragDepthEXT = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif",logdepthbuf_pars_fragment:"#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tuniform float logDepthBufFC;\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif",logdepthbuf_pars_vertex:"#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvarying float vFragDepth;\n\t\tvarying float vIsPerspective;\n\t#else\n\t\tuniform float logDepthBufFC;\n\t#endif\n#endif",logdepthbuf_vertex:"#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvFragDepth = 1.0 + gl_Position.w;\n\t\tvIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) );\n\t#else\n\t\tif ( isPerspectiveMatrix( projectionMatrix ) ) {\n\t\t\tgl_Position.z = log2( max( EPSILON, gl_Position.w + 1.0 ) ) * logDepthBufFC - 1.0;\n\t\t\tgl_Position.z *= gl_Position.w;\n\t\t}\n\t#endif\n#endif",map_fragment:"#ifdef USE_MAP\n\tvec4 texelColor = texture2D( map, vUv );\n\ttexelColor = mapTexelToLinear( texelColor );\n\tdiffuseColor *= texelColor;\n#endif",map_pars_fragment:"#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif",map_particle_fragment:"#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n#endif\n#ifdef USE_MAP\n\tvec4 mapTexel = texture2D( map, uv );\n\tdiffuseColor *= mapTexelToLinear( mapTexel );\n#endif\n#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, uv ).g;\n#endif",map_particle_pars_fragment:"#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\tuniform mat3 uvTransform;\n#endif\n#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif",metalnessmap_fragment:"float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif",metalnessmap_pars_fragment:"#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif",morphnormal_vertex:"#ifdef USE_MORPHNORMALS\n\tobjectNormal *= morphTargetBaseInfluence;\n\tobjectNormal += morphNormal0 * morphTargetInfluences[ 0 ];\n\tobjectNormal += morphNormal1 * morphTargetInfluences[ 1 ];\n\tobjectNormal += morphNormal2 * morphTargetInfluences[ 2 ];\n\tobjectNormal += morphNormal3 * morphTargetInfluences[ 3 ];\n#endif",morphtarget_pars_vertex:"#ifdef USE_MORPHTARGETS\n\tuniform float morphTargetBaseInfluence;\n\t#ifndef USE_MORPHNORMALS\n\t\tuniform float morphTargetInfluences[ 8 ];\n\t#else\n\t\tuniform float morphTargetInfluences[ 4 ];\n\t#endif\n#endif",morphtarget_vertex:"#ifdef USE_MORPHTARGETS\n\ttransformed *= morphTargetBaseInfluence;\n\ttransformed += morphTarget0 * morphTargetInfluences[ 0 ];\n\ttransformed += morphTarget1 * morphTargetInfluences[ 1 ];\n\ttransformed += morphTarget2 * morphTargetInfluences[ 2 ];\n\ttransformed += morphTarget3 * morphTargetInfluences[ 3 ];\n\t#ifndef USE_MORPHNORMALS\n\t\ttransformed += morphTarget4 * morphTargetInfluences[ 4 ];\n\t\ttransformed += morphTarget5 * morphTargetInfluences[ 5 ];\n\t\ttransformed += morphTarget6 * morphTargetInfluences[ 6 ];\n\t\ttransformed += morphTarget7 * morphTargetInfluences[ 7 ];\n\t#endif\n#endif",normal_fragment_begin:"float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;\n#ifdef FLAT_SHADED\n\tvec3 fdx = vec3( dFdx( vViewPosition.x ), dFdx( vViewPosition.y ), dFdx( vViewPosition.z ) );\n\tvec3 fdy = vec3( dFdy( vViewPosition.x ), dFdy( vViewPosition.y ), dFdy( vViewPosition.z ) );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n#else\n\tvec3 normal = normalize( vNormal );\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * faceDirection;\n\t#endif\n\t#ifdef USE_TANGENT\n\t\tvec3 tangent = normalize( vTangent );\n\t\tvec3 bitangent = normalize( vBitangent );\n\t\t#ifdef DOUBLE_SIDED\n\t\t\ttangent = tangent * faceDirection;\n\t\t\tbitangent = bitangent * faceDirection;\n\t\t#endif\n\t\t#if defined( TANGENTSPACE_NORMALMAP ) || defined( USE_CLEARCOAT_NORMALMAP )\n\t\t\tmat3 vTBN = mat3( tangent, bitangent, normal );\n\t\t#endif\n\t#endif\n#endif\nvec3 geometryNormal = normal;",normal_fragment_maps:"#ifdef OBJECTSPACE_NORMALMAP\n\tnormal = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n\t#ifdef FLIP_SIDED\n\t\tnormal = - normal;\n\t#endif\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * faceDirection;\n\t#endif\n\tnormal = normalize( normalMatrix * normal );\n#elif defined( TANGENTSPACE_NORMALMAP )\n\tvec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n\tmapN.xy *= normalScale;\n\t#ifdef USE_TANGENT\n\t\tnormal = normalize( vTBN * mapN );\n\t#else\n\t\tnormal = perturbNormal2Arb( -vViewPosition, normal, mapN, faceDirection );\n\t#endif\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( -vViewPosition, normal, dHdxy_fwd(), faceDirection );\n#endif",normalmap_pars_fragment:"#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n#endif\n#ifdef OBJECTSPACE_NORMALMAP\n\tuniform mat3 normalMatrix;\n#endif\n#if ! defined ( USE_TANGENT ) && ( defined ( TANGENTSPACE_NORMALMAP ) || defined ( USE_CLEARCOAT_NORMALMAP ) )\n\tvec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm, vec3 mapN, float faceDirection ) {\n\t\tvec3 q0 = vec3( dFdx( eye_pos.x ), dFdx( eye_pos.y ), dFdx( eye_pos.z ) );\n\t\tvec3 q1 = vec3( dFdy( eye_pos.x ), dFdy( eye_pos.y ), dFdy( eye_pos.z ) );\n\t\tvec2 st0 = dFdx( vUv.st );\n\t\tvec2 st1 = dFdy( vUv.st );\n\t\tvec3 N = surf_norm;\n\t\tvec3 q1perp = cross( q1, N );\n\t\tvec3 q0perp = cross( N, q0 );\n\t\tvec3 T = q1perp * st0.x + q0perp * st1.x;\n\t\tvec3 B = q1perp * st0.y + q0perp * st1.y;\n\t\tfloat det = max( dot( T, T ), dot( B, B ) );\n\t\tfloat scale = ( det == 0.0 ) ? 0.0 : faceDirection * inversesqrt( det );\n\t\treturn normalize( T * ( mapN.x * scale ) + B * ( mapN.y * scale ) + N * mapN.z );\n\t}\n#endif",clearcoat_normal_fragment_begin:"#ifdef CLEARCOAT\n\tvec3 clearcoatNormal = geometryNormal;\n#endif",clearcoat_normal_fragment_maps:"#ifdef USE_CLEARCOAT_NORMALMAP\n\tvec3 clearcoatMapN = texture2D( clearcoatNormalMap, vUv ).xyz * 2.0 - 1.0;\n\tclearcoatMapN.xy *= clearcoatNormalScale;\n\t#ifdef USE_TANGENT\n\t\tclearcoatNormal = normalize( vTBN * clearcoatMapN );\n\t#else\n\t\tclearcoatNormal = perturbNormal2Arb( - vViewPosition, clearcoatNormal, clearcoatMapN, faceDirection );\n\t#endif\n#endif",clearcoat_pars_fragment:"#ifdef USE_CLEARCOATMAP\n\tuniform sampler2D clearcoatMap;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform sampler2D clearcoatRoughnessMap;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform sampler2D clearcoatNormalMap;\n\tuniform vec2 clearcoatNormalScale;\n#endif",packing:"vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;\nconst vec3 PackFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\nconst vec4 UnpackFactors = UnpackDownscale / vec4( PackFactors, 1. );\nconst float ShiftRight8 = 1. / 256.;\nvec4 packDepthToRGBA( const in float v ) {\n\tvec4 r = vec4( fract( v * PackFactors ), v );\n\tr.yzw -= r.xyz * ShiftRight8;\treturn r * PackUpscale;\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors );\n}\nvec4 pack2HalfToRGBA( vec2 v ) {\n\tvec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ));\n\treturn vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w);\n}\nvec2 unpackRGBATo2Half( vec4 v ) {\n\treturn vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float linearClipZ, const in float near, const in float far ) {\n\treturn linearClipZ * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn (( near + viewZ ) * far ) / (( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float invClipZ, const in float near, const in float far ) {\n\treturn ( near * far ) / ( ( far - near ) * invClipZ - far );\n}",premultiplied_alpha_fragment:"#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif",project_vertex:"vec4 mvPosition = vec4( transformed, 1.0 );\n#ifdef USE_INSTANCING\n\tmvPosition = instanceMatrix * mvPosition;\n#endif\nmvPosition = modelViewMatrix * mvPosition;\ngl_Position = projectionMatrix * mvPosition;",dithering_fragment:"#ifdef DITHERING\n\tgl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif",dithering_pars_fragment:"#ifdef DITHERING\n\tvec3 dithering( vec3 color ) {\n\t\tfloat grid_position = rand( gl_FragCoord.xy );\n\t\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\n\t\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\n\t\treturn color + dither_shift_RGB;\n\t}\n#endif",roughnessmap_fragment:"float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vUv );\n\troughnessFactor *= texelRoughness.g;\n#endif",roughnessmap_pars_fragment:"#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif",shadowmap_pars_fragment:"#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\treturn step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n\t}\n\tvec2 texture2DDistribution( sampler2D shadow, vec2 uv ) {\n\t\treturn unpackRGBATo2Half( texture2D( shadow, uv ) );\n\t}\n\tfloat VSMShadow (sampler2D shadow, vec2 uv, float compare ){\n\t\tfloat occlusion = 1.0;\n\t\tvec2 distribution = texture2DDistribution( shadow, uv );\n\t\tfloat hard_shadow = step( compare , distribution.x );\n\t\tif (hard_shadow != 1.0 ) {\n\t\t\tfloat distance = compare - distribution.x ;\n\t\t\tfloat variance = max( 0.00000, distribution.y * distribution.y );\n\t\t\tfloat softness_probability = variance / (variance + distance * distance );\t\t\tsoftness_probability = clamp( ( softness_probability - 0.3 ) / ( 0.95 - 0.3 ), 0.0, 1.0 );\t\t\tocclusion = clamp( max( hard_shadow, softness_probability ), 0.0, 1.0 );\n\t\t}\n\t\treturn occlusion;\n\t}\n\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\tfloat shadow = 1.0;\n\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\tshadowCoord.z += shadowBias;\n\t\tbvec4 inFrustumVec = bvec4 ( shadowCoord.x >= 0.0, shadowCoord.x <= 1.0, shadowCoord.y >= 0.0, shadowCoord.y <= 1.0 );\n\t\tbool inFrustum = all( inFrustumVec );\n\t\tbvec2 frustumTestVec = bvec2( inFrustum, shadowCoord.z <= 1.0 );\n\t\tbool frustumTest = all( frustumTestVec );\n\t\tif ( frustumTest ) {\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tfloat dx2 = dx0 / 2.0;\n\t\t\tfloat dy2 = dy0 / 2.0;\n\t\t\tfloat dx3 = dx1 / 2.0;\n\t\t\tfloat dy3 = dy1 / 2.0;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 17.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx = texelSize.x;\n\t\t\tfloat dy = texelSize.y;\n\t\t\tvec2 uv = shadowCoord.xy;\n\t\t\tvec2 f = fract( uv * shadowMapSize + 0.5 );\n\t\t\tuv -= f * texelSize;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, uv, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( dx, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 0.0, dy ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + texelSize, shadowCoord.z ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, 0.0 ), shadowCoord.z ), \n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, dy ), shadowCoord.z ), \n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, dy ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( 0.0, -dy ), shadowCoord.z ), \n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 0.0, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( dx, -dy ), shadowCoord.z ), \n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( mix( texture2DCompare( shadowMap, uv + vec2( -dx, -dy ), shadowCoord.z ), \n\t\t\t\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 2.0 * dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t\tf.x ),\n\t\t\t\t\t mix( texture2DCompare( shadowMap, uv + vec2( -dx, 2.0 * dy ), shadowCoord.z ), \n\t\t\t\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t\tf.x ),\n\t\t\t\t\t f.y )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_VSM )\n\t\t\tshadow = VSMShadow( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#else\n\t\t\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#endif\n\t\t}\n\t\treturn shadow;\n\t}\n\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\n\t\tvec3 absV = abs( v );\n\t\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n\t\tabsV *= scaleToCube;\n\t\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n\t\tvec2 planar = v.xy;\n\t\tfloat almostATexel = 1.5 * texelSizeY;\n\t\tfloat almostOne = 1.0 - almostATexel;\n\t\tif ( absV.z >= almostOne ) {\n\t\t\tif ( v.z > 0.0 )\n\t\t\t\tplanar.x = 4.0 - v.x;\n\t\t} else if ( absV.x >= almostOne ) {\n\t\t\tfloat signX = sign( v.x );\n\t\t\tplanar.x = v.z * signX + 2.0 * signX;\n\t\t} else if ( absV.y >= almostOne ) {\n\t\t\tfloat signY = sign( v.y );\n\t\t\tplanar.x = v.x + 2.0 * signY + 2.0;\n\t\t\tplanar.y = v.z * signY - 2.0;\n\t\t}\n\t\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\t}\n\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\tfloat dp = ( length( lightToPosition ) - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\t\tdp += shadowBias;\n\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT ) || defined( SHADOWMAP_TYPE_VSM )\n\t\t\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n\t\t\treturn (\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#else\n\t\t\treturn texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n\t\t#endif\n\t}\n#endif",shadowmap_pars_vertex:"#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 spotShadowMatrix[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n#endif",shadowmap_vertex:"#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0 || NUM_SPOT_LIGHT_SHADOWS > 0 || NUM_POINT_LIGHT_SHADOWS > 0\n\t\tvec3 shadowWorldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\tvec4 shadowWorldPosition;\n\t#endif\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );\n\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * spotLightShadows[ i ].shadowNormalBias, 0 );\n\t\tvSpotShadowCoord[ i ] = spotShadowMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * pointLightShadows[ i ].shadowNormalBias, 0 );\n\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n#endif",shadowmask_pars_fragment:"float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tdirectionalLight = directionalLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tspotLight = spotLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tpointLight = pointLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#endif\n\treturn shadow;\n}",skinbase_vertex:"#ifdef USE_SKINNING\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif",skinning_pars_vertex:"#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\t#ifdef BONE_TEXTURE\n\t\tuniform highp sampler2D boneTexture;\n\t\tuniform int boneTextureSize;\n\t\tmat4 getBoneMatrix( const in float i ) {\n\t\t\tfloat j = i * 4.0;\n\t\t\tfloat x = mod( j, float( boneTextureSize ) );\n\t\t\tfloat y = floor( j / float( boneTextureSize ) );\n\t\t\tfloat dx = 1.0 / float( boneTextureSize );\n\t\t\tfloat dy = 1.0 / float( boneTextureSize );\n\t\t\ty = dy * ( y + 0.5 );\n\t\t\tvec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );\n\t\t\tvec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );\n\t\t\tvec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );\n\t\t\tvec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );\n\t\t\tmat4 bone = mat4( v1, v2, v3, v4 );\n\t\t\treturn bone;\n\t\t}\n\t#else\n\t\tuniform mat4 boneMatrices[ MAX_BONES ];\n\t\tmat4 getBoneMatrix( const in float i ) {\n\t\t\tmat4 bone = boneMatrices[ int(i) ];\n\t\t\treturn bone;\n\t\t}\n\t#endif\n#endif",skinning_vertex:"#ifdef USE_SKINNING\n\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n\tvec4 skinned = vec4( 0.0 );\n\tskinned += boneMatX * skinVertex * skinWeight.x;\n\tskinned += boneMatY * skinVertex * skinWeight.y;\n\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\tskinned += boneMatW * skinVertex * skinWeight.w;\n\ttransformed = ( bindMatrixInverse * skinned ).xyz;\n#endif",skinnormal_vertex:"#ifdef USE_SKINNING\n\tmat4 skinMatrix = mat4( 0.0 );\n\tskinMatrix += skinWeight.x * boneMatX;\n\tskinMatrix += skinWeight.y * boneMatY;\n\tskinMatrix += skinWeight.z * boneMatZ;\n\tskinMatrix += skinWeight.w * boneMatW;\n\tskinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n\t#ifdef USE_TANGENT\n\t\tobjectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#endif\n#endif",specularmap_fragment:"float specularStrength;\n#ifdef USE_SPECULARMAP\n\tvec4 texelSpecular = texture2D( specularMap, vUv );\n\tspecularStrength = texelSpecular.r;\n#else\n\tspecularStrength = 1.0;\n#endif",specularmap_pars_fragment:"#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif",tonemapping_fragment:"#if defined( TONE_MAPPING )\n\tgl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif",tonemapping_pars_fragment:"#ifndef saturate\n#define saturate(a) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn toneMappingExposure * color;\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\nvec3 OptimizedCineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\nvec3 RRTAndODTFit( vec3 v ) {\n\tvec3 a = v * ( v + 0.0245786 ) - 0.000090537;\n\tvec3 b = v * ( 0.983729 * v + 0.4329510 ) + 0.238081;\n\treturn a / b;\n}\nvec3 ACESFilmicToneMapping( vec3 color ) {\n\tconst mat3 ACESInputMat = mat3(\n\t\tvec3( 0.59719, 0.07600, 0.02840 ),\t\tvec3( 0.35458, 0.90834, 0.13383 ),\n\t\tvec3( 0.04823, 0.01566, 0.83777 )\n\t);\n\tconst mat3 ACESOutputMat = mat3(\n\t\tvec3(\t1.60475, -0.10208, -0.00327 ),\t\tvec3( -0.53108,\t1.10813, -0.07276 ),\n\t\tvec3( -0.07367, -0.00605,\t1.07602 )\n\t);\n\tcolor *= toneMappingExposure / 0.6;\n\tcolor = ACESInputMat * color;\n\tcolor = RRTAndODTFit( color );\n\tcolor = ACESOutputMat * color;\n\treturn saturate( color );\n}\nvec3 CustomToneMapping( vec3 color ) { return color; }",transmissionmap_fragment:"#ifdef USE_TRANSMISSIONMAP\n\ttotalTransmission *= texture2D( transmissionMap, vUv ).r;\n#endif",transmissionmap_pars_fragment:"#ifdef USE_TRANSMISSIONMAP\n\tuniform sampler2D transmissionMap;\n#endif",uv_pars_fragment:"#if ( defined( USE_UV ) && ! defined( UVS_VERTEX_ONLY ) )\n\tvarying vec2 vUv;\n#endif",uv_pars_vertex:"#ifdef USE_UV\n\t#ifdef UVS_VERTEX_ONLY\n\t\tvec2 vUv;\n\t#else\n\t\tvarying vec2 vUv;\n\t#endif\n\tuniform mat3 uvTransform;\n#endif",uv_vertex:"#ifdef USE_UV\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n#endif",uv2_pars_fragment:"#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tvarying vec2 vUv2;\n#endif",uv2_pars_vertex:"#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tattribute vec2 uv2;\n\tvarying vec2 vUv2;\n\tuniform mat3 uv2Transform;\n#endif",uv2_vertex:"#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tvUv2 = ( uv2Transform * vec3( uv2, 1 ) ).xy;\n#endif",worldpos_vertex:"#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP )\n\tvec4 worldPosition = vec4( transformed, 1.0 );\n\t#ifdef USE_INSTANCING\n\t\tworldPosition = instanceMatrix * worldPosition;\n\t#endif\n\tworldPosition = modelMatrix * worldPosition;\n#endif",background_frag:"uniform sampler2D t2D;\nvarying vec2 vUv;\nvoid main() {\n\tvec4 texColor = texture2D( t2D, vUv );\n\tgl_FragColor = mapTexelToLinear( texColor );\n\t#include \n\t#include \n}",background_vert:"varying vec2 vUv;\nuniform mat3 uvTransform;\nvoid main() {\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\tgl_Position = vec4( position.xy, 1.0, 1.0 );\n}",cube_frag:"#include \nuniform float opacity;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvec3 vReflect = vWorldDirection;\n\t#include \n\tgl_FragColor = envColor;\n\tgl_FragColor.a *= opacity;\n\t#include \n\t#include \n}",cube_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}",depth_frag:"#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\tfloat fragCoordZ = 0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5;\n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( fragCoordZ );\n\t#endif\n}",depth_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvHighPrecisionZW = gl_Position.zw;\n}",distanceRGBA_frag:"#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main () {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#include \n\t#include \n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = packDepthToRGBA( dist );\n}",distanceRGBA_vert:"#define DISTANCE\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvWorldPosition = worldPosition.xyz;\n}",equirect_frag:"uniform sampler2D tEquirect;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvec3 direction = normalize( vWorldDirection );\n\tvec2 sampleUV = equirectUv( direction );\n\tvec4 texColor = texture2D( tEquirect, sampleUV );\n\tgl_FragColor = mapTexelToLinear( texColor );\n\t#include \n\t#include \n}",equirect_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n}",linedashed_frag:"uniform vec3 diffuse;\nuniform float opacity;\nuniform float dashSize;\nuniform float totalSize;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\n\t\tdiscard;\n\t}\n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n}",linedashed_vert:"uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvLineDistance = scale * lineDistance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshbasic_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\t#ifdef USE_LIGHTMAP\n\t\n\t\tvec4 lightMapTexel= texture2D( lightMap, vUv2 );\n\t\treflectedLight.indirectDiffuse += lightMapTexelToLinear( lightMapTexel ).rgb * lightMapIntensity;\n\t#else\n\t\treflectedLight.indirectDiffuse += vec3( 1.0 );\n\t#endif\n\t#include \n\treflectedLight.indirectDiffuse *= diffuseColor.rgb;\n\tvec3 outgoingLight = reflectedLight.indirectDiffuse;\n\t#include \n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshbasic_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_ENVMAP\n\t#include \n\t#include \n\t#include \n\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshlambert_frag:"uniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\nvarying vec3 vLightFront;\nvarying vec3 vIndirectFront;\n#ifdef DOUBLE_SIDED\n\tvarying vec3 vLightBack;\n\tvarying vec3 vIndirectBack;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef DOUBLE_SIDED\n\t\treflectedLight.indirectDiffuse += ( gl_FrontFacing ) ? vIndirectFront : vIndirectBack;\n\t#else\n\t\treflectedLight.indirectDiffuse += vIndirectFront;\n\t#endif\n\t#include \n\treflectedLight.indirectDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb );\n\t#ifdef DOUBLE_SIDED\n\t\treflectedLight.directDiffuse = ( gl_FrontFacing ) ? vLightFront : vLightBack;\n\t#else\n\t\treflectedLight.directDiffuse = vLightFront;\n\t#endif\n\treflectedLight.directDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb ) * getShadowMask();\n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshlambert_vert:"#define LAMBERT\nvarying vec3 vLightFront;\nvarying vec3 vIndirectFront;\n#ifdef DOUBLE_SIDED\n\tvarying vec3 vLightBack;\n\tvarying vec3 vIndirectBack;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshmatcap_frag:"#define MATCAP\nuniform vec3 diffuse;\nuniform float opacity;\nuniform sampler2D matcap;\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 viewDir = normalize( vViewPosition );\n\tvec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) );\n\tvec3 y = cross( viewDir, x );\n\tvec2 uv = vec2( dot( x, normal ), dot( y, normal ) ) * 0.495 + 0.5;\n\t#ifdef USE_MATCAP\n\t\tvec4 matcapColor = texture2D( matcap, uv );\n\t\tmatcapColor = matcapTexelToLinear( matcapColor );\n\t#else\n\t\tvec4 matcapColor = vec4( 1.0 );\n\t#endif\n\tvec3 outgoingLight = diffuseColor.rgb * matcapColor.rgb;\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshmatcap_vert:"#define MATCAP\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifndef FLAT_SHADED\n\t\tvNormal = normalize( transformedNormal );\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n}",meshtoon_frag:"#define TOON\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshtoon_vert:"#define TOON\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n}",meshphong_frag:"#define PHONG\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform vec3 specular;\nuniform float shininess;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\t#include \n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshphong_vert:"#define PHONG\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}",meshphysical_frag:"#define STANDARD\n#ifdef PHYSICAL\n\t#define REFLECTIVITY\n\t#define CLEARCOAT\n\t#define TRANSMISSION\n#endif\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float roughness;\nuniform float metalness;\nuniform float opacity;\n#ifdef TRANSMISSION\n\tuniform float transmission;\n#endif\n#ifdef REFLECTIVITY\n\tuniform float reflectivity;\n#endif\n#ifdef CLEARCOAT\n\tuniform float clearcoat;\n\tuniform float clearcoatRoughness;\n#endif\n#ifdef USE_SHEEN\n\tuniform vec3 sheen;\n#endif\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#ifdef TRANSMISSION\n\t\tfloat totalTransmission = transmission;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\t#ifdef TRANSMISSION\n\t\tdiffuseColor.a *= mix( saturate( 1. - totalTransmission + linearToRelativeLuminance( reflectedLight.directSpecular + reflectedLight.indirectSpecular ) ), 1.0, metalness );\n\t#endif\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshphysical_vert:"#define STANDARD\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n\t#ifdef USE_TANGENT\n\t\tvTangent = normalize( transformedTangent );\n\t\tvBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );\n\t#endif\n#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n}",normal_frag:"#define NORMAL\nuniform float opacity;\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )\n\tvarying vec3 vViewPosition;\n#endif\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_FragColor = vec4( packNormalToRGB( normal ), opacity );\n}",normal_vert:"#define NORMAL\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )\n\tvarying vec3 vViewPosition;\n#endif\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n\t#ifdef USE_TANGENT\n\t\tvTangent = normalize( transformedTangent );\n\t\tvBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );\n\t#endif\n#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )\n\tvViewPosition = - mvPosition.xyz;\n#endif\n}",points_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n}",points_vert:"uniform float size;\nuniform float scale;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_PointSize = size;\n\t#ifdef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) gl_PointSize *= ( scale / - mvPosition.z );\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n}",shadow_frag:"uniform vec3 color;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tgl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) );\n\t#include \n\t#include \n\t#include \n}",shadow_vert:"#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",sprite_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n}",sprite_vert:"uniform float rotation;\nuniform vec2 center;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 mvPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );\n\tvec2 scale;\n\tscale.x = length( vec3( modelMatrix[ 0 ].x, modelMatrix[ 0 ].y, modelMatrix[ 0 ].z ) );\n\tscale.y = length( vec3( modelMatrix[ 1 ].x, modelMatrix[ 1 ].y, modelMatrix[ 1 ].z ) );\n\t#ifndef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) scale *= - mvPosition.z;\n\t#endif\n\tvec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale;\n\tvec2 rotatedPosition;\n\trotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;\n\trotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;\n\tmvPosition.xy += rotatedPosition;\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include \n\t#include \n\t#include \n}"},ui={common:{diffuse:{value:new tn(15658734)},opacity:{value:1},map:{value:null},uvTransform:{value:new yt},uv2Transform:{value:new yt},alphaMap:{value:null}},specularmap:{specularMap:{value:null}},envmap:{envMap:{value:null},flipEnvMap:{value:-1},reflectivity:{value:1},refractionRatio:{value:.98},maxMipLevel:{value:0}},aomap:{aoMap:{value:null},aoMapIntensity:{value:1}},lightmap:{lightMap:{value:null},lightMapIntensity:{value:1}},emissivemap:{emissiveMap:{value:null}},bumpmap:{bumpMap:{value:null},bumpScale:{value:1}},normalmap:{normalMap:{value:null},normalScale:{value:new vt(1,1)}},displacementmap:{displacementMap:{value:null},displacementScale:{value:1},displacementBias:{value:0}},roughnessmap:{roughnessMap:{value:null}},metalnessmap:{metalnessMap:{value:null}},gradientmap:{gradientMap:{value:null}},fog:{fogDensity:{value:25e-5},fogNear:{value:1},fogFar:{value:2e3},fogColor:{value:new tn(16777215)}},lights:{ambientLightColor:{value:[]},lightProbe:{value:[]},directionalLights:{value:[],properties:{direction:{},color:{}}},directionalLightShadows:{value:[],properties:{shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{}}},directionalShadowMap:{value:[]},directionalShadowMatrix:{value:[]},spotLights:{value:[],properties:{color:{},position:{},direction:{},distance:{},coneCos:{},penumbraCos:{},decay:{}}},spotLightShadows:{value:[],properties:{shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{}}},spotShadowMap:{value:[]},spotShadowMatrix:{value:[]},pointLights:{value:[],properties:{color:{},position:{},decay:{},distance:{}}},pointLightShadows:{value:[],properties:{shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{},shadowCameraNear:{},shadowCameraFar:{}}},pointShadowMap:{value:[]},pointShadowMatrix:{value:[]},hemisphereLights:{value:[],properties:{direction:{},skyColor:{},groundColor:{}}},rectAreaLights:{value:[],properties:{color:{},position:{},width:{},height:{}}},ltc_1:{value:null},ltc_2:{value:null}},points:{diffuse:{value:new tn(15658734)},opacity:{value:1},size:{value:1},scale:{value:1},map:{value:null},alphaMap:{value:null},uvTransform:{value:new yt}},sprite:{diffuse:{value:new tn(15658734)},opacity:{value:1},center:{value:new vt(.5,.5)},rotation:{value:0},map:{value:null},alphaMap:{value:null},uvTransform:{value:new yt}}},di={basic:{uniforms:Yn([ui.common,ui.specularmap,ui.envmap,ui.aomap,ui.lightmap,ui.fog]),vertexShader:hi.meshbasic_vert,fragmentShader:hi.meshbasic_frag},lambert:{uniforms:Yn([ui.common,ui.specularmap,ui.envmap,ui.aomap,ui.lightmap,ui.emissivemap,ui.fog,ui.lights,{emissive:{value:new tn(0)}}]),vertexShader:hi.meshlambert_vert,fragmentShader:hi.meshlambert_frag},phong:{uniforms:Yn([ui.common,ui.specularmap,ui.envmap,ui.aomap,ui.lightmap,ui.emissivemap,ui.bumpmap,ui.normalmap,ui.displacementmap,ui.fog,ui.lights,{emissive:{value:new tn(0)},specular:{value:new tn(1118481)},shininess:{value:30}}]),vertexShader:hi.meshphong_vert,fragmentShader:hi.meshphong_frag},standard:{uniforms:Yn([ui.common,ui.envmap,ui.aomap,ui.lightmap,ui.emissivemap,ui.bumpmap,ui.normalmap,ui.displacementmap,ui.roughnessmap,ui.metalnessmap,ui.fog,ui.lights,{emissive:{value:new tn(0)},roughness:{value:1},metalness:{value:0},envMapIntensity:{value:1}}]),vertexShader:hi.meshphysical_vert,fragmentShader:hi.meshphysical_frag},toon:{uniforms:Yn([ui.common,ui.aomap,ui.lightmap,ui.emissivemap,ui.bumpmap,ui.normalmap,ui.displacementmap,ui.gradientmap,ui.fog,ui.lights,{emissive:{value:new tn(0)}}]),vertexShader:hi.meshtoon_vert,fragmentShader:hi.meshtoon_frag},matcap:{uniforms:Yn([ui.common,ui.bumpmap,ui.normalmap,ui.displacementmap,ui.fog,{matcap:{value:null}}]),vertexShader:hi.meshmatcap_vert,fragmentShader:hi.meshmatcap_frag},points:{uniforms:Yn([ui.points,ui.fog]),vertexShader:hi.points_vert,fragmentShader:hi.points_frag},dashed:{uniforms:Yn([ui.common,ui.fog,{scale:{value:1},dashSize:{value:1},totalSize:{value:2}}]),vertexShader:hi.linedashed_vert,fragmentShader:hi.linedashed_frag},depth:{uniforms:Yn([ui.common,ui.displacementmap]),vertexShader:hi.depth_vert,fragmentShader:hi.depth_frag},normal:{uniforms:Yn([ui.common,ui.bumpmap,ui.normalmap,ui.displacementmap,{opacity:{value:1}}]),vertexShader:hi.normal_vert,fragmentShader:hi.normal_frag},sprite:{uniforms:Yn([ui.sprite,ui.fog]),vertexShader:hi.sprite_vert,fragmentShader:hi.sprite_frag},background:{uniforms:{uvTransform:{value:new yt},t2D:{value:null}},vertexShader:hi.background_vert,fragmentShader:hi.background_frag},cube:{uniforms:Yn([ui.envmap,{opacity:{value:1}}]),vertexShader:hi.cube_vert,fragmentShader:hi.cube_frag},equirect:{uniforms:{tEquirect:{value:null}},vertexShader:hi.equirect_vert,fragmentShader:hi.equirect_frag},distanceRGBA:{uniforms:Yn([ui.common,ui.displacementmap,{referencePosition:{value:new Lt},nearDistance:{value:1},farDistance:{value:1e3}}]),vertexShader:hi.distanceRGBA_vert,fragmentShader:hi.distanceRGBA_frag},shadow:{uniforms:Yn([ui.lights,ui.fog,{color:{value:new tn(0)},opacity:{value:1}}]),vertexShader:hi.shadow_vert,fragmentShader:hi.shadow_frag}};function pi(t,e,n,i,r){const s=new tn(0);let a,o,c=0,h=null,u=0,d=null;function p(t,e){n.buffers.color.setClear(t.r,t.g,t.b,e,r)}return{getClearColor:function(){return s},setClearColor:function(t,e=1){s.set(t),c=e,p(s,c)},getClearAlpha:function(){return c},setClearAlpha:function(t){c=t,p(s,c)},render:function(n,r,m,f){let g=!0===r.isScene?r.background:null;g&&g.isTexture&&(g=e.get(g));const v=t.xr,y=v.getSession&&v.getSession();y&&"additive"===y.environmentBlendMode&&(g=null),null===g?p(s,c):g&&g.isColor&&(p(g,1),f=!0),(t.autoClear||f)&&t.clear(t.autoClearColor,t.autoClearDepth,t.autoClearStencil),g&&(g.isCubeTexture||g.mapping===l)?(void 0===o&&(o=new Wn(new qn(1,1,1),new Jn({name:"BackgroundCubeMaterial",uniforms:Xn(di.cube.uniforms),vertexShader:di.cube.vertexShader,fragmentShader:di.cube.fragmentShader,side:1,depthTest:!1,depthWrite:!1,fog:!1})),o.geometry.deleteAttribute("normal"),o.geometry.deleteAttribute("uv"),o.onBeforeRender=function(t,e,n){this.matrixWorld.copyPosition(n.matrixWorld)},Object.defineProperty(o.material,"envMap",{get:function(){return this.uniforms.envMap.value}}),i.update(o)),o.material.uniforms.envMap.value=g,o.material.uniforms.flipEnvMap.value=g.isCubeTexture&&g._needsFlipEnvMap?-1:1,h===g&&u===g.version&&d===t.toneMapping||(o.material.needsUpdate=!0,h=g,u=g.version,d=t.toneMapping),n.unshift(o,o.geometry,o.material,0,0,null)):g&&g.isTexture&&(void 0===a&&(a=new Wn(new ci(2,2),new Jn({name:"BackgroundMaterial",uniforms:Xn(di.background.uniforms),vertexShader:di.background.vertexShader,fragmentShader:di.background.fragmentShader,side:0,depthTest:!1,depthWrite:!1,fog:!1})),a.geometry.deleteAttribute("normal"),Object.defineProperty(a.material,"map",{get:function(){return this.uniforms.t2D.value}}),i.update(a)),a.material.uniforms.t2D.value=g,!0===g.matrixAutoUpdate&&g.updateMatrix(),a.material.uniforms.uvTransform.value.copy(g.matrix),h===g&&u===g.version&&d===t.toneMapping||(a.material.needsUpdate=!0,h=g,u=g.version,d=t.toneMapping),n.unshift(a,a.geometry,a.material,0,0,null))}}}function mi(t,e,n,i){const r=t.getParameter(34921),s=i.isWebGL2?null:e.get("OES_vertex_array_object"),a=i.isWebGL2||null!==s,o={},l=d(null);let c=l;function h(e){return i.isWebGL2?t.bindVertexArray(e):s.bindVertexArrayOES(e)}function u(e){return i.isWebGL2?t.deleteVertexArray(e):s.deleteVertexArrayOES(e)}function d(t){const e=[],n=[],i=[];for(let t=0;t=0){const s=l[e];if(void 0!==s){const e=s.normalized,r=s.itemSize,a=n.get(s);if(void 0===a)continue;const l=a.buffer,c=a.type,h=a.bytesPerElement;if(s.isInterleavedBufferAttribute){const n=s.data,a=n.stride,u=s.offset;n&&n.isInstancedInterleavedBuffer?(f(i,n.meshPerAttribute),void 0===o._maxInstanceCount&&(o._maxInstanceCount=n.meshPerAttribute*n.count)):m(i),t.bindBuffer(34962,l),v(i,r,c,e,a*h,u*h)}else s.isInstancedBufferAttribute?(f(i,s.meshPerAttribute),void 0===o._maxInstanceCount&&(o._maxInstanceCount=s.meshPerAttribute*s.count)):m(i),t.bindBuffer(34962,l),v(i,r,c,e,0,0)}else if("instanceMatrix"===e){const e=n.get(r.instanceMatrix);if(void 0===e)continue;const s=e.buffer,a=e.type;f(i+0,1),f(i+1,1),f(i+2,1),f(i+3,1),t.bindBuffer(34962,s),t.vertexAttribPointer(i+0,4,a,!1,64,0),t.vertexAttribPointer(i+1,4,a,!1,64,16),t.vertexAttribPointer(i+2,4,a,!1,64,32),t.vertexAttribPointer(i+3,4,a,!1,64,48)}else if("instanceColor"===e){const e=n.get(r.instanceColor);if(void 0===e)continue;const s=e.buffer,a=e.type;f(i,1),t.bindBuffer(34962,s),t.vertexAttribPointer(i,3,a,!1,12,0)}else if(void 0!==h){const n=h[e];if(void 0!==n)switch(n.length){case 2:t.vertexAttrib2fv(i,n);break;case 3:t.vertexAttrib3fv(i,n);break;case 4:t.vertexAttrib4fv(i,n);break;default:t.vertexAttrib1fv(i,n)}}}}g()}(r,l,u,y),null!==x&&t.bindBuffer(34963,n.get(x).buffer))},reset:y,resetDefaultState:x,dispose:function(){y();for(const t in o){const e=o[t];for(const t in e){const n=e[t];for(const t in n)u(n[t].object),delete n[t];delete e[t]}delete o[t]}},releaseStatesOfGeometry:function(t){if(void 0===o[t.id])return;const e=o[t.id];for(const t in e){const n=e[t];for(const t in n)u(n[t].object),delete n[t];delete e[t]}delete o[t.id]},releaseStatesOfProgram:function(t){for(const e in o){const n=o[e];if(void 0===n[t.id])continue;const i=n[t.id];for(const t in i)u(i[t].object),delete i[t];delete n[t.id]}},initAttributes:p,enableAttribute:m,disableUnusedAttributes:g}}function fi(t,e,n,i){const r=i.isWebGL2;let s;this.setMode=function(t){s=t},this.render=function(e,i){t.drawArrays(s,e,i),n.update(i,s,1)},this.renderInstances=function(i,a,o){if(0===o)return;let l,c;if(r)l=t,c="drawArraysInstanced";else if(l=e.get("ANGLE_instanced_arrays"),c="drawArraysInstancedANGLE",null===l)return void console.error("THREE.WebGLBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.");l[c](s,i,a,o),n.update(a,s,o)}}function gi(t,e,n){let i;function r(e){if("highp"===e){if(t.getShaderPrecisionFormat(35633,36338).precision>0&&t.getShaderPrecisionFormat(35632,36338).precision>0)return"highp";e="mediump"}return"mediump"===e&&t.getShaderPrecisionFormat(35633,36337).precision>0&&t.getShaderPrecisionFormat(35632,36337).precision>0?"mediump":"lowp"}const s="undefined"!=typeof WebGL2RenderingContext&&t instanceof WebGL2RenderingContext||"undefined"!=typeof WebGL2ComputeRenderingContext&&t instanceof WebGL2ComputeRenderingContext;let a=void 0!==n.precision?n.precision:"highp";const o=r(a);o!==a&&(console.warn("THREE.WebGLRenderer:",a,"not supported, using",o,"instead."),a=o);const l=!0===n.logarithmicDepthBuffer,c=t.getParameter(34930),h=t.getParameter(35660),u=t.getParameter(3379),d=t.getParameter(34076),p=t.getParameter(34921),m=t.getParameter(36347),f=t.getParameter(36348),g=t.getParameter(36349),v=h>0,y=s||e.has("OES_texture_float");return{isWebGL2:s,getMaxAnisotropy:function(){if(void 0!==i)return i;if(!0===e.has("EXT_texture_filter_anisotropic")){const n=e.get("EXT_texture_filter_anisotropic");i=t.getParameter(n.MAX_TEXTURE_MAX_ANISOTROPY_EXT)}else i=0;return i},getMaxPrecision:r,precision:a,logarithmicDepthBuffer:l,maxTextures:c,maxVertexTextures:h,maxTextureSize:u,maxCubemapSize:d,maxAttributes:p,maxVertexUniforms:m,maxVaryings:f,maxFragmentUniforms:g,vertexTextures:v,floatFragmentTextures:y,floatVertexTextures:v&&y,maxSamples:s?t.getParameter(36183):0}}function vi(t){const e=this;let n=null,i=0,r=!1,s=!1;const a=new Ne,o=new yt,l={value:null,needsUpdate:!1};function c(){l.value!==n&&(l.value=n,l.needsUpdate=i>0),e.numPlanes=i,e.numIntersection=0}function h(t,n,i,r){const s=null!==t?t.length:0;let c=null;if(0!==s){if(c=l.value,!0!==r||null===c){const e=i+4*s,r=n.matrixWorldInverse;o.getNormalMatrix(r),(null===c||c.length0){const a=t.getRenderTarget(),o=new ni(s.height/2);return o.fromEquirectangularTexture(t,r),e.set(r,o),t.setRenderTarget(a),r.addEventListener("dispose",i),n(o.texture,r.mapping)}return null}}}return r},dispose:function(){e=new WeakMap}}}function xi(t){const e={};function n(n){if(void 0!==e[n])return e[n];let i;switch(n){case"WEBGL_depth_texture":i=t.getExtension("WEBGL_depth_texture")||t.getExtension("MOZ_WEBGL_depth_texture")||t.getExtension("WEBKIT_WEBGL_depth_texture");break;case"EXT_texture_filter_anisotropic":i=t.getExtension("EXT_texture_filter_anisotropic")||t.getExtension("MOZ_EXT_texture_filter_anisotropic")||t.getExtension("WEBKIT_EXT_texture_filter_anisotropic");break;case"WEBGL_compressed_texture_s3tc":i=t.getExtension("WEBGL_compressed_texture_s3tc")||t.getExtension("MOZ_WEBGL_compressed_texture_s3tc")||t.getExtension("WEBKIT_WEBGL_compressed_texture_s3tc");break;case"WEBGL_compressed_texture_pvrtc":i=t.getExtension("WEBGL_compressed_texture_pvrtc")||t.getExtension("WEBKIT_WEBGL_compressed_texture_pvrtc");break;default:i=t.getExtension(n)}return e[n]=i,i}return{has:function(t){return null!==n(t)},init:function(t){t.isWebGL2?n("EXT_color_buffer_float"):(n("WEBGL_depth_texture"),n("OES_texture_float"),n("OES_texture_half_float"),n("OES_texture_half_float_linear"),n("OES_standard_derivatives"),n("OES_element_index_uint"),n("OES_vertex_array_object"),n("ANGLE_instanced_arrays")),n("OES_texture_float_linear"),n("EXT_color_buffer_half_float")},get:function(t){const e=n(t);return null===e&&console.warn("THREE.WebGLRenderer: "+t+" extension not supported."),e}}}function _i(t,e,n,i){const r={},s=new WeakMap;function a(t){const o=t.target;null!==o.index&&e.remove(o.index);for(const t in o.attributes)e.remove(o.attributes[t]);o.removeEventListener("dispose",a),delete r[o.id];const l=s.get(o);l&&(e.remove(l),s.delete(o)),i.releaseStatesOfGeometry(o),!0===o.isInstancedBufferGeometry&&delete o._maxInstanceCount,n.memory.geometries--}function o(t){const n=[],i=t.index,r=t.attributes.position;let a=0;if(null!==i){const t=i.array;a=i.version;for(let e=0,i=t.length;e65535?dn:hn)(n,1);o.version=a;const l=s.get(t);l&&e.remove(l),s.set(t,o)}return{get:function(t,e){return!0===r[e.id]||(e.addEventListener("dispose",a),r[e.id]=!0,n.memory.geometries++),e},update:function(t){const n=t.attributes;for(const t in n)e.update(n[t],34962);const i=t.morphAttributes;for(const t in i){const n=i[t];for(let t=0,i=n.length;t0)return t;const r=e*n;let s=Ii[r];if(void 0===s&&(s=new Float32Array(r),Ii[r]=s),0!==e){i.toArray(s,0);for(let i=1,r=0;i!==e;++i)r+=n,t[i].toArray(s,r)}return s}function Hi(t,e){if(t.length!==e.length)return!1;for(let n=0,i=t.length;n/gm;function kr(t){return t.replace(Ur,Vr)}function Vr(t,e){const n=hi[e];if(void 0===n)throw new Error("Can not resolve #include <"+e+">");return kr(n)}const Wr=/#pragma unroll_loop[\s]+?for \( int i \= (\d+)\; i < (\d+)\; i \+\+ \) \{([\s\S]+?)(?=\})\}/g,jr=/#pragma unroll_loop_start\s+for\s*\(\s*int\s+i\s*=\s*(\d+)\s*;\s*i\s*<\s*(\d+)\s*;\s*i\s*\+\+\s*\)\s*{([\s\S]+?)}\s+#pragma unroll_loop_end/g;function qr(t){return t.replace(jr,Yr).replace(Wr,Xr)}function Xr(t,e,n,i){return console.warn("WebGLProgram: #pragma unroll_loop shader syntax is deprecated. Please use #pragma unroll_loop_start syntax instead."),Yr(t,e,n,i)}function Yr(t,e,n,i){let r="";for(let t=parseInt(e);t0?t.gammaFactor:1,v=n.isWebGL2?"":function(t){return[t.extensionDerivatives||t.envMapCubeUV||t.bumpMap||t.tangentSpaceNormalMap||t.clearcoatNormalMap||t.flatShading||"physical"===t.shaderID?"#extension GL_OES_standard_derivatives : enable":"",(t.extensionFragDepth||t.logarithmicDepthBuffer)&&t.rendererExtensionFragDepth?"#extension GL_EXT_frag_depth : enable":"",t.extensionDrawBuffers&&t.rendererExtensionDrawBuffers?"#extension GL_EXT_draw_buffers : require":"",(t.extensionShaderTextureLOD||t.envMap)&&t.rendererExtensionShaderTextureLod?"#extension GL_EXT_shader_texture_lod : enable":""].filter(Or).join("\n")}(n),y=function(t){const e=[];for(const n in t){const i=t[n];!1!==i&&e.push("#define "+n+" "+i)}return e.join("\n")}(o),x=a.createProgram();let _,w,b=n.glslVersion?"#version "+n.glslVersion+"\n":"";n.isRawShaderMaterial?(_=[y].filter(Or).join("\n"),_.length>0&&(_+="\n"),w=[v,y].filter(Or).join("\n"),w.length>0&&(w+="\n")):(_=[Zr(n),"#define SHADER_NAME "+n.shaderName,y,n.instancing?"#define USE_INSTANCING":"",n.instancingColor?"#define USE_INSTANCING_COLOR":"",n.supportsVertexTextures?"#define VERTEX_TEXTURES":"","#define GAMMA_FACTOR "+g,"#define MAX_BONES "+n.maxBones,n.useFog&&n.fog?"#define USE_FOG":"",n.useFog&&n.fogExp2?"#define FOG_EXP2":"",n.map?"#define USE_MAP":"",n.envMap?"#define USE_ENVMAP":"",n.envMap?"#define "+m:"",n.lightMap?"#define USE_LIGHTMAP":"",n.aoMap?"#define USE_AOMAP":"",n.emissiveMap?"#define USE_EMISSIVEMAP":"",n.bumpMap?"#define USE_BUMPMAP":"",n.normalMap?"#define USE_NORMALMAP":"",n.normalMap&&n.objectSpaceNormalMap?"#define OBJECTSPACE_NORMALMAP":"",n.normalMap&&n.tangentSpaceNormalMap?"#define TANGENTSPACE_NORMALMAP":"",n.clearcoatMap?"#define USE_CLEARCOATMAP":"",n.clearcoatRoughnessMap?"#define USE_CLEARCOAT_ROUGHNESSMAP":"",n.clearcoatNormalMap?"#define USE_CLEARCOAT_NORMALMAP":"",n.displacementMap&&n.supportsVertexTextures?"#define USE_DISPLACEMENTMAP":"",n.specularMap?"#define USE_SPECULARMAP":"",n.roughnessMap?"#define USE_ROUGHNESSMAP":"",n.metalnessMap?"#define USE_METALNESSMAP":"",n.alphaMap?"#define USE_ALPHAMAP":"",n.transmissionMap?"#define USE_TRANSMISSIONMAP":"",n.vertexTangents?"#define USE_TANGENT":"",n.vertexColors?"#define USE_COLOR":"",n.vertexAlphas?"#define USE_COLOR_ALPHA":"",n.vertexUvs?"#define USE_UV":"",n.uvsVertexOnly?"#define UVS_VERTEX_ONLY":"",n.flatShading?"#define FLAT_SHADED":"",n.skinning?"#define USE_SKINNING":"",n.useVertexTexture?"#define BONE_TEXTURE":"",n.morphTargets?"#define USE_MORPHTARGETS":"",n.morphNormals&&!1===n.flatShading?"#define USE_MORPHNORMALS":"",n.doubleSided?"#define DOUBLE_SIDED":"",n.flipSided?"#define FLIP_SIDED":"",n.shadowMapEnabled?"#define USE_SHADOWMAP":"",n.shadowMapEnabled?"#define "+d:"",n.sizeAttenuation?"#define USE_SIZEATTENUATION":"",n.logarithmicDepthBuffer?"#define USE_LOGDEPTHBUF":"",n.logarithmicDepthBuffer&&n.rendererExtensionFragDepth?"#define USE_LOGDEPTHBUF_EXT":"","uniform mat4 modelMatrix;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","uniform mat4 viewMatrix;","uniform mat3 normalMatrix;","uniform vec3 cameraPosition;","uniform bool isOrthographic;","#ifdef USE_INSTANCING","\tattribute mat4 instanceMatrix;","#endif","#ifdef USE_INSTANCING_COLOR","\tattribute vec3 instanceColor;","#endif","attribute vec3 position;","attribute vec3 normal;","attribute vec2 uv;","#ifdef USE_TANGENT","\tattribute vec4 tangent;","#endif","#if defined( USE_COLOR_ALPHA )","\tattribute vec4 color;","#elif defined( USE_COLOR )","\tattribute vec3 color;","#endif","#ifdef USE_MORPHTARGETS","\tattribute vec3 morphTarget0;","\tattribute vec3 morphTarget1;","\tattribute vec3 morphTarget2;","\tattribute vec3 morphTarget3;","\t#ifdef USE_MORPHNORMALS","\t\tattribute vec3 morphNormal0;","\t\tattribute vec3 morphNormal1;","\t\tattribute vec3 morphNormal2;","\t\tattribute vec3 morphNormal3;","\t#else","\t\tattribute vec3 morphTarget4;","\t\tattribute vec3 morphTarget5;","\t\tattribute vec3 morphTarget6;","\t\tattribute vec3 morphTarget7;","\t#endif","#endif","#ifdef USE_SKINNING","\tattribute vec4 skinIndex;","\tattribute vec4 skinWeight;","#endif","\n"].filter(Or).join("\n"),w=[v,Zr(n),"#define SHADER_NAME "+n.shaderName,y,n.alphaTest?"#define ALPHATEST "+n.alphaTest+(n.alphaTest%1?"":".0"):"","#define GAMMA_FACTOR "+g,n.useFog&&n.fog?"#define USE_FOG":"",n.useFog&&n.fogExp2?"#define FOG_EXP2":"",n.map?"#define USE_MAP":"",n.matcap?"#define USE_MATCAP":"",n.envMap?"#define USE_ENVMAP":"",n.envMap?"#define "+p:"",n.envMap?"#define "+m:"",n.envMap?"#define "+f:"",n.lightMap?"#define USE_LIGHTMAP":"",n.aoMap?"#define USE_AOMAP":"",n.emissiveMap?"#define USE_EMISSIVEMAP":"",n.bumpMap?"#define USE_BUMPMAP":"",n.normalMap?"#define USE_NORMALMAP":"",n.normalMap&&n.objectSpaceNormalMap?"#define OBJECTSPACE_NORMALMAP":"",n.normalMap&&n.tangentSpaceNormalMap?"#define TANGENTSPACE_NORMALMAP":"",n.clearcoatMap?"#define USE_CLEARCOATMAP":"",n.clearcoatRoughnessMap?"#define USE_CLEARCOAT_ROUGHNESSMAP":"",n.clearcoatNormalMap?"#define USE_CLEARCOAT_NORMALMAP":"",n.specularMap?"#define USE_SPECULARMAP":"",n.roughnessMap?"#define USE_ROUGHNESSMAP":"",n.metalnessMap?"#define USE_METALNESSMAP":"",n.alphaMap?"#define USE_ALPHAMAP":"",n.sheen?"#define USE_SHEEN":"",n.transmissionMap?"#define USE_TRANSMISSIONMAP":"",n.vertexTangents?"#define USE_TANGENT":"",n.vertexColors||n.instancingColor?"#define USE_COLOR":"",n.vertexAlphas?"#define USE_COLOR_ALPHA":"",n.vertexUvs?"#define USE_UV":"",n.uvsVertexOnly?"#define UVS_VERTEX_ONLY":"",n.gradientMap?"#define USE_GRADIENTMAP":"",n.flatShading?"#define FLAT_SHADED":"",n.doubleSided?"#define DOUBLE_SIDED":"",n.flipSided?"#define FLIP_SIDED":"",n.shadowMapEnabled?"#define USE_SHADOWMAP":"",n.shadowMapEnabled?"#define "+d:"",n.premultipliedAlpha?"#define PREMULTIPLIED_ALPHA":"",n.physicallyCorrectLights?"#define PHYSICALLY_CORRECT_LIGHTS":"",n.logarithmicDepthBuffer?"#define USE_LOGDEPTHBUF":"",n.logarithmicDepthBuffer&&n.rendererExtensionFragDepth?"#define USE_LOGDEPTHBUF_EXT":"",(n.extensionShaderTextureLOD||n.envMap)&&n.rendererExtensionShaderTextureLod?"#define TEXTURE_LOD_EXT":"","uniform mat4 viewMatrix;","uniform vec3 cameraPosition;","uniform bool isOrthographic;",0!==n.toneMapping?"#define TONE_MAPPING":"",0!==n.toneMapping?hi.tonemapping_pars_fragment:"",0!==n.toneMapping?Fr("toneMapping",n.toneMapping):"",n.dithering?"#define DITHERING":"",hi.encodings_pars_fragment,n.map?Br("mapTexelToLinear",n.mapEncoding):"",n.matcap?Br("matcapTexelToLinear",n.matcapEncoding):"",n.envMap?Br("envMapTexelToLinear",n.envMapEncoding):"",n.emissiveMap?Br("emissiveMapTexelToLinear",n.emissiveMapEncoding):"",n.lightMap?Br("lightMapTexelToLinear",n.lightMapEncoding):"",zr("linearToOutputTexel",n.outputEncoding),n.depthPacking?"#define DEPTH_PACKING "+n.depthPacking:"","\n"].filter(Or).join("\n")),h=kr(h),h=Hr(h,n),h=Gr(h,n),u=kr(u),u=Hr(u,n),u=Gr(u,n),h=qr(h),u=qr(u),n.isWebGL2&&!0!==n.isRawShaderMaterial&&(b="#version 300 es\n",_=["#define attribute in","#define varying out","#define texture2D texture"].join("\n")+"\n"+_,w=["#define varying in",n.glslVersion===it?"":"out highp vec4 pc_fragColor;",n.glslVersion===it?"":"#define gl_FragColor pc_fragColor","#define gl_FragDepthEXT gl_FragDepth","#define texture2D texture","#define textureCube texture","#define texture2DProj textureProj","#define texture2DLodEXT textureLod","#define texture2DProjLodEXT textureProjLod","#define textureCubeLodEXT textureLod","#define texture2DGradEXT textureGrad","#define texture2DProjGradEXT textureProjGrad","#define textureCubeGradEXT textureGrad"].join("\n")+"\n"+w);const M=b+w+u,S=Pr(a,35633,b+_+h),T=Pr(a,35632,M);if(a.attachShader(x,S),a.attachShader(x,T),void 0!==n.index0AttributeName?a.bindAttribLocation(x,0,n.index0AttributeName):!0===n.morphTargets&&a.bindAttribLocation(x,0,"position"),a.linkProgram(x),t.debug.checkShaderErrors){const t=a.getProgramInfoLog(x).trim(),e=a.getShaderInfoLog(S).trim(),n=a.getShaderInfoLog(T).trim();let i=!0,r=!0;if(!1===a.getProgramParameter(x,35714)){i=!1;const e=Nr(a,S,"vertex"),n=Nr(a,T,"fragment");console.error("THREE.WebGLProgram: shader error: ",a.getError(),"35715",a.getProgramParameter(x,35715),"gl.getProgramInfoLog",t,e,n)}else""!==t?console.warn("THREE.WebGLProgram: gl.getProgramInfoLog()",t):""!==e&&""!==n||(r=!1);r&&(this.diagnostics={runnable:i,programLog:t,vertexShader:{log:e,prefix:_},fragmentShader:{log:n,prefix:w}})}let E,A;return a.deleteShader(S),a.deleteShader(T),this.getUniforms=function(){return void 0===E&&(E=new Cr(a,x)),E},this.getAttributes=function(){return void 0===A&&(A=function(t,e){const n={},i=t.getProgramParameter(e,35721);for(let r=0;r0,maxBones:S,useVertexTexture:u,morphTargets:r.morphTargets,morphNormals:r.morphNormals,numDirLights:a.directional.length,numPointLights:a.point.length,numSpotLights:a.spot.length,numRectAreaLights:a.rectArea.length,numHemiLights:a.hemi.length,numDirLightShadows:a.directionalShadowMap.length,numPointLightShadows:a.pointShadowMap.length,numSpotLightShadows:a.spotShadowMap.length,numClippingPlanes:s.numPlanes,numClipIntersection:s.numIntersection,dithering:r.dithering,shadowMapEnabled:t.shadowMap.enabled&&g.length>0,shadowMapType:t.shadowMap.type,toneMapping:r.toneMapped?t.toneMapping:0,physicallyCorrectLights:t.physicallyCorrectLights,premultipliedAlpha:r.premultipliedAlpha,alphaTest:r.alphaTest,doubleSided:2===r.side,flipSided:1===r.side,depthPacking:void 0!==r.depthPacking&&r.depthPacking,index0AttributeName:r.index0AttributeName,extensionDerivatives:r.extensions&&r.extensions.derivatives,extensionFragDepth:r.extensions&&r.extensions.fragDepth,extensionDrawBuffers:r.extensions&&r.extensions.drawBuffers,extensionShaderTextureLOD:r.extensions&&r.extensions.shaderTextureLOD,rendererExtensionFragDepth:o||n.has("EXT_frag_depth"),rendererExtensionDrawBuffers:o||n.has("WEBGL_draw_buffers"),rendererExtensionShaderTextureLod:o||n.has("EXT_shader_texture_lod"),customProgramCacheKey:r.customProgramCacheKey()}},getProgramCacheKey:function(e){const n=[];if(e.shaderID?n.push(e.shaderID):(n.push(e.fragmentShader),n.push(e.vertexShader)),void 0!==e.defines)for(const t in e.defines)n.push(t),n.push(e.defines[t]);if(!1===e.isRawShaderMaterial){for(let t=0;t1&&i.sort(t||$r),r.length>1&&r.sort(e||ts)}}}function ns(t){let e=new WeakMap;return{get:function(n,i){let r;return!1===e.has(n)?(r=new es(t),e.set(n,[r])):i>=e.get(n).length?(r=new es(t),e.get(n).push(r)):r=e.get(n)[i],r},dispose:function(){e=new WeakMap}}}function is(){const t={};return{get:function(e){if(void 0!==t[e.id])return t[e.id];let n;switch(e.type){case"DirectionalLight":n={direction:new Lt,color:new tn};break;case"SpotLight":n={position:new Lt,direction:new Lt,color:new tn,distance:0,coneCos:0,penumbraCos:0,decay:0};break;case"PointLight":n={position:new Lt,color:new tn,distance:0,decay:0};break;case"HemisphereLight":n={direction:new Lt,skyColor:new tn,groundColor:new tn};break;case"RectAreaLight":n={color:new tn,position:new Lt,halfWidth:new Lt,halfHeight:new Lt}}return t[e.id]=n,n}}}let rs=0;function ss(t,e){return(e.castShadow?1:0)-(t.castShadow?1:0)}function as(t,e){const n=new is,i=function(){const t={};return{get:function(e){if(void 0!==t[e.id])return t[e.id];let n;switch(e.type){case"DirectionalLight":case"SpotLight":n={shadowBias:0,shadowNormalBias:0,shadowRadius:1,shadowMapSize:new vt};break;case"PointLight":n={shadowBias:0,shadowNormalBias:0,shadowRadius:1,shadowMapSize:new vt,shadowCameraNear:1,shadowCameraFar:1e3}}return t[e.id]=n,n}}}(),r={version:0,hash:{directionalLength:-1,pointLength:-1,spotLength:-1,rectAreaLength:-1,hemiLength:-1,numDirectionalShadows:-1,numPointShadows:-1,numSpotShadows:-1},ambient:[0,0,0],probe:[],directional:[],directionalShadow:[],directionalShadowMap:[],directionalShadowMatrix:[],spot:[],spotShadow:[],spotShadowMap:[],spotShadowMatrix:[],rectArea:[],rectAreaLTC1:null,rectAreaLTC2:null,point:[],pointShadow:[],pointShadowMap:[],pointShadowMatrix:[],hemi:[]};for(let t=0;t<9;t++)r.probe.push(new Lt);const s=new Lt,a=new se,o=new se;return{setup:function(s){let a=0,o=0,l=0;for(let t=0;t<9;t++)r.probe[t].set(0,0,0);let c=0,h=0,u=0,d=0,p=0,m=0,f=0,g=0;s.sort(ss);for(let t=0,e=s.length;t0&&(e.isWebGL2||!0===t.has("OES_texture_float_linear")?(r.rectAreaLTC1=ui.LTC_FLOAT_1,r.rectAreaLTC2=ui.LTC_FLOAT_2):!0===t.has("OES_texture_half_float_linear")?(r.rectAreaLTC1=ui.LTC_HALF_1,r.rectAreaLTC2=ui.LTC_HALF_2):console.error("THREE.WebGLRenderer: Unable to use RectAreaLight. Missing WebGL extensions.")),r.ambient[0]=a,r.ambient[1]=o,r.ambient[2]=l;const v=r.hash;v.directionalLength===c&&v.pointLength===h&&v.spotLength===u&&v.rectAreaLength===d&&v.hemiLength===p&&v.numDirectionalShadows===m&&v.numPointShadows===f&&v.numSpotShadows===g||(r.directional.length=c,r.spot.length=u,r.rectArea.length=d,r.point.length=h,r.hemi.length=p,r.directionalShadow.length=m,r.directionalShadowMap.length=m,r.pointShadow.length=f,r.pointShadowMap.length=f,r.spotShadow.length=g,r.spotShadowMap.length=g,r.directionalShadowMatrix.length=m,r.pointShadowMatrix.length=f,r.spotShadowMatrix.length=g,v.directionalLength=c,v.pointLength=h,v.spotLength=u,v.rectAreaLength=d,v.hemiLength=p,v.numDirectionalShadows=m,v.numPointShadows=f,v.numSpotShadows=g,r.version=rs++)},setupView:function(t,e){let n=0,i=0,l=0,c=0,h=0;const u=e.matrixWorldInverse;for(let e=0,d=t.length;e=n.get(i).length?(s=new os(t,e),n.get(i).push(s)):s=n.get(i)[r],s},dispose:function(){n=new WeakMap}}}class cs extends Xe{constructor(t){super(),this.type="MeshDepthMaterial",this.depthPacking=3200,this.skinning=!1,this.morphTargets=!1,this.map=null,this.alphaMap=null,this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.wireframe=!1,this.wireframeLinewidth=1,this.fog=!1,this.setValues(t)}copy(t){return super.copy(t),this.depthPacking=t.depthPacking,this.skinning=t.skinning,this.morphTargets=t.morphTargets,this.map=t.map,this.alphaMap=t.alphaMap,this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this}}cs.prototype.isMeshDepthMaterial=!0;class hs extends Xe{constructor(t){super(),this.type="MeshDistanceMaterial",this.referencePosition=new Lt,this.nearDistance=1,this.farDistance=1e3,this.skinning=!1,this.morphTargets=!1,this.map=null,this.alphaMap=null,this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.fog=!1,this.setValues(t)}copy(t){return super.copy(t),this.referencePosition.copy(t.referencePosition),this.nearDistance=t.nearDistance,this.farDistance=t.farDistance,this.skinning=t.skinning,this.morphTargets=t.morphTargets,this.map=t.map,this.alphaMap=t.alphaMap,this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this}}hs.prototype.isMeshDistanceMaterial=!0;function us(t,e,n){let i=new ai;const r=new vt,s=new vt,a=new St,o=[],l=[],c={},h=n.maxTextureSize,u={0:1,1:0,2:2},d=new Jn({defines:{SAMPLE_RATE:2/8,HALF_SAMPLE_RATE:1/8},uniforms:{shadow_pass:{value:null},resolution:{value:new vt},radius:{value:4}},vertexShader:"void main() {\n\tgl_Position = vec4( position, 1.0 );\n}",fragmentShader:"uniform sampler2D shadow_pass;\nuniform vec2 resolution;\nuniform float radius;\n#include \nvoid main() {\n\tfloat mean = 0.0;\n\tfloat squared_mean = 0.0;\n\tfloat depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy ) / resolution ) );\n\tfor ( float i = -1.0; i < 1.0 ; i += SAMPLE_RATE) {\n\t\t#ifdef HORIZONTAL_PASS\n\t\t\tvec2 distribution = unpackRGBATo2Half( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( i, 0.0 ) * radius ) / resolution ) );\n\t\t\tmean += distribution.x;\n\t\t\tsquared_mean += distribution.y * distribution.y + distribution.x * distribution.x;\n\t\t#else\n\t\t\tfloat depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( 0.0, i ) * radius ) / resolution ) );\n\t\t\tmean += depth;\n\t\t\tsquared_mean += depth * depth;\n\t\t#endif\n\t}\n\tmean = mean * HALF_SAMPLE_RATE;\n\tsquared_mean = squared_mean * HALF_SAMPLE_RATE;\n\tfloat std_dev = sqrt( squared_mean - mean * mean );\n\tgl_FragColor = pack2HalfToRGBA( vec2( mean, std_dev ) );\n}"}),m=d.clone();m.defines.HORIZONTAL_PASS=1;const f=new En;f.setAttribute("position",new sn(new Float32Array([-1,-1,.5,3,-1,.5,-1,3,.5]),3));const v=new Wn(f,d),y=this;function x(n,i){const r=e.update(v);d.uniforms.shadow_pass.value=n.map.texture,d.uniforms.resolution.value=n.mapSize,d.uniforms.radius.value=n.radius,t.setRenderTarget(n.mapPass),t.clear(),t.renderBufferDirect(i,null,r,d,v,null),m.uniforms.shadow_pass.value=n.mapPass.texture,m.uniforms.resolution.value=n.mapSize,m.uniforms.radius.value=n.radius,t.setRenderTarget(n.map),t.clear(),t.renderBufferDirect(i,null,r,m,v,null)}function _(t,e,n){const i=t<<0|e<<1|n<<2;let r=o[i];return void 0===r&&(r=new cs({depthPacking:3201,morphTargets:t,skinning:e}),o[i]=r),r}function w(t,e,n){const i=t<<0|e<<1|n<<2;let r=l[i];return void 0===r&&(r=new hs({morphTargets:t,skinning:e}),l[i]=r),r}function b(e,n,i,r,s,a,o){let l=null,h=_,d=e.customDepthMaterial;if(!0===r.isPointLight&&(h=w,d=e.customDistanceMaterial),void 0===d){let t=!1;!0===i.morphTargets&&(t=n.morphAttributes&&n.morphAttributes.position&&n.morphAttributes.position.length>0);let r=!1;!0===e.isSkinnedMesh&&(!0===i.skinning?r=!0:console.warn("THREE.WebGLShadowMap: THREE.SkinnedMesh with material.skinning set to false:",e));l=h(t,r,!0===e.isInstancedMesh)}else l=d;if(t.localClippingEnabled&&!0===i.clipShadows&&0!==i.clippingPlanes.length){const t=l.uuid,e=i.uuid;let n=c[t];void 0===n&&(n={},c[t]=n);let r=n[e];void 0===r&&(r=l.clone(),n[e]=r),l=r}return l.visible=i.visible,l.wireframe=i.wireframe,l.side=3===o?null!==i.shadowSide?i.shadowSide:i.side:null!==i.shadowSide?i.shadowSide:u[i.side],l.clipShadows=i.clipShadows,l.clippingPlanes=i.clippingPlanes,l.clipIntersection=i.clipIntersection,l.wireframeLinewidth=i.wireframeLinewidth,l.linewidth=i.linewidth,!0===r.isPointLight&&!0===l.isMeshDistanceMaterial&&(l.referencePosition.setFromMatrixPosition(r.matrixWorld),l.nearDistance=s,l.farDistance=a),l}function M(n,r,s,a,o){if(!1===n.visible)return;if(n.layers.test(r.layers)&&(n.isMesh||n.isLine||n.isPoints)&&(n.castShadow||n.receiveShadow&&3===o)&&(!n.frustumCulled||i.intersectsObject(n))){n.modelViewMatrix.multiplyMatrices(s.matrixWorldInverse,n.matrixWorld);const i=e.update(n),r=n.material;if(Array.isArray(r)){const e=i.groups;for(let l=0,c=e.length;lh||r.y>h)&&(r.x>h&&(s.x=Math.floor(h/m.x),r.x=s.x*m.x,u.mapSize.x=s.x),r.y>h&&(s.y=Math.floor(h/m.y),r.y=s.y*m.y,u.mapSize.y=s.y)),null===u.map&&!u.isPointLightShadow&&3===this.type){const t={minFilter:g,magFilter:g,format:E};u.map=new Tt(r.x,r.y,t),u.map.texture.name=c.name+".shadowMap",u.mapPass=new Tt(r.x,r.y,t),u.camera.updateProjectionMatrix()}if(null===u.map){const t={minFilter:p,magFilter:p,format:E};u.map=new Tt(r.x,r.y,t),u.map.texture.name=c.name+".shadowMap",u.camera.updateProjectionMatrix()}t.setRenderTarget(u.map),t.clear();const f=u.getViewportCount();for(let t=0;t=1):-1!==R.indexOf("OpenGL ES")&&(L=parseFloat(/^OpenGL ES (\d)/.exec(R)[1]),A=L>=2);let C=null,P={};const D=new St(0,0,t.canvas.width,t.canvas.height),I=new St(0,0,t.canvas.width,t.canvas.height);function N(e,n,i){const r=new Uint8Array(4),s=t.createTexture();t.bindTexture(e,s),t.texParameteri(e,10241,9728),t.texParameteri(e,10240,9728);for(let e=0;ei||t.height>i)&&(r=i/Math.max(t.width,t.height)),r<1||!0===e){if("undefined"!=typeof HTMLImageElement&&t instanceof HTMLImageElement||"undefined"!=typeof HTMLCanvasElement&&t instanceof HTMLCanvasElement||"undefined"!=typeof ImageBitmap&&t instanceof ImageBitmap){const i=e?ft:Math.floor,s=i(r*t.width),a=i(r*t.height);void 0===P&&(P=I(s,a));const o=n?I(s,a):P;o.width=s,o.height=a;return o.getContext("2d").drawImage(t,0,0,s,a),console.warn("THREE.WebGLRenderer: Texture has been resized from ("+t.width+"x"+t.height+") to ("+s+"x"+a+")."),o}return"data"in t&&console.warn("THREE.WebGLRenderer: Image in DataTexture is too big ("+t.width+"x"+t.height+")."),t}return t}function B(t){return pt(t.width)&&pt(t.height)}function z(t,e){return t.generateMipmaps&&e&&t.minFilter!==p&&t.minFilter!==g}function F(e,n,r,s){t.generateMipmap(e);i.get(n).__maxMipLevel=Math.log2(Math.max(r,s))}function O(n,i,r){if(!1===o)return i;if(null!==n){if(void 0!==t[n])return t[n];console.warn("THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format '"+n+"'")}let s=i;return 6403===i&&(5126===r&&(s=33326),5131===r&&(s=33325),5121===r&&(s=33321)),6407===i&&(5126===r&&(s=34837),5131===r&&(s=34843),5121===r&&(s=32849)),6408===i&&(5126===r&&(s=34836),5131===r&&(s=34842),5121===r&&(s=32856)),33325!==s&&33326!==s&&34842!==s&&34836!==s||e.get("EXT_color_buffer_float"),s}function H(t){return t===p||t===m||t===f?9728:9729}function G(e){const n=e.target;n.removeEventListener("dispose",G),function(e){const n=i.get(e);if(void 0===n.__webglInit)return;t.deleteTexture(n.__webglTexture),i.remove(e)}(n),n.isVideoTexture&&C.delete(n),a.memory.textures--}function U(e){const n=e.target;n.removeEventListener("dispose",U),function(e){const n=e.texture,r=i.get(e),s=i.get(n);if(!e)return;void 0!==s.__webglTexture&&t.deleteTexture(s.__webglTexture);e.depthTexture&&e.depthTexture.dispose();if(e.isWebGLCubeRenderTarget)for(let e=0;e<6;e++)t.deleteFramebuffer(r.__webglFramebuffer[e]),r.__webglDepthbuffer&&t.deleteRenderbuffer(r.__webglDepthbuffer[e]);else t.deleteFramebuffer(r.__webglFramebuffer),r.__webglDepthbuffer&&t.deleteRenderbuffer(r.__webglDepthbuffer),r.__webglMultisampledFramebuffer&&t.deleteFramebuffer(r.__webglMultisampledFramebuffer),r.__webglColorRenderbuffer&&t.deleteRenderbuffer(r.__webglColorRenderbuffer),r.__webglDepthRenderbuffer&&t.deleteRenderbuffer(r.__webglDepthRenderbuffer);i.remove(n),i.remove(e)}(n),a.memory.textures--}let k=0;function V(t,e){const r=i.get(t);if(t.isVideoTexture&&function(t){const e=a.render.frame;C.get(t)!==e&&(C.set(t,e),t.update())}(t),t.version>0&&r.__version!==t.version){const n=t.image;if(void 0===n)console.warn("THREE.WebGLRenderer: Texture marked for update but image is undefined");else{if(!1!==n.complete)return void Z(r,t,e);console.warn("THREE.WebGLRenderer: Texture marked for update but image is incomplete")}}n.activeTexture(33984+e),n.bindTexture(3553,r.__webglTexture)}function W(e,r){const a=i.get(e);e.version>0&&a.__version!==e.version?function(e,i,r){if(6!==i.image.length)return;Y(e,i),n.activeTexture(33984+r),n.bindTexture(34067,e.__webglTexture),t.pixelStorei(37440,i.flipY),t.pixelStorei(37441,i.premultiplyAlpha),t.pixelStorei(3317,i.unpackAlignment),t.pixelStorei(37443,0);const a=i&&(i.isCompressedTexture||i.image[0].isCompressedTexture),l=i.image[0]&&i.image[0].isDataTexture,h=[];for(let t=0;t<6;t++)h[t]=a||l?l?i.image[t].image:i.image[t]:N(i.image[t],!1,!0,c);const u=h[0],d=B(u)||o,p=s.convert(i.format),m=s.convert(i.type),f=O(i.internalFormat,p,m);let g;if(X(34067,i,d),a){for(let t=0;t<6;t++){g=h[t].mipmaps;for(let e=0;e1||i.get(s).__currentAnisotropy)&&(t.texParameterf(n,a.TEXTURE_MAX_ANISOTROPY_EXT,Math.min(s.anisotropy,r.getMaxAnisotropy())),i.get(s).__currentAnisotropy=s.anisotropy)}}function Y(e,n){void 0===e.__webglInit&&(e.__webglInit=!0,n.addEventListener("dispose",G),e.__webglTexture=t.createTexture(),a.memory.textures++)}function Z(e,i,r){let a=3553;i.isDataTexture2DArray&&(a=35866),i.isDataTexture3D&&(a=32879),Y(e,i),n.activeTexture(33984+r),n.bindTexture(a,e.__webglTexture),t.pixelStorei(37440,i.flipY),t.pixelStorei(37441,i.premultiplyAlpha),t.pixelStorei(3317,i.unpackAlignment),t.pixelStorei(37443,0);const l=function(t){return!o&&(t.wrapS!==u||t.wrapT!==u||t.minFilter!==p&&t.minFilter!==g)}(i)&&!1===B(i.image),c=N(i.image,l,!1,x),h=B(c)||o,d=s.convert(i.format);let m,f=s.convert(i.type),v=O(i.internalFormat,d,f);X(a,i,h);const y=i.mipmaps;if(i.isDepthTexture)v=6402,o?v=i.type===b?36012:i.type===w?33190:i.type===S?35056:33189:i.type===b&&console.error("WebGLRenderer: Floating point depth texture requires WebGL2."),i.format===A&&6402===v&&i.type!==_&&i.type!==w&&(console.warn("THREE.WebGLRenderer: Use UnsignedShortType or UnsignedIntType for DepthFormat DepthTexture."),i.type=_,f=s.convert(i.type)),i.format===L&&6402===v&&(v=34041,i.type!==S&&(console.warn("THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture."),i.type=S,f=s.convert(i.type))),n.texImage2D(3553,0,v,c.width,c.height,0,d,f,null);else if(i.isDataTexture)if(y.length>0&&h){for(let t=0,e=y.length;t0&&h){for(let t=0,e=y.length;t=l&&console.warn("THREE.WebGLTextures: Trying to use "+t+" texture units while this GPU supports only "+l),k+=1,t},this.resetTextureUnits=function(){k=0},this.setTexture2D=V,this.setTexture2DArray=function(t,e){const r=i.get(t);t.version>0&&r.__version!==t.version?Z(r,t,e):(n.activeTexture(33984+e),n.bindTexture(35866,r.__webglTexture))},this.setTexture3D=function(t,e){const r=i.get(t);t.version>0&&r.__version!==t.version?Z(r,t,e):(n.activeTexture(33984+e),n.bindTexture(32879,r.__webglTexture))},this.setTextureCube=W,this.setupRenderTarget=function(e){const r=e.texture,l=i.get(e),c=i.get(r);e.addEventListener("dispose",U),c.__webglTexture=t.createTexture(),c.__version=r.version,a.memory.textures++;const h=!0===e.isWebGLCubeRenderTarget,u=!0===e.isWebGLMultisampleRenderTarget,d=r.isDataTexture3D||r.isDataTexture2DArray,p=B(e)||o;if(!o||r.format!==T||r.type!==b&&r.type!==M||(r.format=E,console.warn("THREE.WebGLRenderer: Rendering to textures with RGB format is not supported. Using RGBA format instead.")),h){l.__webglFramebuffer=[];for(let e=0;e<6;e++)l.__webglFramebuffer[e]=t.createFramebuffer()}else if(l.__webglFramebuffer=t.createFramebuffer(),u)if(o){l.__webglMultisampledFramebuffer=t.createFramebuffer(),l.__webglColorRenderbuffer=t.createRenderbuffer(),t.bindRenderbuffer(36161,l.__webglColorRenderbuffer);const i=s.convert(r.format),a=s.convert(r.type),o=O(r.internalFormat,i,a),c=$(e);t.renderbufferStorageMultisample(36161,c,o,e.width,e.height),n.bindFramebuffer(36160,l.__webglMultisampledFramebuffer),t.framebufferRenderbuffer(36160,36064,36161,l.__webglColorRenderbuffer),t.bindRenderbuffer(36161,null),e.depthBuffer&&(l.__webglDepthRenderbuffer=t.createRenderbuffer(),Q(l.__webglDepthRenderbuffer,e,!0)),n.bindFramebuffer(36160,null)}else console.warn("THREE.WebGLRenderer: WebGLMultisampleRenderTarget can only be used with WebGL2.");if(h){n.bindTexture(34067,c.__webglTexture),X(34067,r,p);for(let t=0;t<6;t++)J(l.__webglFramebuffer[t],e,36064,34069+t);z(r,p)&&F(34067,r,e.width,e.height),n.bindTexture(34067,null)}else{let t=3553;if(d)if(o){t=r.isDataTexture3D?32879:35866}else console.warn("THREE.DataTexture3D and THREE.DataTexture2DArray only supported with WebGL2.");n.bindTexture(t,c.__webglTexture),X(t,r,p),J(l.__webglFramebuffer,e,36064,t),z(r,p)&&F(3553,r,e.width,e.height),n.bindTexture(3553,null)}e.depthBuffer&&K(e)},this.updateRenderTargetMipmap=function(t){const e=t.texture;if(z(e,B(t)||o)){const r=t.isWebGLCubeRenderTarget?34067:3553,s=i.get(e).__webglTexture;n.bindTexture(r,s),F(r,e,t.width,t.height),n.bindTexture(r,null)}},this.updateMultisampleRenderTarget=function(e){if(e.isWebGLMultisampleRenderTarget)if(o){const r=e.width,s=e.height;let a=16384;e.depthBuffer&&(a|=256),e.stencilBuffer&&(a|=1024);const o=i.get(e);n.bindFramebuffer(36008,o.__webglMultisampledFramebuffer),n.bindFramebuffer(36009,o.__webglFramebuffer),t.blitFramebuffer(0,0,r,s,0,0,r,s,a,9728),n.bindFramebuffer(36008,null),n.bindFramebuffer(36009,o.__webglMultisampledFramebuffer)}else console.warn("THREE.WebGLRenderer: WebGLMultisampleRenderTarget can only be used with WebGL2.")},this.safeSetTexture2D=function(t,e){t&&t.isWebGLRenderTarget&&(!1===tt&&(console.warn("THREE.WebGLTextures.safeSetTexture2D: don't use render targets as textures. Use their .texture property instead."),tt=!0),t=t.texture),V(t,e)},this.safeSetTextureCube=function(t,e){t&&t.isWebGLCubeRenderTarget&&(!1===et&&(console.warn("THREE.WebGLTextures.safeSetTextureCube: don't use cube render targets as textures. Use their .texture property instead."),et=!0),t=t.texture),W(t,e)}}function ms(t,e,n){const i=n.isWebGL2;return{convert:function(t){let n;if(t===x)return 5121;if(1017===t)return 32819;if(1018===t)return 32820;if(1019===t)return 33635;if(1010===t)return 5120;if(1011===t)return 5122;if(t===_)return 5123;if(1013===t)return 5124;if(t===w)return 5125;if(t===b)return 5126;if(t===M)return i?5131:(n=e.get("OES_texture_half_float"),null!==n?n.HALF_FLOAT_OES:null);if(1021===t)return 6406;if(t===T)return 6407;if(t===E)return 6408;if(1024===t)return 6409;if(1025===t)return 6410;if(t===A)return 6402;if(t===L)return 34041;if(1028===t)return 6403;if(1029===t)return 36244;if(1030===t)return 33319;if(1031===t)return 33320;if(1032===t)return 36248;if(1033===t)return 36249;if(t===R||t===C||t===P||t===D){if(n=e.get("WEBGL_compressed_texture_s3tc"),null===n)return null;if(t===R)return n.COMPRESSED_RGB_S3TC_DXT1_EXT;if(t===C)return n.COMPRESSED_RGBA_S3TC_DXT1_EXT;if(t===P)return n.COMPRESSED_RGBA_S3TC_DXT3_EXT;if(t===D)return n.COMPRESSED_RGBA_S3TC_DXT5_EXT}if(t===I||t===N||t===B||t===z){if(n=e.get("WEBGL_compressed_texture_pvrtc"),null===n)return null;if(t===I)return n.COMPRESSED_RGB_PVRTC_4BPPV1_IMG;if(t===N)return n.COMPRESSED_RGB_PVRTC_2BPPV1_IMG;if(t===B)return n.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;if(t===z)return n.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG}if(36196===t)return n=e.get("WEBGL_compressed_texture_etc1"),null!==n?n.COMPRESSED_RGB_ETC1_WEBGL:null;if((t===F||t===O)&&(n=e.get("WEBGL_compressed_texture_etc"),null!==n)){if(t===F)return n.COMPRESSED_RGB8_ETC2;if(t===O)return n.COMPRESSED_RGBA8_ETC2_EAC}return 37808===t||37809===t||37810===t||37811===t||37812===t||37813===t||37814===t||37815===t||37816===t||37817===t||37818===t||37819===t||37820===t||37821===t||37840===t||37841===t||37842===t||37843===t||37844===t||37845===t||37846===t||37847===t||37848===t||37849===t||37850===t||37851===t||37852===t||37853===t?(n=e.get("WEBGL_compressed_texture_astc"),null!==n?t:null):36492===t?(n=e.get("EXT_texture_compression_bptc"),null!==n?t:null):t===S?i?34042:(n=e.get("WEBGL_depth_texture"),null!==n?n.UNSIGNED_INT_24_8_WEBGL:null):void 0}}}class fs extends Kn{constructor(t=[]){super(),this.cameras=t}}fs.prototype.isArrayCamera=!0;class gs extends Ce{constructor(){super(),this.type="Group"}}gs.prototype.isGroup=!0;const vs={type:"move"};class ys{constructor(){this._targetRay=null,this._grip=null,this._hand=null}getHandSpace(){return null===this._hand&&(this._hand=new gs,this._hand.matrixAutoUpdate=!1,this._hand.visible=!1,this._hand.joints={},this._hand.inputState={pinching:!1}),this._hand}getTargetRaySpace(){return null===this._targetRay&&(this._targetRay=new gs,this._targetRay.matrixAutoUpdate=!1,this._targetRay.visible=!1,this._targetRay.hasLinearVelocity=!1,this._targetRay.linearVelocity=new Lt,this._targetRay.hasAngularVelocity=!1,this._targetRay.angularVelocity=new Lt),this._targetRay}getGripSpace(){return null===this._grip&&(this._grip=new gs,this._grip.matrixAutoUpdate=!1,this._grip.visible=!1,this._grip.hasLinearVelocity=!1,this._grip.linearVelocity=new Lt,this._grip.hasAngularVelocity=!1,this._grip.angularVelocity=new Lt),this._grip}dispatchEvent(t){return null!==this._targetRay&&this._targetRay.dispatchEvent(t),null!==this._grip&&this._grip.dispatchEvent(t),null!==this._hand&&this._hand.dispatchEvent(t),this}disconnect(t){return this.dispatchEvent({type:"disconnected",data:t}),null!==this._targetRay&&(this._targetRay.visible=!1),null!==this._grip&&(this._grip.visible=!1),null!==this._hand&&(this._hand.visible=!1),this}update(t,e,n){let i=null,r=null,s=null;const a=this._targetRay,o=this._grip,l=this._hand;if(t&&"visible-blurred"!==e.session.visibilityState)if(null!==a&&(i=e.getPose(t.targetRaySpace,n),null!==i&&(a.matrix.fromArray(i.transform.matrix),a.matrix.decompose(a.position,a.rotation,a.scale),i.linearVelocity?(a.hasLinearVelocity=!0,a.linearVelocity.copy(i.linearVelocity)):a.hasLinearVelocity=!1,i.angularVelocity?(a.hasAngularVelocity=!0,a.angularVelocity.copy(i.angularVelocity)):a.hasAngularVelocity=!1,this.dispatchEvent(vs))),l&&t.hand){s=!0;for(const i of t.hand.values()){const t=e.getJointPose(i,n);if(void 0===l.joints[i.jointName]){const t=new gs;t.matrixAutoUpdate=!1,t.visible=!1,l.joints[i.jointName]=t,l.add(t)}const r=l.joints[i.jointName];null!==t&&(r.matrix.fromArray(t.transform.matrix),r.matrix.decompose(r.position,r.rotation,r.scale),r.jointRadius=t.radius),r.visible=null!==t}const i=l.joints["index-finger-tip"],r=l.joints["thumb-tip"],a=i.position.distanceTo(r.position),o=.02,c=.005;l.inputState.pinching&&a>o+c?(l.inputState.pinching=!1,this.dispatchEvent({type:"pinchend",handedness:t.handedness,target:this})):!l.inputState.pinching&&a<=o-c&&(l.inputState.pinching=!0,this.dispatchEvent({type:"pinchstart",handedness:t.handedness,target:this}))}else null!==o&&t.gripSpace&&(r=e.getPose(t.gripSpace,n),null!==r&&(o.matrix.fromArray(r.transform.matrix),o.matrix.decompose(o.position,o.rotation,o.scale),r.linearVelocity?(o.hasLinearVelocity=!0,o.linearVelocity.copy(r.linearVelocity)):o.hasLinearVelocity=!1,r.angularVelocity?(o.hasAngularVelocity=!0,o.angularVelocity.copy(r.angularVelocity)):o.hasAngularVelocity=!1));return null!==a&&(a.visible=null!==i),null!==o&&(o.visible=null!==r),null!==l&&(l.visible=null!==s),this}}class xs extends rt{constructor(t,e){super();const n=this,i=t.state;let r=null,s=1,a=null,o="local-floor",l=null;const c=[],h=new Map,u=new Kn;u.layers.enable(1),u.viewport=new St;const d=new Kn;d.layers.enable(2),d.viewport=new St;const p=[u,d],m=new fs;m.layers.enable(1),m.layers.enable(2);let f=null,g=null;function v(t){const e=h.get(t.inputSource);e&&e.dispatchEvent({type:t.type,data:t.inputSource})}function y(){h.forEach((function(t,e){t.disconnect(e)})),h.clear(),f=null,g=null,i.bindXRFramebuffer(null),t.setRenderTarget(t.getRenderTarget()),S.stop(),n.isPresenting=!1,n.dispatchEvent({type:"sessionend"})}function x(t){const e=r.inputSources;for(let t=0;t0&&Rt(s,t,e),a.length>0&&Rt(a,t,e),null!==_&&(J.updateRenderTargetMipmap(_),J.updateMultisampleRenderTarget(_)),!0===t.isScene&&t.onAfterRender(f,t,e),q.buffers.depth.setTest(!0),q.buffers.depth.setMask(!0),q.buffers.color.setMask(!0),q.setPolygonOffset(!1),dt.resetDefaultState(),w=-1,S=null,m.pop(),d=m.length>0?m[m.length-1]:null,p.pop(),u=p.length>0?p[p.length-1]:null},this.getActiveCubeFace=function(){return v},this.getActiveMipmapLevel=function(){return y},this.getRenderTarget=function(){return _},this.setRenderTarget=function(t,e=0,n=0){_=t,v=e,y=n,t&&void 0===Z.get(t).__webglFramebuffer&&J.setupRenderTarget(t);let i=null,r=!1,s=!1;if(t){const n=t.texture;(n.isDataTexture3D||n.isDataTexture2DArray)&&(s=!0);const a=Z.get(t).__webglFramebuffer;t.isWebGLCubeRenderTarget?(i=a[e],r=!0):i=t.isWebGLMultisampleRenderTarget?Z.get(t).__webglMultisampledFramebuffer:a,T.copy(t.viewport),A.copy(t.scissor),L=t.scissorTest}else T.copy(N).multiplyScalar(P).floor(),A.copy(B).multiplyScalar(P).floor(),L=z;if(q.bindFramebuffer(36160,i),q.viewport(T),q.scissor(A),q.setScissorTest(L),r){const i=Z.get(t.texture);pt.framebufferTexture2D(36160,36064,34069+e,i.__webglTexture,n)}else if(s){const i=Z.get(t.texture),r=e||0;pt.framebufferTextureLayer(36160,36064,i.__webglTexture,n||0,r)}},this.readRenderTargetPixels=function(t,e,n,i,r,s,a){if(!t||!t.isWebGLRenderTarget)return void console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.");let o=Z.get(t).__webglFramebuffer;if(t.isWebGLCubeRenderTarget&&void 0!==a&&(o=o[a]),o){q.bindFramebuffer(36160,o);try{const a=t.texture,o=a.format,l=a.type;if(o!==E&&ut.convert(o)!==pt.getParameter(35739))return void console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.");const c=l===M&&(W.has("EXT_color_buffer_half_float")||j.isWebGL2&&W.has("EXT_color_buffer_float"));if(!(l===x||ut.convert(l)===pt.getParameter(35738)||l===b&&(j.isWebGL2||W.has("OES_texture_float")||W.has("WEBGL_color_buffer_float"))||c))return void console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.");36053===pt.checkFramebufferStatus(36160)?e>=0&&e<=t.width-i&&n>=0&&n<=t.height-r&&pt.readPixels(e,n,i,r,ut.convert(o),ut.convert(l),s):console.error("THREE.WebGLRenderer.readRenderTargetPixels: readPixels from renderTarget failed. Framebuffer not complete.")}finally{const t=null!==_?Z.get(_).__webglFramebuffer:null;q.bindFramebuffer(36160,t)}}},this.copyFramebufferToTexture=function(t,e,n=0){const i=Math.pow(2,-n),r=Math.floor(e.image.width*i),s=Math.floor(e.image.height*i),a=ut.convert(e.format);J.setTexture2D(e,0),pt.copyTexImage2D(3553,n,a,t.x,t.y,r,s,0),q.unbindTexture()},this.copyTextureToTexture=function(t,e,n,i=0){const r=e.image.width,s=e.image.height,a=ut.convert(n.format),o=ut.convert(n.type);J.setTexture2D(n,0),pt.pixelStorei(37440,n.flipY),pt.pixelStorei(37441,n.premultiplyAlpha),pt.pixelStorei(3317,n.unpackAlignment),e.isDataTexture?pt.texSubImage2D(3553,i,t.x,t.y,r,s,a,o,e.image.data):e.isCompressedTexture?pt.compressedTexSubImage2D(3553,i,t.x,t.y,e.mipmaps[0].width,e.mipmaps[0].height,a,e.mipmaps[0].data):pt.texSubImage2D(3553,i,t.x,t.y,a,o,e.image),0===i&&n.generateMipmaps&&pt.generateMipmap(3553),q.unbindTexture()},this.copyTextureToTexture3D=function(t,e,n,i,r=0){if(f.isWebGL1Renderer)return void console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: can only be used with WebGL2.");const{width:s,height:a,data:o}=n.image,l=ut.convert(i.format),c=ut.convert(i.type);let h;if(i.isDataTexture3D)J.setTexture3D(i,0),h=32879;else{if(!i.isDataTexture2DArray)return void console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: only supports THREE.DataTexture3D and THREE.DataTexture2DArray.");J.setTexture2DArray(i,0),h=35866}pt.pixelStorei(37440,i.flipY),pt.pixelStorei(37441,i.premultiplyAlpha),pt.pixelStorei(3317,i.unpackAlignment);const u=pt.getParameter(3314),d=pt.getParameter(32878),p=pt.getParameter(3316),m=pt.getParameter(3315),g=pt.getParameter(32877);pt.pixelStorei(3314,s),pt.pixelStorei(32878,a),pt.pixelStorei(3316,t.min.x),pt.pixelStorei(3315,t.min.y),pt.pixelStorei(32877,t.min.z),pt.texSubImage3D(h,r,e.x,e.y,e.z,t.max.x-t.min.x+1,t.max.y-t.min.y+1,t.max.z-t.min.z+1,l,c,o),pt.pixelStorei(3314,u),pt.pixelStorei(32878,d),pt.pixelStorei(3316,p),pt.pixelStorei(3315,m),pt.pixelStorei(32877,g),0===r&&i.generateMipmaps&&pt.generateMipmap(h),q.unbindTexture()},this.initTexture=function(t){J.setTexture2D(t,0),q.unbindTexture()},this.resetState=function(){v=0,y=0,_=null,q.reset(),dt.reset()},"undefined"!=typeof __THREE_DEVTOOLS__&&__THREE_DEVTOOLS__.dispatchEvent(new CustomEvent("observe",{detail:this}))}class bs extends ws{}bs.prototype.isWebGL1Renderer=!0;class Ms{constructor(t,e=25e-5){this.name="",this.color=new tn(t),this.density=e}clone(){return new Ms(this.color,this.density)}toJSON(){return{type:"FogExp2",color:this.color.getHex(),density:this.density}}}Ms.prototype.isFogExp2=!0;class Ss{constructor(t,e=1,n=1e3){this.name="",this.color=new tn(t),this.near=e,this.far=n}clone(){return new Ss(this.color,this.near,this.far)}toJSON(){return{type:"Fog",color:this.color.getHex(),near:this.near,far:this.far}}}Ss.prototype.isFog=!0;class Ts extends Ce{constructor(){super(),this.type="Scene",this.background=null,this.environment=null,this.fog=null,this.overrideMaterial=null,this.autoUpdate=!0,"undefined"!=typeof __THREE_DEVTOOLS__&&__THREE_DEVTOOLS__.dispatchEvent(new CustomEvent("observe",{detail:this}))}copy(t,e){return super.copy(t,e),null!==t.background&&(this.background=t.background.clone()),null!==t.environment&&(this.environment=t.environment.clone()),null!==t.fog&&(this.fog=t.fog.clone()),null!==t.overrideMaterial&&(this.overrideMaterial=t.overrideMaterial.clone()),this.autoUpdate=t.autoUpdate,this.matrixAutoUpdate=t.matrixAutoUpdate,this}toJSON(t){const e=super.toJSON(t);return null!==this.background&&(e.object.background=this.background.toJSON(t)),null!==this.environment&&(e.object.environment=this.environment.toJSON(t)),null!==this.fog&&(e.object.fog=this.fog.toJSON()),e}}Ts.prototype.isScene=!0;class Es{constructor(t,e){this.array=t,this.stride=e,this.count=void 0!==t?t.length/e:0,this.usage=et,this.updateRange={offset:0,count:-1},this.version=0,this.uuid=ct(),this.onUploadCallback=function(){}}set needsUpdate(t){!0===t&&this.version++}setUsage(t){return this.usage=t,this}copy(t){return this.array=new t.array.constructor(t.array),this.count=t.count,this.stride=t.stride,this.usage=t.usage,this}copyAt(t,e,n){t*=this.stride,n*=e.stride;for(let i=0,r=this.stride;it.far||e.push({distance:o,point:Ps.clone(),uv:je.getUV(Ps,Fs,Os,Hs,Gs,Us,ks,new vt),face:null,object:this})}copy(t){return super.copy(t),void 0!==t.center&&this.center.copy(t.center),this.material=t.material,this}}function Ws(t,e,n,i,r,s){Ns.subVectors(t,n).addScalar(.5).multiply(i),void 0!==r?(Bs.x=s*Ns.x-r*Ns.y,Bs.y=r*Ns.x+s*Ns.y):Bs.copy(Ns),t.copy(e),t.x+=Bs.x,t.y+=Bs.y,t.applyMatrix4(zs)}Vs.prototype.isSprite=!0;const js=new Lt,qs=new Lt;class Xs extends Ce{constructor(){super(),this._currentLevel=0,this.type="LOD",Object.defineProperties(this,{levels:{enumerable:!0,value:[]},isLOD:{value:!0}}),this.autoUpdate=!0}copy(t){super.copy(t,!1);const e=t.levels;for(let t=0,n=e.length;t0){let n,i;for(n=1,i=e.length;n0){js.setFromMatrixPosition(this.matrixWorld);const n=t.ray.origin.distanceTo(js);this.getObjectForDistance(n).raycast(t,e)}}update(t){const e=this.levels;if(e.length>1){js.setFromMatrixPosition(t.matrixWorld),qs.setFromMatrixPosition(this.matrixWorld);const n=js.distanceTo(qs)/t.zoom;let i,r;for(e[0].object.visible=!0,i=1,r=e.length;i=e[i].distance;i++)e[i-1].object.visible=!1,e[i].object.visible=!0;for(this._currentLevel=i-1;io)continue;u.applyMatrix4(this.matrixWorld);const d=t.ray.origin.distanceTo(u);dt.far||e.push({distance:d,point:h.clone().applyMatrix4(this.matrixWorld),index:n,face:null,faceIndex:null,object:this})}}else{for(let n=Math.max(0,s.start),i=Math.min(r.count,s.start+s.count)-1;no)continue;u.applyMatrix4(this.matrixWorld);const i=t.ray.origin.distanceTo(u);it.far||e.push({distance:i,point:h.clone().applyMatrix4(this.matrixWorld),index:n,face:null,faceIndex:null,object:this})}}}else n.isGeometry&&console.error("THREE.Line.raycast() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.")}updateMorphTargets(){const t=this.geometry;if(t.isBufferGeometry){const e=t.morphAttributes,n=Object.keys(e);if(n.length>0){const t=e[n[0]];if(void 0!==t){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let e=0,n=t.length;e0&&console.error("THREE.Line.updateMorphTargets() does not support THREE.Geometry. Use THREE.BufferGeometry instead.")}}}fa.prototype.isLine=!0;const ga=new Lt,va=new Lt;class ya extends fa{constructor(t,e){super(t,e),this.type="LineSegments"}computeLineDistances(){const t=this.geometry;if(t.isBufferGeometry)if(null===t.index){const e=t.attributes.position,n=[];for(let t=0,i=e.count;t0){const t=e[n[0]];if(void 0!==t){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let e=0,n=t.length;e0&&console.error("THREE.Points.updateMorphTargets() does not support THREE.Geometry. Use THREE.BufferGeometry instead.")}}}function Ea(t,e,n,i,r,s,a){const o=ba.distanceSqToPoint(t);if(or.far)return;s.push({distance:l,distanceToRay:Math.sqrt(o),point:n,index:e,face:null,object:a})}}Ta.prototype.isPoints=!0;class Aa extends bt{constructor(t,e,n,i,r,s,a,o,l){super(t,e,n,i,r,s,a,o,l),this.format=void 0!==a?a:T,this.minFilter=void 0!==s?s:g,this.magFilter=void 0!==r?r:g,this.generateMipmaps=!1;const c=this;"requestVideoFrameCallback"in t&&t.requestVideoFrameCallback((function e(){c.needsUpdate=!0,t.requestVideoFrameCallback(e)}))}clone(){return new this.constructor(this.image).copy(this)}update(){const t=this.image;!1==="requestVideoFrameCallback"in t&&t.readyState>=t.HAVE_CURRENT_DATA&&(this.needsUpdate=!0)}}Aa.prototype.isVideoTexture=!0;class La extends bt{constructor(t,e,n,i,r,s,a,o,l,c,h,u){super(null,s,a,o,l,c,i,r,h,u),this.image={width:e,height:n},this.mipmaps=t,this.flipY=!1,this.generateMipmaps=!1}}La.prototype.isCompressedTexture=!0;class Ra extends bt{constructor(t,e,n,i,r,s,a,o,l){super(t,e,n,i,r,s,a,o,l),this.needsUpdate=!0}}Ra.prototype.isCanvasTexture=!0;class Ca extends bt{constructor(t,e,n,i,r,s,a,o,l,c){if((c=void 0!==c?c:A)!==A&&c!==L)throw new Error("DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat");void 0===n&&c===A&&(n=_),void 0===n&&c===L&&(n=S),super(null,i,r,s,a,o,c,n,l),this.image={width:t,height:e},this.magFilter=void 0!==a?a:p,this.minFilter=void 0!==o?o:p,this.flipY=!1,this.generateMipmaps=!1}}Ca.prototype.isDepthTexture=!0;class Pa extends En{constructor(t=1,e=8,n=0,i=2*Math.PI){super(),this.type="CircleGeometry",this.parameters={radius:t,segments:e,thetaStart:n,thetaLength:i},e=Math.max(3,e);const r=[],s=[],a=[],o=[],l=new Lt,c=new vt;s.push(0,0,0),a.push(0,0,1),o.push(.5,.5);for(let r=0,h=3;r<=e;r++,h+=3){const u=n+r/e*i;l.x=t*Math.cos(u),l.y=t*Math.sin(u),s.push(l.x,l.y,l.z),a.push(0,0,1),c.x=(s[h]/t+1)/2,c.y=(s[h+1]/t+1)/2,o.push(c.x,c.y)}for(let t=1;t<=e;t++)r.push(t,t+1,0);this.setIndex(r),this.setAttribute("position",new mn(s,3)),this.setAttribute("normal",new mn(a,3)),this.setAttribute("uv",new mn(o,2))}}class Da extends En{constructor(t=1,e=1,n=1,i=8,r=1,s=!1,a=0,o=2*Math.PI){super(),this.type="CylinderGeometry",this.parameters={radiusTop:t,radiusBottom:e,height:n,radialSegments:i,heightSegments:r,openEnded:s,thetaStart:a,thetaLength:o};const l=this;i=Math.floor(i),r=Math.floor(r);const c=[],h=[],u=[],d=[];let p=0;const m=[],f=n/2;let g=0;function v(n){const r=p,s=new vt,m=new Lt;let v=0;const y=!0===n?t:e,x=!0===n?1:-1;for(let t=1;t<=i;t++)h.push(0,f*x,0),u.push(0,x,0),d.push(.5,.5),p++;const _=p;for(let t=0;t<=i;t++){const e=t/i*o+a,n=Math.cos(e),r=Math.sin(e);m.x=y*r,m.y=f*x,m.z=y*n,h.push(m.x,m.y,m.z),u.push(0,x,0),s.x=.5*n+.5,s.y=.5*r*x+.5,d.push(s.x,s.y),p++}for(let t=0;t0&&v(!0),e>0&&v(!1)),this.setIndex(c),this.setAttribute("position",new mn(h,3)),this.setAttribute("normal",new mn(u,3)),this.setAttribute("uv",new mn(d,2))}}class Ia extends Da{constructor(t=1,e=1,n=8,i=1,r=!1,s=0,a=2*Math.PI){super(0,t,e,n,i,r,s,a),this.type="ConeGeometry",this.parameters={radius:t,height:e,radialSegments:n,heightSegments:i,openEnded:r,thetaStart:s,thetaLength:a}}}class Na extends En{constructor(t,e,n=1,i=0){super(),this.type="PolyhedronGeometry",this.parameters={vertices:t,indices:e,radius:n,detail:i};const r=[],s=[];function a(t,e,n,i){const r=i+1,s=[];for(let i=0;i<=r;i++){s[i]=[];const a=t.clone().lerp(n,i/r),o=e.clone().lerp(n,i/r),l=r-i;for(let t=0;t<=l;t++)s[i][t]=0===t&&i===r?a:a.clone().lerp(o,t/l)}for(let t=0;t.9&&a<.1&&(e<.2&&(s[t+0]+=1),n<.2&&(s[t+2]+=1),i<.2&&(s[t+4]+=1))}}()}(),this.setAttribute("position",new mn(r,3)),this.setAttribute("normal",new mn(r.slice(),3)),this.setAttribute("uv",new mn(s,2)),0===i?this.computeVertexNormals():this.normalizeNormals()}}class Ba extends Na{constructor(t=1,e=0){const n=(1+Math.sqrt(5))/2,i=1/n;super([-1,-1,-1,-1,-1,1,-1,1,-1,-1,1,1,1,-1,-1,1,-1,1,1,1,-1,1,1,1,0,-i,-n,0,-i,n,0,i,-n,0,i,n,-i,-n,0,-i,n,0,i,-n,0,i,n,0,-n,0,-i,n,0,-i,-n,0,i,n,0,i],[3,11,7,3,7,15,3,15,13,7,19,17,7,17,6,7,6,15,17,4,8,17,8,10,17,10,6,8,0,16,8,16,2,8,2,10,0,12,1,0,1,18,0,18,16,6,10,2,6,2,13,6,13,15,2,16,18,2,18,3,2,3,13,18,1,9,18,9,11,18,11,3,4,14,12,4,12,0,4,0,8,11,9,5,11,5,19,11,19,7,19,5,14,19,14,4,19,4,17,1,12,14,1,14,5,1,5,9],t,e),this.type="DodecahedronGeometry",this.parameters={radius:t,detail:e}}}const za=new Lt,Fa=new Lt,Oa=new Lt,Ha=new je;class Ga extends En{constructor(t,e){if(super(),this.type="EdgesGeometry",this.parameters={thresholdAngle:e},e=void 0!==e?e:1,!0===t.isGeometry)return void console.error("THREE.EdgesGeometry no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.");const n=Math.pow(10,4),i=Math.cos(ot*e),r=t.getIndex(),s=t.getAttribute("position"),a=r?r.count:s.count,o=[0,0,0],l=["a","b","c"],c=new Array(3),h={},u=[];for(let t=0;t80*n){o=c=t[0],l=h=t[1];for(let e=n;ec&&(c=u),d>h&&(h=d);p=Math.max(c-o,h-l),p=0!==p?1/p:0}return Wa(s,a,n,o,l,p),a};function ka(t,e,n,i,r){let s,a;if(r===function(t,e,n,i){let r=0;for(let s=e,a=n-i;s0)for(s=e;s=e;s-=i)a=co(s,t[s],t[s+1],a);return a&&io(a,a.next)&&(ho(a),a=a.next),a}function Va(t,e){if(!t)return t;e||(e=t);let n,i=t;do{if(n=!1,i.steiner||!io(i,i.next)&&0!==no(i.prev,i,i.next))i=i.next;else{if(ho(i),i=e=i.prev,i===i.next)break;n=!0}}while(n||i!==e);return e}function Wa(t,e,n,i,r,s,a){if(!t)return;!a&&s&&function(t,e,n,i){let r=t;do{null===r.z&&(r.z=Ka(r.x,r.y,e,n,i)),r.prevZ=r.prev,r.nextZ=r.next,r=r.next}while(r!==t);r.prevZ.nextZ=null,r.prevZ=null,function(t){let e,n,i,r,s,a,o,l,c=1;do{for(n=t,t=null,s=null,a=0;n;){for(a++,i=n,o=0,e=0;e0||l>0&&i;)0!==o&&(0===l||!i||n.z<=i.z)?(r=n,n=n.nextZ,o--):(r=i,i=i.nextZ,l--),s?s.nextZ=r:t=r,r.prevZ=s,s=r;n=i}s.nextZ=null,c*=2}while(a>1)}(r)}(t,i,r,s);let o,l,c=t;for(;t.prev!==t.next;)if(o=t.prev,l=t.next,s?qa(t,i,r,s):ja(t))e.push(o.i/n),e.push(t.i/n),e.push(l.i/n),ho(t),t=l.next,c=l.next;else if((t=l)===c){a?1===a?Wa(t=Xa(Va(t),e,n),e,n,i,r,s,2):2===a&&Ya(t,e,n,i,r,s):Wa(Va(t),e,n,i,r,s,1);break}}function ja(t){const e=t.prev,n=t,i=t.next;if(no(e,n,i)>=0)return!1;let r=t.next.next;for(;r!==t.prev;){if(to(e.x,e.y,n.x,n.y,i.x,i.y,r.x,r.y)&&no(r.prev,r,r.next)>=0)return!1;r=r.next}return!0}function qa(t,e,n,i){const r=t.prev,s=t,a=t.next;if(no(r,s,a)>=0)return!1;const o=r.xs.x?r.x>a.x?r.x:a.x:s.x>a.x?s.x:a.x,h=r.y>s.y?r.y>a.y?r.y:a.y:s.y>a.y?s.y:a.y,u=Ka(o,l,e,n,i),d=Ka(c,h,e,n,i);let p=t.prevZ,m=t.nextZ;for(;p&&p.z>=u&&m&&m.z<=d;){if(p!==t.prev&&p!==t.next&&to(r.x,r.y,s.x,s.y,a.x,a.y,p.x,p.y)&&no(p.prev,p,p.next)>=0)return!1;if(p=p.prevZ,m!==t.prev&&m!==t.next&&to(r.x,r.y,s.x,s.y,a.x,a.y,m.x,m.y)&&no(m.prev,m,m.next)>=0)return!1;m=m.nextZ}for(;p&&p.z>=u;){if(p!==t.prev&&p!==t.next&&to(r.x,r.y,s.x,s.y,a.x,a.y,p.x,p.y)&&no(p.prev,p,p.next)>=0)return!1;p=p.prevZ}for(;m&&m.z<=d;){if(m!==t.prev&&m!==t.next&&to(r.x,r.y,s.x,s.y,a.x,a.y,m.x,m.y)&&no(m.prev,m,m.next)>=0)return!1;m=m.nextZ}return!0}function Xa(t,e,n){let i=t;do{const r=i.prev,s=i.next.next;!io(r,s)&&ro(r,i,i.next,s)&&oo(r,s)&&oo(s,r)&&(e.push(r.i/n),e.push(i.i/n),e.push(s.i/n),ho(i),ho(i.next),i=t=s),i=i.next}while(i!==t);return Va(i)}function Ya(t,e,n,i,r,s){let a=t;do{let t=a.next.next;for(;t!==a.prev;){if(a.i!==t.i&&eo(a,t)){let o=lo(a,t);return a=Va(a,a.next),o=Va(o,o.next),Wa(a,e,n,i,r,s),void Wa(o,e,n,i,r,s)}t=t.next}a=a.next}while(a!==t)}function Za(t,e){return t.x-e.x}function Ja(t,e){if(e=function(t,e){let n=e;const i=t.x,r=t.y;let s,a=-1/0;do{if(r<=n.y&&r>=n.next.y&&n.next.y!==n.y){const t=n.x+(r-n.y)*(n.next.x-n.x)/(n.next.y-n.y);if(t<=i&&t>a){if(a=t,t===i){if(r===n.y)return n;if(r===n.next.y)return n.next}s=n.x=n.x&&n.x>=l&&i!==n.x&&to(rs.x||n.x===s.x&&Qa(s,n)))&&(s=n,u=h)),n=n.next}while(n!==o);return s}(t,e)){const n=lo(e,t);Va(e,e.next),Va(n,n.next)}}function Qa(t,e){return no(t.prev,t,e.prev)<0&&no(e.next,t,t.next)<0}function Ka(t,e,n,i,r){return(t=1431655765&((t=858993459&((t=252645135&((t=16711935&((t=32767*(t-n)*r)|t<<8))|t<<4))|t<<2))|t<<1))|(e=1431655765&((e=858993459&((e=252645135&((e=16711935&((e=32767*(e-i)*r)|e<<8))|e<<4))|e<<2))|e<<1))<<1}function $a(t){let e=t,n=t;do{(e.x=0&&(t-a)*(i-o)-(n-a)*(e-o)>=0&&(n-a)*(s-o)-(r-a)*(i-o)>=0}function eo(t,e){return t.next.i!==e.i&&t.prev.i!==e.i&&!function(t,e){let n=t;do{if(n.i!==t.i&&n.next.i!==t.i&&n.i!==e.i&&n.next.i!==e.i&&ro(n,n.next,t,e))return!0;n=n.next}while(n!==t);return!1}(t,e)&&(oo(t,e)&&oo(e,t)&&function(t,e){let n=t,i=!1;const r=(t.x+e.x)/2,s=(t.y+e.y)/2;do{n.y>s!=n.next.y>s&&n.next.y!==n.y&&r<(n.next.x-n.x)*(s-n.y)/(n.next.y-n.y)+n.x&&(i=!i),n=n.next}while(n!==t);return i}(t,e)&&(no(t.prev,t,e.prev)||no(t,e.prev,e))||io(t,e)&&no(t.prev,t,t.next)>0&&no(e.prev,e,e.next)>0)}function no(t,e,n){return(e.y-t.y)*(n.x-e.x)-(e.x-t.x)*(n.y-e.y)}function io(t,e){return t.x===e.x&&t.y===e.y}function ro(t,e,n,i){const r=ao(no(t,e,n)),s=ao(no(t,e,i)),a=ao(no(n,i,t)),o=ao(no(n,i,e));return r!==s&&a!==o||(!(0!==r||!so(t,n,e))||(!(0!==s||!so(t,i,e))||(!(0!==a||!so(n,t,i))||!(0!==o||!so(n,e,i)))))}function so(t,e,n){return e.x<=Math.max(t.x,n.x)&&e.x>=Math.min(t.x,n.x)&&e.y<=Math.max(t.y,n.y)&&e.y>=Math.min(t.y,n.y)}function ao(t){return t>0?1:t<0?-1:0}function oo(t,e){return no(t.prev,t,t.next)<0?no(t,e,t.next)>=0&&no(t,t.prev,e)>=0:no(t,e,t.prev)<0||no(t,t.next,e)<0}function lo(t,e){const n=new uo(t.i,t.x,t.y),i=new uo(e.i,e.x,e.y),r=t.next,s=e.prev;return t.next=e,e.prev=t,n.next=r,r.prev=n,i.next=n,n.prev=i,s.next=i,i.prev=s,i}function co(t,e,n,i){const r=new uo(t,e,n);return i?(r.next=i.next,r.prev=i,i.next.prev=r,i.next=r):(r.prev=r,r.next=r),r}function ho(t){t.next.prev=t.prev,t.prev.next=t.next,t.prevZ&&(t.prevZ.nextZ=t.nextZ),t.nextZ&&(t.nextZ.prevZ=t.prevZ)}function uo(t,e,n){this.i=t,this.x=e,this.y=n,this.prev=null,this.next=null,this.z=null,this.prevZ=null,this.nextZ=null,this.steiner=!1}class po{static area(t){const e=t.length;let n=0;for(let i=e-1,r=0;r2&&t[e-1].equals(t[0])&&t.pop()}function fo(t,e){for(let n=0;nNumber.EPSILON){const u=Math.sqrt(h),d=Math.sqrt(l*l+c*c),p=e.x-o/u,m=e.y+a/u,f=((n.x-c/d-p)*c-(n.y+l/d-m)*l)/(a*c-o*l);i=p+a*f-t.x,r=m+o*f-t.y;const g=i*i+r*r;if(g<=2)return new vt(i,r);s=Math.sqrt(g/2)}else{let t=!1;a>Number.EPSILON?l>Number.EPSILON&&(t=!0):a<-Number.EPSILON?l<-Number.EPSILON&&(t=!0):Math.sign(o)===Math.sign(c)&&(t=!0),t?(i=-o,r=a,s=Math.sqrt(h)):(i=a,r=o,s=Math.sqrt(h/2))}return new vt(i/s,r/s)}const P=[];for(let t=0,e=E.length,n=e-1,i=t+1;t=0;t--){const e=t/p,n=h*Math.cos(e*Math.PI/2),i=u*Math.sin(e*Math.PI/2)+d;for(let t=0,e=E.length;t=0;){const i=n;let r=n-1;r<0&&(r=t.length-1);for(let t=0,n=o+2*p;t=0?(t(i-o,p,h),u.subVectors(c,h)):(t(i+o,p,h),u.subVectors(h,c)),p-o>=0?(t(i,p-o,h),d.subVectors(c,h)):(t(i,p+o,h),d.subVectors(h,c)),l.crossVectors(u,d).normalize(),s.push(l.x,l.y,l.z),a.push(i,p)}}for(let t=0;t0)&&d.push(e,r,l),(t!==n-1||o=i)){l.push(e.times[t]);for(let n=0;ns.tracks[t].times[0]&&(o=s.tracks[t].times[0]);for(let t=0;t=i.times[u]){const t=u*l+o,e=t+l-o;d=Vo.arraySlice(i.values,t,e)}else{const t=i.createInterpolant(),e=o,n=l-o;t.evaluate(s),d=Vo.arraySlice(t.resultBuffer,e,n)}if("quaternion"===r){(new At).fromArray(d).normalize().conjugate().toArray(d)}const p=a.times.length;for(let t=0;t=r)break t;{const a=e[1];t=r)break e}s=n,n=0}}for(;n>>1;te;)--s;if(++s,0!==r||s!==i){r>=s&&(s=Math.max(s,1),r=s-1);const t=this.getValueSize();this.times=Vo.arraySlice(n,r,s),this.values=Vo.arraySlice(this.values,r*t,s*t)}return this}validate(){let t=!0;const e=this.getValueSize();e-Math.floor(e)!=0&&(console.error("THREE.KeyframeTrack: Invalid value size in track.",this),t=!1);const n=this.times,i=this.values,r=n.length;0===r&&(console.error("THREE.KeyframeTrack: Track is empty.",this),t=!1);let s=null;for(let e=0;e!==r;e++){const i=n[e];if("number"==typeof i&&isNaN(i)){console.error("THREE.KeyframeTrack: Time is not a valid number.",this,e,i),t=!1;break}if(null!==s&&s>i){console.error("THREE.KeyframeTrack: Out of order keys.",this,e,i,s),t=!1;break}s=i}if(void 0!==i&&Vo.isTypedArray(i))for(let e=0,n=i.length;e!==n;++e){const n=i[e];if(isNaN(n)){console.error("THREE.KeyframeTrack: Value is not a valid number.",this,e,n),t=!1;break}}return t}optimize(){const t=Vo.arraySlice(this.times),e=Vo.arraySlice(this.values),n=this.getValueSize(),i=this.getInterpolation()===U,r=t.length-1;let s=1;for(let a=1;a0){t[s]=t[r];for(let t=r*n,i=s*n,a=0;a!==n;++a)e[i+a]=e[t+a];++s}return s!==t.length?(this.times=Vo.arraySlice(t,0,s),this.values=Vo.arraySlice(e,0,s*n)):(this.times=t,this.values=e),this}clone(){const t=Vo.arraySlice(this.times,0),e=Vo.arraySlice(this.values,0),n=new(0,this.constructor)(this.name,t,e);return n.createInterpolant=this.createInterpolant,n}}Yo.prototype.TimeBufferType=Float32Array,Yo.prototype.ValueBufferType=Float32Array,Yo.prototype.DefaultInterpolation=G;class Zo extends Yo{}Zo.prototype.ValueTypeName="bool",Zo.prototype.ValueBufferType=Array,Zo.prototype.DefaultInterpolation=H,Zo.prototype.InterpolantFactoryMethodLinear=void 0,Zo.prototype.InterpolantFactoryMethodSmooth=void 0;class Jo extends Yo{}Jo.prototype.ValueTypeName="color";class Qo extends Yo{}Qo.prototype.ValueTypeName="number";class Ko extends Wo{constructor(t,e,n,i){super(t,e,n,i)}interpolate_(t,e,n,i){const r=this.resultBuffer,s=this.sampleValues,a=this.valueSize,o=(n-e)/(i-e);let l=t*a;for(let t=l+a;l!==t;l+=4)At.slerpFlat(r,0,s,l-a,s,l,o);return r}}class $o extends Yo{InterpolantFactoryMethodLinear(t){return new Ko(this.times,this.values,this.getValueSize(),t)}}$o.prototype.ValueTypeName="quaternion",$o.prototype.DefaultInterpolation=G,$o.prototype.InterpolantFactoryMethodSmooth=void 0;class tl extends Yo{}tl.prototype.ValueTypeName="string",tl.prototype.ValueBufferType=Array,tl.prototype.DefaultInterpolation=H,tl.prototype.InterpolantFactoryMethodLinear=void 0,tl.prototype.InterpolantFactoryMethodSmooth=void 0;class el extends Yo{}el.prototype.ValueTypeName="vector";class nl{constructor(t,e=-1,n,i=2500){this.name=t,this.tracks=n,this.duration=e,this.blendMode=i,this.uuid=ct(),this.duration<0&&this.resetDuration()}static parse(t){const e=[],n=t.tracks,i=1/(t.fps||1);for(let t=0,r=n.length;t!==r;++t)e.push(il(n[t]).scale(i));const r=new this(t.name,t.duration,e,t.blendMode);return r.uuid=t.uuid,r}static toJSON(t){const e=[],n=t.tracks,i={name:t.name,duration:t.duration,tracks:e,uuid:t.uuid,blendMode:t.blendMode};for(let t=0,i=n.length;t!==i;++t)e.push(Yo.toJSON(n[t]));return i}static CreateFromMorphTargetSequence(t,e,n,i){const r=e.length,s=[];for(let t=0;t1){const t=s[1];let e=i[t];e||(i[t]=e=[]),e.push(n)}}const s=[];for(const t in i)s.push(this.CreateFromMorphTargetSequence(t,i[t],e,n));return s}static parseAnimation(t,e){if(!t)return console.error("THREE.AnimationClip: No animation in JSONLoader data."),null;const n=function(t,e,n,i,r){if(0!==n.length){const s=[],a=[];Vo.flattenJSON(n,s,a,i),0!==s.length&&r.push(new t(e,s,a))}},i=[],r=t.name||"default",s=t.fps||30,a=t.blendMode;let o=t.length||-1;const l=t.hierarchy||[];for(let t=0;t0||0===t.search(/^data\:image\/jpeg/);r.format=i?T:E,r.needsUpdate=!0,void 0!==e&&e(r)}),n,i),r}}class ml{constructor(){this.type="Curve",this.arcLengthDivisions=200}getPoint(){return console.warn("THREE.Curve: .getPoint() not implemented."),null}getPointAt(t,e){const n=this.getUtoTmapping(t);return this.getPoint(n,e)}getPoints(t=5){const e=[];for(let n=0;n<=t;n++)e.push(this.getPoint(n/t));return e}getSpacedPoints(t=5){const e=[];for(let n=0;n<=t;n++)e.push(this.getPointAt(n/t));return e}getLength(){const t=this.getLengths();return t[t.length-1]}getLengths(t=this.arcLengthDivisions){if(this.cacheArcLengths&&this.cacheArcLengths.length===t+1&&!this.needsUpdate)return this.cacheArcLengths;this.needsUpdate=!1;const e=[];let n,i=this.getPoint(0),r=0;e.push(0);for(let s=1;s<=t;s++)n=this.getPoint(s/t),r+=n.distanceTo(i),e.push(r),i=n;return this.cacheArcLengths=e,e}updateArcLengths(){this.needsUpdate=!0,this.getLengths()}getUtoTmapping(t,e){const n=this.getLengths();let i=0;const r=n.length;let s;s=e||t*n[r-1];let a,o=0,l=r-1;for(;o<=l;)if(i=Math.floor(o+(l-o)/2),a=n[i]-s,a<0)o=i+1;else{if(!(a>0)){l=i;break}l=i-1}if(i=l,n[i]===s)return i/(r-1);const c=n[i];return(i+(s-c)/(n[i+1]-c))/(r-1)}getTangent(t,e){const n=1e-4;let i=t-n,r=t+n;i<0&&(i=0),r>1&&(r=1);const s=this.getPoint(i),a=this.getPoint(r),o=e||(s.isVector2?new vt:new Lt);return o.copy(a).sub(s).normalize(),o}getTangentAt(t,e){const n=this.getUtoTmapping(t);return this.getTangent(n,e)}computeFrenetFrames(t,e){const n=new Lt,i=[],r=[],s=[],a=new Lt,o=new se;for(let e=0;e<=t;e++){const n=e/t;i[e]=this.getTangentAt(n,new Lt),i[e].normalize()}r[0]=new Lt,s[0]=new Lt;let l=Number.MAX_VALUE;const c=Math.abs(i[0].x),h=Math.abs(i[0].y),u=Math.abs(i[0].z);c<=l&&(l=c,n.set(1,0,0)),h<=l&&(l=h,n.set(0,1,0)),u<=l&&n.set(0,0,1),a.crossVectors(i[0],n).normalize(),r[0].crossVectors(i[0],a),s[0].crossVectors(i[0],r[0]);for(let e=1;e<=t;e++){if(r[e]=r[e-1].clone(),s[e]=s[e-1].clone(),a.crossVectors(i[e-1],i[e]),a.length()>Number.EPSILON){a.normalize();const t=Math.acos(ht(i[e-1].dot(i[e]),-1,1));r[e].applyMatrix4(o.makeRotationAxis(a,t))}s[e].crossVectors(i[e],r[e])}if(!0===e){let e=Math.acos(ht(r[0].dot(r[t]),-1,1));e/=t,i[0].dot(a.crossVectors(r[0],r[t]))>0&&(e=-e);for(let n=1;n<=t;n++)r[n].applyMatrix4(o.makeRotationAxis(i[n],e*n)),s[n].crossVectors(i[n],r[n])}return{tangents:i,normals:r,binormals:s}}clone(){return(new this.constructor).copy(this)}copy(t){return this.arcLengthDivisions=t.arcLengthDivisions,this}toJSON(){const t={metadata:{version:4.5,type:"Curve",generator:"Curve.toJSON"}};return t.arcLengthDivisions=this.arcLengthDivisions,t.type=this.type,t}fromJSON(t){return this.arcLengthDivisions=t.arcLengthDivisions,this}}class fl extends ml{constructor(t=0,e=0,n=1,i=1,r=0,s=2*Math.PI,a=!1,o=0){super(),this.type="EllipseCurve",this.aX=t,this.aY=e,this.xRadius=n,this.yRadius=i,this.aStartAngle=r,this.aEndAngle=s,this.aClockwise=a,this.aRotation=o}getPoint(t,e){const n=e||new vt,i=2*Math.PI;let r=this.aEndAngle-this.aStartAngle;const s=Math.abs(r)i;)r-=i;r0?0:(Math.floor(Math.abs(l)/r)+1)*r:0===c&&l===r-1&&(l=r-2,c=1),this.closed||l>0?a=i[(l-1)%r]:(yl.subVectors(i[0],i[1]).add(i[0]),a=yl);const h=i[l%r],u=i[(l+1)%r];if(this.closed||l+2i.length-2?i.length-1:s+1],h=i[s>i.length-3?i.length-1:s+2];return n.set(Ml(a,o.x,l.x,c.x,h.x),Ml(a,o.y,l.y,c.y,h.y)),n}copy(t){super.copy(t),this.points=[];for(let e=0,n=t.points.length;e=e){const t=n[i]-e,r=this.curves[i],s=r.getLength(),a=0===s?0:1-t/s;return r.getPointAt(a)}i++}return null}getLength(){const t=this.getCurveLengths();return t[t.length-1]}updateArcLengths(){this.needsUpdate=!0,this.cacheLengths=null,this.getCurveLengths()}getCurveLengths(){if(this.cacheLengths&&this.cacheLengths.length===this.curves.length)return this.cacheLengths;const t=[];let e=0;for(let n=0,i=this.curves.length;n1&&!e[e.length-1].equals(e[0])&&e.push(e[0]),e}copy(t){super.copy(t),this.curves=[];for(let e=0,n=t.curves.length;e0){const t=l.getPoint(0);t.equals(this.currentPoint)||this.lineTo(t.x,t.y)}this.curves.push(l);const c=l.getPoint(1);return this.currentPoint.copy(c),this}copy(t){return super.copy(t),this.currentPoint.copy(t.currentPoint),this}toJSON(){const t=super.toJSON();return t.currentPoint=this.currentPoint.toArray(),t}fromJSON(t){return super.fromJSON(t),this.currentPoint.fromArray(t.currentPoint),this}}class zl extends Bl{constructor(t){super(t),this.uuid=ct(),this.type="Shape",this.holes=[]}getPointsHoles(t){const e=[];for(let n=0,i=this.holes.length;n0:i.vertexColors=t.vertexColors),void 0!==t.uniforms)for(const e in t.uniforms){const r=t.uniforms[e];switch(i.uniforms[e]={},r.type){case"t":i.uniforms[e].value=n(r.value);break;case"c":i.uniforms[e].value=(new tn).setHex(r.value);break;case"v2":i.uniforms[e].value=(new vt).fromArray(r.value);break;case"v3":i.uniforms[e].value=(new Lt).fromArray(r.value);break;case"v4":i.uniforms[e].value=(new St).fromArray(r.value);break;case"m3":i.uniforms[e].value=(new yt).fromArray(r.value);break;case"m4":i.uniforms[e].value=(new se).fromArray(r.value);break;default:i.uniforms[e].value=r.value}}if(void 0!==t.defines&&(i.defines=t.defines),void 0!==t.vertexShader&&(i.vertexShader=t.vertexShader),void 0!==t.fragmentShader&&(i.fragmentShader=t.fragmentShader),void 0!==t.extensions)for(const e in t.extensions)i.extensions[e]=t.extensions[e];if(void 0!==t.shading&&(i.flatShading=1===t.shading),void 0!==t.size&&(i.size=t.size),void 0!==t.sizeAttenuation&&(i.sizeAttenuation=t.sizeAttenuation),void 0!==t.map&&(i.map=n(t.map)),void 0!==t.matcap&&(i.matcap=n(t.matcap)),void 0!==t.alphaMap&&(i.alphaMap=n(t.alphaMap)),void 0!==t.bumpMap&&(i.bumpMap=n(t.bumpMap)),void 0!==t.bumpScale&&(i.bumpScale=t.bumpScale),void 0!==t.normalMap&&(i.normalMap=n(t.normalMap)),void 0!==t.normalMapType&&(i.normalMapType=t.normalMapType),void 0!==t.normalScale){let e=t.normalScale;!1===Array.isArray(e)&&(e=[e,e]),i.normalScale=(new vt).fromArray(e)}return void 0!==t.displacementMap&&(i.displacementMap=n(t.displacementMap)),void 0!==t.displacementScale&&(i.displacementScale=t.displacementScale),void 0!==t.displacementBias&&(i.displacementBias=t.displacementBias),void 0!==t.roughnessMap&&(i.roughnessMap=n(t.roughnessMap)),void 0!==t.metalnessMap&&(i.metalnessMap=n(t.metalnessMap)),void 0!==t.emissiveMap&&(i.emissiveMap=n(t.emissiveMap)),void 0!==t.emissiveIntensity&&(i.emissiveIntensity=t.emissiveIntensity),void 0!==t.specularMap&&(i.specularMap=n(t.specularMap)),void 0!==t.envMap&&(i.envMap=n(t.envMap)),void 0!==t.envMapIntensity&&(i.envMapIntensity=t.envMapIntensity),void 0!==t.reflectivity&&(i.reflectivity=t.reflectivity),void 0!==t.refractionRatio&&(i.refractionRatio=t.refractionRatio),void 0!==t.lightMap&&(i.lightMap=n(t.lightMap)),void 0!==t.lightMapIntensity&&(i.lightMapIntensity=t.lightMapIntensity),void 0!==t.aoMap&&(i.aoMap=n(t.aoMap)),void 0!==t.aoMapIntensity&&(i.aoMapIntensity=t.aoMapIntensity),void 0!==t.gradientMap&&(i.gradientMap=n(t.gradientMap)),void 0!==t.clearcoatMap&&(i.clearcoatMap=n(t.clearcoatMap)),void 0!==t.clearcoatRoughnessMap&&(i.clearcoatRoughnessMap=n(t.clearcoatRoughnessMap)),void 0!==t.clearcoatNormalMap&&(i.clearcoatNormalMap=n(t.clearcoatNormalMap)),void 0!==t.clearcoatNormalScale&&(i.clearcoatNormalScale=(new vt).fromArray(t.clearcoatNormalScale)),void 0!==t.transmission&&(i.transmission=t.transmission),void 0!==t.transmissionMap&&(i.transmissionMap=n(t.transmissionMap)),i}setTextures(t){return this.textures=t,this}}class rc{static decodeText(t){if("undefined"!=typeof TextDecoder)return(new TextDecoder).decode(t);let e="";for(let n=0,i=t.length;nNumber.EPSILON){if(l<0&&(n=e[s],o=-o,a=e[r],l=-l),t.ya.y)continue;if(t.y===n.y){if(t.x===n.x)return!0}else{const e=l*(t.x-n.x)-o*(t.y-n.y);if(0===e)return!0;if(e<0)continue;i=!i}}else{if(t.y!==n.y)continue;if(a.x<=t.x&&t.x<=n.x||n.x<=t.x&&t.x<=a.x)return!0}}return i}const r=po.isClockWise,s=this.subPaths;if(0===s.length)return[];if(!0===e)return n(s);let a,o,l;const c=[];if(1===s.length)return o=s[0],l=new zl,l.curves=o.curves,c.push(l),c;let h=!r(s[0].getPoints());h=t?!h:h;const u=[],d=[];let p,m,f=[],g=0;d[g]=void 0,f[g]=[];for(let e=0,n=s.length;e1){let t=!1;const e=[];for(let t=0,e=d.length;t0&&(t||(f=u))}for(let t=0,e=d.length;t0){this.source.connect(this.filters[0]);for(let t=1,e=this.filters.length;t0){this.source.disconnect(this.filters[0]);for(let t=1,e=this.filters.length;t0&&this._mixBufferRegionAdditive(n,i,this._addIndex*e,1,e);for(let t=e,r=e+e;t!==r;++t)if(n[t]!==n[t+e]){a.setValue(n,i);break}}saveOriginalState(){const t=this.binding,e=this.buffer,n=this.valueSize,i=n*this._origIndex;t.getValue(e,i);for(let t=n,r=i;t!==r;++t)e[t]=e[i+t%n];this._setIdentity(),this.cumulativeWeight=0,this.cumulativeWeightAdditive=0}restoreOriginalState(){const t=3*this.valueSize;this.binding.setValue(this.buffer,t)}_setAdditiveIdentityNumeric(){const t=this._addIndex*this.valueSize,e=t+this.valueSize;for(let n=t;n=.5)for(let i=0;i!==r;++i)t[e+i]=t[n+i]}_slerp(t,e,n,i){At.slerpFlat(t,e,t,e,t,n,i)}_slerpAdditive(t,e,n,i,r){const s=this._workIndex*r;At.multiplyQuaternionsFlat(t,s,t,e,t,n),At.slerpFlat(t,e,t,e,t,s,i)}_lerp(t,e,n,i,r){const s=1-i;for(let a=0;a!==r;++a){const r=e+a;t[r]=t[r]*s+t[n+a]*i}}_lerpAdditive(t,e,n,i,r){for(let s=0;s!==r;++s){const r=e+s;t[r]=t[r]+t[n+s]*i}}}const Bc="\\[\\]\\.:\\/",zc=new RegExp("[\\[\\]\\.:\\/]","g"),Fc="[^\\[\\]\\.:\\/]",Oc="[^"+Bc.replace("\\.","")+"]",Hc=/((?:WC+[\/:])*)/.source.replace("WC",Fc),Gc=/(WCOD+)?/.source.replace("WCOD",Oc),Uc=/(?:\.(WC+)(?:\[(.+)\])?)?/.source.replace("WC",Fc),kc=/\.(WC+)(?:\[(.+)\])?/.source.replace("WC",Fc),Vc=new RegExp("^"+Hc+Gc+Uc+kc+"$"),Wc=["material","materials","bones"];class jc{constructor(t,e,n){this.path=e,this.parsedPath=n||jc.parseTrackName(e),this.node=jc.findNode(t,this.parsedPath.nodeName)||t,this.rootNode=t,this.getValue=this._getValue_unbound,this.setValue=this._setValue_unbound}static create(t,e,n){return t&&t.isAnimationObjectGroup?new jc.Composite(t,e,n):new jc(t,e,n)}static sanitizeNodeName(t){return t.replace(/\s/g,"_").replace(zc,"")}static parseTrackName(t){const e=Vc.exec(t);if(!e)throw new Error("PropertyBinding: Cannot parse trackName: "+t);const n={nodeName:e[2],objectName:e[3],objectIndex:e[4],propertyName:e[5],propertyIndex:e[6]},i=n.nodeName&&n.nodeName.lastIndexOf(".");if(void 0!==i&&-1!==i){const t=n.nodeName.substring(i+1);-1!==Wc.indexOf(t)&&(n.nodeName=n.nodeName.substring(0,i),n.objectName=t)}if(null===n.propertyName||0===n.propertyName.length)throw new Error("PropertyBinding: can not parse propertyName from trackName: "+t);return n}static findNode(t,e){if(!e||""===e||"."===e||-1===e||e===t.name||e===t.uuid)return t;if(t.skeleton){const n=t.skeleton.getBoneByName(e);if(void 0!==n)return n}if(t.children){const n=function(t){for(let i=0;i=r){const s=r++,c=t[s];e[c.uuid]=l,t[l]=c,e[o]=s,t[s]=a;for(let t=0,e=i;t!==e;++t){const e=n[t],i=e[s],r=e[l];e[l]=i,e[s]=r}}}this.nCachedObjects_=r}uncache(){const t=this._objects,e=this._indicesByUUID,n=this._bindings,i=n.length;let r=this.nCachedObjects_,s=t.length;for(let a=0,o=arguments.length;a!==o;++a){const o=arguments[a].uuid,l=e[o];if(void 0!==l)if(delete e[o],l0&&(e[a.uuid]=l),t[l]=a,t.pop();for(let t=0,e=i;t!==e;++t){const e=n[t];e[l]=e[r],e.pop()}}}this.nCachedObjects_=r}subscribe_(t,e){const n=this._bindingsIndicesByPath;let i=n[t];const r=this._bindings;if(void 0!==i)return r[i];const s=this._paths,a=this._parsedPaths,o=this._objects,l=o.length,c=this.nCachedObjects_,h=new Array(l);i=r.length,n[t]=i,s.push(t),a.push(e),r.push(h);for(let n=c,i=o.length;n!==i;++n){const i=o[n];h[n]=new jc(i,t,e)}return h}unsubscribe_(t){const e=this._bindingsIndicesByPath,n=e[t];if(void 0!==n){const i=this._paths,r=this._parsedPaths,s=this._bindings,a=s.length-1,o=s[a];e[t[a]]=n,s[n]=o,s.pop(),r[n]=r[a],r.pop(),i[n]=i[a],i.pop()}}}qc.prototype.isAnimationObjectGroup=!0;class Xc{constructor(t,e,n=null,i=e.blendMode){this._mixer=t,this._clip=e,this._localRoot=n,this.blendMode=i;const r=e.tracks,s=r.length,a=new Array(s),o={endingStart:k,endingEnd:k};for(let t=0;t!==s;++t){const e=r[t].createInterpolant(null);a[t]=e,e.settings=o}this._interpolantSettings=o,this._interpolants=a,this._propertyBindings=new Array(s),this._cacheIndex=null,this._byClipCacheIndex=null,this._timeScaleInterpolant=null,this._weightInterpolant=null,this.loop=2201,this._loopCount=-1,this._startTime=null,this.time=0,this.timeScale=1,this._effectiveTimeScale=1,this.weight=1,this._effectiveWeight=1,this.repetitions=1/0,this.paused=!1,this.enabled=!0,this.clampWhenFinished=!1,this.zeroSlopeAtStart=!0,this.zeroSlopeAtEnd=!0}play(){return this._mixer._activateAction(this),this}stop(){return this._mixer._deactivateAction(this),this.reset()}reset(){return this.paused=!1,this.enabled=!0,this.time=0,this._loopCount=-1,this._startTime=null,this.stopFading().stopWarping()}isRunning(){return this.enabled&&!this.paused&&0!==this.timeScale&&null===this._startTime&&this._mixer._isActiveAction(this)}isScheduled(){return this._mixer._isActiveAction(this)}startAt(t){return this._startTime=t,this}setLoop(t,e){return this.loop=t,this.repetitions=e,this}setEffectiveWeight(t){return this.weight=t,this._effectiveWeight=this.enabled?t:0,this.stopFading()}getEffectiveWeight(){return this._effectiveWeight}fadeIn(t){return this._scheduleFading(t,0,1)}fadeOut(t){return this._scheduleFading(t,1,0)}crossFadeFrom(t,e,n){if(t.fadeOut(e),this.fadeIn(e),n){const n=this._clip.duration,i=t._clip.duration,r=i/n,s=n/i;t.warp(1,r,e),this.warp(s,1,e)}return this}crossFadeTo(t,e,n){return t.crossFadeFrom(this,e,n)}stopFading(){const t=this._weightInterpolant;return null!==t&&(this._weightInterpolant=null,this._mixer._takeBackControlInterpolant(t)),this}setEffectiveTimeScale(t){return this.timeScale=t,this._effectiveTimeScale=this.paused?0:t,this.stopWarping()}getEffectiveTimeScale(){return this._effectiveTimeScale}setDuration(t){return this.timeScale=this._clip.duration/t,this.stopWarping()}syncWith(t){return this.time=t.time,this.timeScale=t.timeScale,this.stopWarping()}halt(t){return this.warp(this._effectiveTimeScale,0,t)}warp(t,e,n){const i=this._mixer,r=i.time,s=this.timeScale;let a=this._timeScaleInterpolant;null===a&&(a=i._lendControlInterpolant(),this._timeScaleInterpolant=a);const o=a.parameterPositions,l=a.sampleValues;return o[0]=r,o[1]=r+n,l[0]=t/s,l[1]=e/s,this}stopWarping(){const t=this._timeScaleInterpolant;return null!==t&&(this._timeScaleInterpolant=null,this._mixer._takeBackControlInterpolant(t)),this}getMixer(){return this._mixer}getClip(){return this._clip}getRoot(){return this._localRoot||this._mixer._root}_update(t,e,n,i){if(!this.enabled)return void this._updateWeight(t);const r=this._startTime;if(null!==r){const i=(t-r)*n;if(i<0||0===n)return;this._startTime=null,e=n*i}e*=this._updateTimeScale(t);const s=this._updateTime(e),a=this._updateWeight(t);if(a>0){const t=this._interpolants,e=this._propertyBindings;switch(this.blendMode){case q:for(let n=0,i=t.length;n!==i;++n)t[n].evaluate(s),e[n].accumulateAdditive(a);break;case j:default:for(let n=0,r=t.length;n!==r;++n)t[n].evaluate(s),e[n].accumulate(i,a)}}}_updateWeight(t){let e=0;if(this.enabled){e=this.weight;const n=this._weightInterpolant;if(null!==n){const i=n.evaluate(t)[0];e*=i,t>n.parameterPositions[1]&&(this.stopFading(),0===i&&(this.enabled=!1))}}return this._effectiveWeight=e,e}_updateTimeScale(t){let e=0;if(!this.paused){e=this.timeScale;const n=this._timeScaleInterpolant;if(null!==n){e*=n.evaluate(t)[0],t>n.parameterPositions[1]&&(this.stopWarping(),0===e?this.paused=!0:this.timeScale=e)}}return this._effectiveTimeScale=e,e}_updateTime(t){const e=this._clip.duration,n=this.loop;let i=this.time+t,r=this._loopCount;const s=2202===n;if(0===t)return-1===r?i:s&&1==(1&r)?e-i:i;if(2200===n){-1===r&&(this._loopCount=0,this._setEndings(!0,!0,!1));t:{if(i>=e)i=e;else{if(!(i<0)){this.time=i;break t}i=0}this.clampWhenFinished?this.paused=!0:this.enabled=!1,this.time=i,this._mixer.dispatchEvent({type:"finished",action:this,direction:t<0?-1:1})}}else{if(-1===r&&(t>=0?(r=0,this._setEndings(!0,0===this.repetitions,s)):this._setEndings(0===this.repetitions,!0,s)),i>=e||i<0){const n=Math.floor(i/e);i-=e*n,r+=Math.abs(n);const a=this.repetitions-r;if(a<=0)this.clampWhenFinished?this.paused=!0:this.enabled=!1,i=t>0?e:0,this.time=i,this._mixer.dispatchEvent({type:"finished",action:this,direction:t>0?1:-1});else{if(1===a){const e=t<0;this._setEndings(e,!e,s)}else this._setEndings(!1,!1,s);this._loopCount=r,this.time=i,this._mixer.dispatchEvent({type:"loop",action:this,loopDelta:n})}}else this.time=i;if(s&&1==(1&r))return e-i}return i}_setEndings(t,e,n){const i=this._interpolantSettings;n?(i.endingStart=V,i.endingEnd=V):(i.endingStart=t?this.zeroSlopeAtStart?V:k:W,i.endingEnd=e?this.zeroSlopeAtEnd?V:k:W)}_scheduleFading(t,e,n){const i=this._mixer,r=i.time;let s=this._weightInterpolant;null===s&&(s=i._lendControlInterpolant(),this._weightInterpolant=s);const a=s.parameterPositions,o=s.sampleValues;return a[0]=r,o[0]=e,a[1]=r+t,o[1]=n,this}}class Yc extends rt{constructor(t){super(),this._root=t,this._initMemoryManager(),this._accuIndex=0,this.time=0,this.timeScale=1}_bindAction(t,e){const n=t._localRoot||this._root,i=t._clip.tracks,r=i.length,s=t._propertyBindings,a=t._interpolants,o=n.uuid,l=this._bindingsByRootAndName;let c=l[o];void 0===c&&(c={},l[o]=c);for(let t=0;t!==r;++t){const r=i[t],l=r.name;let h=c[l];if(void 0!==h)s[t]=h;else{if(h=s[t],void 0!==h){null===h._cacheIndex&&(++h.referenceCount,this._addInactiveBinding(h,o,l));continue}const i=e&&e._propertyBindings[t].binding.parsedPath;h=new Nc(jc.create(n,l,i),r.ValueTypeName,r.getValueSize()),++h.referenceCount,this._addInactiveBinding(h,o,l),s[t]=h}a[t].resultBuffer=h.buffer}}_activateAction(t){if(!this._isActiveAction(t)){if(null===t._cacheIndex){const e=(t._localRoot||this._root).uuid,n=t._clip.uuid,i=this._actionsByClip[n];this._bindAction(t,i&&i.knownActions[0]),this._addInactiveAction(t,n,e)}const e=t._propertyBindings;for(let t=0,n=e.length;t!==n;++t){const n=e[t];0==n.useCount++&&(this._lendBinding(n),n.saveOriginalState())}this._lendAction(t)}}_deactivateAction(t){if(this._isActiveAction(t)){const e=t._propertyBindings;for(let t=0,n=e.length;t!==n;++t){const n=e[t];0==--n.useCount&&(n.restoreOriginalState(),this._takeBackBinding(n))}this._takeBackAction(t)}}_initMemoryManager(){this._actions=[],this._nActiveActions=0,this._actionsByClip={},this._bindings=[],this._nActiveBindings=0,this._bindingsByRootAndName={},this._controlInterpolants=[],this._nActiveControlInterpolants=0;const t=this;this.stats={actions:{get total(){return t._actions.length},get inUse(){return t._nActiveActions}},bindings:{get total(){return t._bindings.length},get inUse(){return t._nActiveBindings}},controlInterpolants:{get total(){return t._controlInterpolants.length},get inUse(){return t._nActiveControlInterpolants}}}}_isActiveAction(t){const e=t._cacheIndex;return null!==e&&e=0;--e)t[e].stop();return this}update(t){t*=this.timeScale;const e=this._actions,n=this._nActiveActions,i=this.time+=t,r=Math.sign(t),s=this._accuIndex^=1;for(let a=0;a!==n;++a){e[a]._update(i,t,r,s)}const a=this._bindings,o=this._nActiveBindings;for(let t=0;t!==o;++t)a[t].apply(s);return this}setTime(t){this.time=0;for(let t=0;tthis.max.x||t.ythis.max.y)}containsBox(t){return this.min.x<=t.min.x&&t.max.x<=this.max.x&&this.min.y<=t.min.y&&t.max.y<=this.max.y}getParameter(t,e){return void 0===e&&(console.warn("THREE.Box2: .getParameter() target is now required"),e=new vt),e.set((t.x-this.min.x)/(this.max.x-this.min.x),(t.y-this.min.y)/(this.max.y-this.min.y))}intersectsBox(t){return!(t.max.xthis.max.x||t.max.ythis.max.y)}clampPoint(t,e){return void 0===e&&(console.warn("THREE.Box2: .clampPoint() target is now required"),e=new vt),e.copy(t).clamp(this.min,this.max)}distanceToPoint(t){return th.copy(t).clamp(this.min,this.max).sub(t).length()}intersect(t){return this.min.max(t.min),this.max.min(t.max),this}union(t){return this.min.min(t.min),this.max.max(t.max),this}translate(t){return this.min.add(t),this.max.add(t),this}equals(t){return t.min.equals(this.min)&&t.max.equals(this.max)}}eh.prototype.isBox2=!0;const nh=new Lt,ih=new Lt;class rh{constructor(t=new Lt,e=new Lt){this.start=t,this.end=e}set(t,e){return this.start.copy(t),this.end.copy(e),this}copy(t){return this.start.copy(t.start),this.end.copy(t.end),this}getCenter(t){return void 0===t&&(console.warn("THREE.Line3: .getCenter() target is now required"),t=new Lt),t.addVectors(this.start,this.end).multiplyScalar(.5)}delta(t){return void 0===t&&(console.warn("THREE.Line3: .delta() target is now required"),t=new Lt),t.subVectors(this.end,this.start)}distanceSq(){return this.start.distanceToSquared(this.end)}distance(){return this.start.distanceTo(this.end)}at(t,e){return void 0===e&&(console.warn("THREE.Line3: .at() target is now required"),e=new Lt),this.delta(e).multiplyScalar(t).add(this.start)}closestPointToPointParameter(t,e){nh.subVectors(t,this.start),ih.subVectors(this.end,this.start);const n=ih.dot(ih);let i=ih.dot(nh)/n;return e&&(i=ht(i,0,1)),i}closestPointToPoint(t,e,n){const i=this.closestPointToPointParameter(t,e);return void 0===n&&(console.warn("THREE.Line3: .closestPointToPoint() target is now required"),n=new Lt),this.delta(n).multiplyScalar(i).add(this.start)}applyMatrix4(t){return this.start.applyMatrix4(t),this.end.applyMatrix4(t),this}equals(t){return t.start.equals(this.start)&&t.end.equals(this.end)}clone(){return(new this.constructor).copy(this)}}class sh extends Ce{constructor(t){super(),this.material=t,this.render=function(){},this.hasPositions=!1,this.hasNormals=!1,this.hasColors=!1,this.hasUvs=!1,this.positionArray=null,this.normalArray=null,this.colorArray=null,this.uvArray=null,this.count=0}}sh.prototype.isImmediateRenderObject=!0;const ah=new Lt;const oh=new Lt,lh=new se,ch=new se;class hh extends ya{constructor(t){const e=uh(t),n=new En,i=[],r=[],s=new tn(0,0,1),a=new tn(0,1,0);for(let t=0;t4?a=Ph[r-8+4-1]:0==r&&(a=0),n.push(a);const o=1/(s-1),l=-o/2,c=1+o/2,h=[l,l,c,l,c,c,l,l,c,c,l,c],u=6,d=6,p=3,m=2,f=1,g=new Float32Array(p*d*u),v=new Float32Array(m*d*u),y=new Float32Array(f*d*u);for(let t=0;t2?0:-1,i=[e,n,0,e+2/3,n,0,e+2/3,n+1,0,e,n,0,e+2/3,n+1,0,e,n+1,0];g.set(i,p*d*t),v.set(h,m*d*t);const r=[t,t,t,t,t,t];y.set(r,f*d*t)}const x=new En;x.setAttribute("position",new sn(g,p)),x.setAttribute("uv",new sn(v,m)),x.setAttribute("faceIndex",new sn(y,f)),t.push(x),i>4&&i--}return{_lodPlanes:t,_sizeLods:e,_sigmas:n}}function Zh(t){const e=new Tt(3*Ch,3*Ch,t);return e.texture.mapping=l,e.texture.name="PMREM.cubeUv",e.scissorTest=!0,e}function Jh(t,e,n,i,r){t.viewport.set(e,n,i,r),t.scissor.set(e,n,i,r)}function Qh(){const t=new vt(1,1);return new Io({name:"EquirectangularToCubeUV",uniforms:{envMap:{value:null},texelSize:{value:t},inputEncoding:{value:Nh[3e3]},outputEncoding:{value:Nh[3e3]}},vertexShader:$h(),fragmentShader:`\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform sampler2D envMap;\n\t\t\tuniform vec2 texelSize;\n\n\t\t\t${tu()}\n\n\t\t\t#include \n\n\t\t\tvoid main() {\n\n\t\t\t\tgl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\n\t\t\t\tvec3 outputDirection = normalize( vOutputDirection );\n\t\t\t\tvec2 uv = equirectUv( outputDirection );\n\n\t\t\t\tvec2 f = fract( uv / texelSize - 0.5 );\n\t\t\t\tuv -= f * texelSize;\n\t\t\t\tvec3 tl = envMapTexelToLinear( texture2D ( envMap, uv ) ).rgb;\n\t\t\t\tuv.x += texelSize.x;\n\t\t\t\tvec3 tr = envMapTexelToLinear( texture2D ( envMap, uv ) ).rgb;\n\t\t\t\tuv.y += texelSize.y;\n\t\t\t\tvec3 br = envMapTexelToLinear( texture2D ( envMap, uv ) ).rgb;\n\t\t\t\tuv.x -= texelSize.x;\n\t\t\t\tvec3 bl = envMapTexelToLinear( texture2D ( envMap, uv ) ).rgb;\n\n\t\t\t\tvec3 tm = mix( tl, tr, f.x );\n\t\t\t\tvec3 bm = mix( bl, br, f.x );\n\t\t\t\tgl_FragColor.rgb = mix( tm, bm, f.y );\n\n\t\t\t\tgl_FragColor = linearToOutputTexel( gl_FragColor );\n\n\t\t\t}\n\t\t`,blending:0,depthTest:!1,depthWrite:!1})}function Kh(){return new Io({name:"CubemapToCubeUV",uniforms:{envMap:{value:null},inputEncoding:{value:Nh[3e3]},outputEncoding:{value:Nh[3e3]}},vertexShader:$h(),fragmentShader:`\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform samplerCube envMap;\n\n\t\t\t${tu()}\n\n\t\t\tvoid main() {\n\n\t\t\t\tgl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t\t\t\tgl_FragColor.rgb = envMapTexelToLinear( textureCube( envMap, vec3( - vOutputDirection.x, vOutputDirection.yz ) ) ).rgb;\n\t\t\t\tgl_FragColor = linearToOutputTexel( gl_FragColor );\n\n\t\t\t}\n\t\t`,blending:0,depthTest:!1,depthWrite:!1})}function $h(){return"\n\n\t\tprecision mediump float;\n\t\tprecision mediump int;\n\n\t\tattribute vec3 position;\n\t\tattribute vec2 uv;\n\t\tattribute float faceIndex;\n\n\t\tvarying vec3 vOutputDirection;\n\n\t\t// RH coordinate system; PMREM face-indexing convention\n\t\tvec3 getDirection( vec2 uv, float face ) {\n\n\t\t\tuv = 2.0 * uv - 1.0;\n\n\t\t\tvec3 direction = vec3( uv, 1.0 );\n\n\t\t\tif ( face == 0.0 ) {\n\n\t\t\t\tdirection = direction.zyx; // ( 1, v, u ) pos x\n\n\t\t\t} else if ( face == 1.0 ) {\n\n\t\t\t\tdirection = direction.xzy;\n\t\t\t\tdirection.xz *= -1.0; // ( -u, 1, -v ) pos y\n\n\t\t\t} else if ( face == 2.0 ) {\n\n\t\t\t\tdirection.x *= -1.0; // ( -u, v, 1 ) pos z\n\n\t\t\t} else if ( face == 3.0 ) {\n\n\t\t\t\tdirection = direction.zyx;\n\t\t\t\tdirection.xz *= -1.0; // ( -1, v, -u ) neg x\n\n\t\t\t} else if ( face == 4.0 ) {\n\n\t\t\t\tdirection = direction.xzy;\n\t\t\t\tdirection.xy *= -1.0; // ( -u, -1, v ) neg y\n\n\t\t\t} else if ( face == 5.0 ) {\n\n\t\t\t\tdirection.z *= -1.0; // ( u, v, -1 ) neg z\n\n\t\t\t}\n\n\t\t\treturn direction;\n\n\t\t}\n\n\t\tvoid main() {\n\n\t\t\tvOutputDirection = getDirection( uv, faceIndex );\n\t\t\tgl_Position = vec4( position, 1.0 );\n\n\t\t}\n\t"}function tu(){return"\n\n\t\tuniform int inputEncoding;\n\t\tuniform int outputEncoding;\n\n\t\t#include \n\n\t\tvec4 inputTexelToLinear( vec4 value ) {\n\n\t\t\tif ( inputEncoding == 0 ) {\n\n\t\t\t\treturn value;\n\n\t\t\t} else if ( inputEncoding == 1 ) {\n\n\t\t\t\treturn sRGBToLinear( value );\n\n\t\t\t} else if ( inputEncoding == 2 ) {\n\n\t\t\t\treturn RGBEToLinear( value );\n\n\t\t\t} else if ( inputEncoding == 3 ) {\n\n\t\t\t\treturn RGBMToLinear( value, 7.0 );\n\n\t\t\t} else if ( inputEncoding == 4 ) {\n\n\t\t\t\treturn RGBMToLinear( value, 16.0 );\n\n\t\t\t} else if ( inputEncoding == 5 ) {\n\n\t\t\t\treturn RGBDToLinear( value, 256.0 );\n\n\t\t\t} else {\n\n\t\t\t\treturn GammaToLinear( value, 2.2 );\n\n\t\t\t}\n\n\t\t}\n\n\t\tvec4 linearToOutputTexel( vec4 value ) {\n\n\t\t\tif ( outputEncoding == 0 ) {\n\n\t\t\t\treturn value;\n\n\t\t\t} else if ( outputEncoding == 1 ) {\n\n\t\t\t\treturn LinearTosRGB( value );\n\n\t\t\t} else if ( outputEncoding == 2 ) {\n\n\t\t\t\treturn LinearToRGBE( value );\n\n\t\t\t} else if ( outputEncoding == 3 ) {\n\n\t\t\t\treturn LinearToRGBM( value, 7.0 );\n\n\t\t\t} else if ( outputEncoding == 4 ) {\n\n\t\t\t\treturn LinearToRGBM( value, 16.0 );\n\n\t\t\t} else if ( outputEncoding == 5 ) {\n\n\t\t\t\treturn LinearToRGBD( value, 256.0 );\n\n\t\t\t} else {\n\n\t\t\t\treturn LinearToGamma( value, 2.2 );\n\n\t\t\t}\n\n\t\t}\n\n\t\tvec4 envMapTexelToLinear( vec4 color ) {\n\n\t\t\treturn inputTexelToLinear( color );\n\n\t\t}\n\t"}ml.create=function(t,e){return console.log("THREE.Curve.create() has been deprecated"),t.prototype=Object.create(ml.prototype),t.prototype.constructor=t,t.prototype.getPoint=e,t},Bl.prototype.fromPoints=function(t){return console.warn("THREE.Path: .fromPoints() has been renamed to .setFromPoints()."),this.setFromPoints(t)},fh.prototype.setColors=function(){console.error("THREE.GridHelper: setColors() has been deprecated, pass them in the constructor instead.")},hh.prototype.update=function(){console.error("THREE.SkeletonHelper: update() no longer needs to be called.")},ol.prototype.extractUrlBase=function(t){return console.warn("THREE.Loader: .extractUrlBase() has been deprecated. Use THREE.LoaderUtils.extractUrlBase() instead."),rc.extractUrlBase(t)},ol.Handlers={add:function(){console.error("THREE.Loader: Handlers.add() has been removed. Use LoadingManager.addHandler() instead.")},get:function(){console.error("THREE.Loader: Handlers.get() has been removed. Use LoadingManager.getHandler() instead.")}},eh.prototype.center=function(t){return console.warn("THREE.Box2: .center() has been renamed to .getCenter()."),this.getCenter(t)},eh.prototype.empty=function(){return console.warn("THREE.Box2: .empty() has been renamed to .isEmpty()."),this.isEmpty()},eh.prototype.isIntersectionBox=function(t){return console.warn("THREE.Box2: .isIntersectionBox() has been renamed to .intersectsBox()."),this.intersectsBox(t)},eh.prototype.size=function(t){return console.warn("THREE.Box2: .size() has been renamed to .getSize()."),this.getSize(t)},Pt.prototype.center=function(t){return console.warn("THREE.Box3: .center() has been renamed to .getCenter()."),this.getCenter(t)},Pt.prototype.empty=function(){return console.warn("THREE.Box3: .empty() has been renamed to .isEmpty()."),this.isEmpty()},Pt.prototype.isIntersectionBox=function(t){return console.warn("THREE.Box3: .isIntersectionBox() has been renamed to .intersectsBox()."),this.intersectsBox(t)},Pt.prototype.isIntersectionSphere=function(t){return console.warn("THREE.Box3: .isIntersectionSphere() has been renamed to .intersectsSphere()."),this.intersectsSphere(t)},Pt.prototype.size=function(t){return console.warn("THREE.Box3: .size() has been renamed to .getSize()."),this.getSize(t)},Jt.prototype.empty=function(){return console.warn("THREE.Sphere: .empty() has been renamed to .isEmpty()."),this.isEmpty()},ai.prototype.setFromMatrix=function(t){return console.warn("THREE.Frustum: .setFromMatrix() has been renamed to .setFromProjectionMatrix()."),this.setFromProjectionMatrix(t)},rh.prototype.center=function(t){return console.warn("THREE.Line3: .center() has been renamed to .getCenter()."),this.getCenter(t)},yt.prototype.flattenToArrayOffset=function(t,e){return console.warn("THREE.Matrix3: .flattenToArrayOffset() has been deprecated. Use .toArray() instead."),this.toArray(t,e)},yt.prototype.multiplyVector3=function(t){return console.warn("THREE.Matrix3: .multiplyVector3() has been removed. Use vector.applyMatrix3( matrix ) instead."),t.applyMatrix3(this)},yt.prototype.multiplyVector3Array=function(){console.error("THREE.Matrix3: .multiplyVector3Array() has been removed.")},yt.prototype.applyToBufferAttribute=function(t){return console.warn("THREE.Matrix3: .applyToBufferAttribute() has been removed. Use attribute.applyMatrix3( matrix ) instead."),t.applyMatrix3(this)},yt.prototype.applyToVector3Array=function(){console.error("THREE.Matrix3: .applyToVector3Array() has been removed.")},yt.prototype.getInverse=function(t){return console.warn("THREE.Matrix3: .getInverse() has been removed. Use matrixInv.copy( matrix ).invert(); instead."),this.copy(t).invert()},se.prototype.extractPosition=function(t){return console.warn("THREE.Matrix4: .extractPosition() has been renamed to .copyPosition()."),this.copyPosition(t)},se.prototype.flattenToArrayOffset=function(t,e){return console.warn("THREE.Matrix4: .flattenToArrayOffset() has been deprecated. Use .toArray() instead."),this.toArray(t,e)},se.prototype.getPosition=function(){return console.warn("THREE.Matrix4: .getPosition() has been removed. Use Vector3.setFromMatrixPosition( matrix ) instead."),(new Lt).setFromMatrixColumn(this,3)},se.prototype.setRotationFromQuaternion=function(t){return console.warn("THREE.Matrix4: .setRotationFromQuaternion() has been renamed to .makeRotationFromQuaternion()."),this.makeRotationFromQuaternion(t)},se.prototype.multiplyToArray=function(){console.warn("THREE.Matrix4: .multiplyToArray() has been removed.")},se.prototype.multiplyVector3=function(t){return console.warn("THREE.Matrix4: .multiplyVector3() has been removed. Use vector.applyMatrix4( matrix ) instead."),t.applyMatrix4(this)},se.prototype.multiplyVector4=function(t){return console.warn("THREE.Matrix4: .multiplyVector4() has been removed. Use vector.applyMatrix4( matrix ) instead."),t.applyMatrix4(this)},se.prototype.multiplyVector3Array=function(){console.error("THREE.Matrix4: .multiplyVector3Array() has been removed.")},se.prototype.rotateAxis=function(t){console.warn("THREE.Matrix4: .rotateAxis() has been removed. Use Vector3.transformDirection( matrix ) instead."),t.transformDirection(this)},se.prototype.crossVector=function(t){return console.warn("THREE.Matrix4: .crossVector() has been removed. Use vector.applyMatrix4( matrix ) instead."),t.applyMatrix4(this)},se.prototype.translate=function(){console.error("THREE.Matrix4: .translate() has been removed.")},se.prototype.rotateX=function(){console.error("THREE.Matrix4: .rotateX() has been removed.")},se.prototype.rotateY=function(){console.error("THREE.Matrix4: .rotateY() has been removed.")},se.prototype.rotateZ=function(){console.error("THREE.Matrix4: .rotateZ() has been removed.")},se.prototype.rotateByAxis=function(){console.error("THREE.Matrix4: .rotateByAxis() has been removed.")},se.prototype.applyToBufferAttribute=function(t){return console.warn("THREE.Matrix4: .applyToBufferAttribute() has been removed. Use attribute.applyMatrix4( matrix ) instead."),t.applyMatrix4(this)},se.prototype.applyToVector3Array=function(){console.error("THREE.Matrix4: .applyToVector3Array() has been removed.")},se.prototype.makeFrustum=function(t,e,n,i,r,s){return console.warn("THREE.Matrix4: .makeFrustum() has been removed. Use .makePerspective( left, right, top, bottom, near, far ) instead."),this.makePerspective(t,e,i,n,r,s)},se.prototype.getInverse=function(t){return console.warn("THREE.Matrix4: .getInverse() has been removed. Use matrixInv.copy( matrix ).invert(); instead."),this.copy(t).invert()},Ne.prototype.isIntersectionLine=function(t){return console.warn("THREE.Plane: .isIntersectionLine() has been renamed to .intersectsLine()."),this.intersectsLine(t)},At.prototype.multiplyVector3=function(t){return console.warn("THREE.Quaternion: .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead."),t.applyQuaternion(this)},At.prototype.inverse=function(){return console.warn("THREE.Quaternion: .inverse() has been renamed to invert()."),this.invert()},re.prototype.isIntersectionBox=function(t){return console.warn("THREE.Ray: .isIntersectionBox() has been renamed to .intersectsBox()."),this.intersectsBox(t)},re.prototype.isIntersectionPlane=function(t){return console.warn("THREE.Ray: .isIntersectionPlane() has been renamed to .intersectsPlane()."),this.intersectsPlane(t)},re.prototype.isIntersectionSphere=function(t){return console.warn("THREE.Ray: .isIntersectionSphere() has been renamed to .intersectsSphere()."),this.intersectsSphere(t)},je.prototype.area=function(){return console.warn("THREE.Triangle: .area() has been renamed to .getArea()."),this.getArea()},je.prototype.barycoordFromPoint=function(t,e){return console.warn("THREE.Triangle: .barycoordFromPoint() has been renamed to .getBarycoord()."),this.getBarycoord(t,e)},je.prototype.midpoint=function(t){return console.warn("THREE.Triangle: .midpoint() has been renamed to .getMidpoint()."),this.getMidpoint(t)},je.prototypenormal=function(t){return console.warn("THREE.Triangle: .normal() has been renamed to .getNormal()."),this.getNormal(t)},je.prototype.plane=function(t){return console.warn("THREE.Triangle: .plane() has been renamed to .getPlane()."),this.getPlane(t)},je.barycoordFromPoint=function(t,e,n,i,r){return console.warn("THREE.Triangle: .barycoordFromPoint() has been renamed to .getBarycoord()."),je.getBarycoord(t,e,n,i,r)},je.normal=function(t,e,n,i){return console.warn("THREE.Triangle: .normal() has been renamed to .getNormal()."),je.getNormal(t,e,n,i)},zl.prototype.extractAllPoints=function(t){return console.warn("THREE.Shape: .extractAllPoints() has been removed. Use .extractPoints() instead."),this.extractPoints(t)},zl.prototype.extrude=function(t){return console.warn("THREE.Shape: .extrude() has been removed. Use ExtrudeGeometry() instead."),new go(this,t)},zl.prototype.makeGeometry=function(t){return console.warn("THREE.Shape: .makeGeometry() has been removed. Use ShapeGeometry() instead."),new Mo(this,t)},vt.prototype.fromAttribute=function(t,e,n){return console.warn("THREE.Vector2: .fromAttribute() has been renamed to .fromBufferAttribute()."),this.fromBufferAttribute(t,e,n)},vt.prototype.distanceToManhattan=function(t){return console.warn("THREE.Vector2: .distanceToManhattan() has been renamed to .manhattanDistanceTo()."),this.manhattanDistanceTo(t)},vt.prototype.lengthManhattan=function(){return console.warn("THREE.Vector2: .lengthManhattan() has been renamed to .manhattanLength()."),this.manhattanLength()},Lt.prototype.setEulerFromRotationMatrix=function(){console.error("THREE.Vector3: .setEulerFromRotationMatrix() has been removed. Use Euler.setFromRotationMatrix() instead.")},Lt.prototype.setEulerFromQuaternion=function(){console.error("THREE.Vector3: .setEulerFromQuaternion() has been removed. Use Euler.setFromQuaternion() instead.")},Lt.prototype.getPositionFromMatrix=function(t){return console.warn("THREE.Vector3: .getPositionFromMatrix() has been renamed to .setFromMatrixPosition()."),this.setFromMatrixPosition(t)},Lt.prototype.getScaleFromMatrix=function(t){return console.warn("THREE.Vector3: .getScaleFromMatrix() has been renamed to .setFromMatrixScale()."),this.setFromMatrixScale(t)},Lt.prototype.getColumnFromMatrix=function(t,e){return console.warn("THREE.Vector3: .getColumnFromMatrix() has been renamed to .setFromMatrixColumn()."),this.setFromMatrixColumn(e,t)},Lt.prototype.applyProjection=function(t){return console.warn("THREE.Vector3: .applyProjection() has been removed. Use .applyMatrix4( m ) instead."),this.applyMatrix4(t)},Lt.prototype.fromAttribute=function(t,e,n){return console.warn("THREE.Vector3: .fromAttribute() has been renamed to .fromBufferAttribute()."),this.fromBufferAttribute(t,e,n)},Lt.prototype.distanceToManhattan=function(t){return console.warn("THREE.Vector3: .distanceToManhattan() has been renamed to .manhattanDistanceTo()."),this.manhattanDistanceTo(t)},Lt.prototype.lengthManhattan=function(){return console.warn("THREE.Vector3: .lengthManhattan() has been renamed to .manhattanLength()."),this.manhattanLength()},St.prototype.fromAttribute=function(t,e,n){return console.warn("THREE.Vector4: .fromAttribute() has been renamed to .fromBufferAttribute()."),this.fromBufferAttribute(t,e,n)},St.prototype.lengthManhattan=function(){return console.warn("THREE.Vector4: .lengthManhattan() has been renamed to .manhattanLength()."),this.manhattanLength()},Ce.prototype.getChildByName=function(t){return console.warn("THREE.Object3D: .getChildByName() has been renamed to .getObjectByName()."),this.getObjectByName(t)},Ce.prototype.renderDepth=function(){console.warn("THREE.Object3D: .renderDepth has been removed. Use .renderOrder, instead.")},Ce.prototype.translate=function(t,e){return console.warn("THREE.Object3D: .translate() has been removed. Use .translateOnAxis( axis, distance ) instead."),this.translateOnAxis(e,t)},Ce.prototype.getWorldRotation=function(){console.error("THREE.Object3D: .getWorldRotation() has been removed. Use THREE.Object3D.getWorldQuaternion( target ) instead.")},Ce.prototype.applyMatrix=function(t){return console.warn("THREE.Object3D: .applyMatrix() has been renamed to .applyMatrix4()."),this.applyMatrix4(t)},Object.defineProperties(Ce.prototype,{eulerOrder:{get:function(){return console.warn("THREE.Object3D: .eulerOrder is now .rotation.order."),this.rotation.order},set:function(t){console.warn("THREE.Object3D: .eulerOrder is now .rotation.order."),this.rotation.order=t}},useQuaternion:{get:function(){console.warn("THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.")},set:function(){console.warn("THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.")}}}),Wn.prototype.setDrawMode=function(){console.error("THREE.Mesh: .setDrawMode() has been removed. The renderer now always assumes THREE.TrianglesDrawMode. Transform your geometry via BufferGeometryUtils.toTrianglesDrawMode() if necessary.")},Object.defineProperties(Wn.prototype,{drawMode:{get:function(){return console.error("THREE.Mesh: .drawMode has been removed. The renderer now always assumes THREE.TrianglesDrawMode."),0},set:function(){console.error("THREE.Mesh: .drawMode has been removed. The renderer now always assumes THREE.TrianglesDrawMode. Transform your geometry via BufferGeometryUtils.toTrianglesDrawMode() if necessary.")}}}),$s.prototype.initBones=function(){console.error("THREE.SkinnedMesh: initBones() has been removed.")},Kn.prototype.setLens=function(t,e){console.warn("THREE.PerspectiveCamera.setLens is deprecated. Use .setFocalLength and .filmGauge for a photographic setup."),void 0!==e&&(this.filmGauge=e),this.setFocalLength(t)},Object.defineProperties(Fl.prototype,{onlyShadow:{set:function(){console.warn("THREE.Light: .onlyShadow has been removed.")}},shadowCameraFov:{set:function(t){console.warn("THREE.Light: .shadowCameraFov is now .shadow.camera.fov."),this.shadow.camera.fov=t}},shadowCameraLeft:{set:function(t){console.warn("THREE.Light: .shadowCameraLeft is now .shadow.camera.left."),this.shadow.camera.left=t}},shadowCameraRight:{set:function(t){console.warn("THREE.Light: .shadowCameraRight is now .shadow.camera.right."),this.shadow.camera.right=t}},shadowCameraTop:{set:function(t){console.warn("THREE.Light: .shadowCameraTop is now .shadow.camera.top."),this.shadow.camera.top=t}},shadowCameraBottom:{set:function(t){console.warn("THREE.Light: .shadowCameraBottom is now .shadow.camera.bottom."),this.shadow.camera.bottom=t}},shadowCameraNear:{set:function(t){console.warn("THREE.Light: .shadowCameraNear is now .shadow.camera.near."),this.shadow.camera.near=t}},shadowCameraFar:{set:function(t){console.warn("THREE.Light: .shadowCameraFar is now .shadow.camera.far."),this.shadow.camera.far=t}},shadowCameraVisible:{set:function(){console.warn("THREE.Light: .shadowCameraVisible has been removed. Use new THREE.CameraHelper( light.shadow.camera ) instead.")}},shadowBias:{set:function(t){console.warn("THREE.Light: .shadowBias is now .shadow.bias."),this.shadow.bias=t}},shadowDarkness:{set:function(){console.warn("THREE.Light: .shadowDarkness has been removed.")}},shadowMapWidth:{set:function(t){console.warn("THREE.Light: .shadowMapWidth is now .shadow.mapSize.width."),this.shadow.mapSize.width=t}},shadowMapHeight:{set:function(t){console.warn("THREE.Light: .shadowMapHeight is now .shadow.mapSize.height."),this.shadow.mapSize.height=t}}}),Object.defineProperties(sn.prototype,{length:{get:function(){return console.warn("THREE.BufferAttribute: .length has been deprecated. Use .count instead."),this.array.length}},dynamic:{get:function(){return console.warn("THREE.BufferAttribute: .dynamic has been deprecated. Use .usage instead."),this.usage===nt},set:function(){console.warn("THREE.BufferAttribute: .dynamic has been deprecated. Use .usage instead."),this.setUsage(nt)}}}),sn.prototype.setDynamic=function(t){return console.warn("THREE.BufferAttribute: .setDynamic() has been deprecated. Use .setUsage() instead."),this.setUsage(!0===t?nt:et),this},sn.prototype.copyIndicesArray=function(){console.error("THREE.BufferAttribute: .copyIndicesArray() has been removed.")},sn.prototype.setArray=function(){console.error("THREE.BufferAttribute: .setArray has been removed. Use BufferGeometry .setAttribute to replace/resize attribute buffers")},En.prototype.addIndex=function(t){console.warn("THREE.BufferGeometry: .addIndex() has been renamed to .setIndex()."),this.setIndex(t)},En.prototype.addAttribute=function(t,e){return console.warn("THREE.BufferGeometry: .addAttribute() has been renamed to .setAttribute()."),e&&e.isBufferAttribute||e&&e.isInterleavedBufferAttribute?"index"===t?(console.warn("THREE.BufferGeometry.addAttribute: Use .setIndex() for index attribute."),this.setIndex(e),this):this.setAttribute(t,e):(console.warn("THREE.BufferGeometry: .addAttribute() now expects ( name, attribute )."),this.setAttribute(t,new sn(arguments[1],arguments[2])))},En.prototype.addDrawCall=function(t,e,n){void 0!==n&&console.warn("THREE.BufferGeometry: .addDrawCall() no longer supports indexOffset."),console.warn("THREE.BufferGeometry: .addDrawCall() is now .addGroup()."),this.addGroup(t,e)},En.prototype.clearDrawCalls=function(){console.warn("THREE.BufferGeometry: .clearDrawCalls() is now .clearGroups()."),this.clearGroups()},En.prototype.computeOffsets=function(){console.warn("THREE.BufferGeometry: .computeOffsets() has been removed.")},En.prototype.removeAttribute=function(t){return console.warn("THREE.BufferGeometry: .removeAttribute() has been renamed to .deleteAttribute()."),this.deleteAttribute(t)},En.prototype.applyMatrix=function(t){return console.warn("THREE.BufferGeometry: .applyMatrix() has been renamed to .applyMatrix4()."),this.applyMatrix4(t)},Object.defineProperties(En.prototype,{drawcalls:{get:function(){return console.error("THREE.BufferGeometry: .drawcalls has been renamed to .groups."),this.groups}},offsets:{get:function(){return console.warn("THREE.BufferGeometry: .offsets has been renamed to .groups."),this.groups}}}),Es.prototype.setDynamic=function(t){return console.warn("THREE.InterleavedBuffer: .setDynamic() has been deprecated. Use .setUsage() instead."),this.setUsage(!0===t?nt:et),this},Es.prototype.setArray=function(){console.error("THREE.InterleavedBuffer: .setArray has been removed. Use BufferGeometry .setAttribute to replace/resize attribute buffers")},go.prototype.getArrays=function(){console.error("THREE.ExtrudeGeometry: .getArrays() has been removed.")},go.prototype.addShapeList=function(){console.error("THREE.ExtrudeGeometry: .addShapeList() has been removed.")},go.prototype.addShape=function(){console.error("THREE.ExtrudeGeometry: .addShape() has been removed.")},Ts.prototype.dispose=function(){console.error("THREE.Scene: .dispose() has been removed.")},Zc.prototype.onUpdate=function(){return console.warn("THREE.Uniform: .onUpdate() has been removed. Use object.onBeforeRender() instead."),this},Object.defineProperties(Xe.prototype,{wrapAround:{get:function(){console.warn("THREE.Material: .wrapAround has been removed.")},set:function(){console.warn("THREE.Material: .wrapAround has been removed.")}},overdraw:{get:function(){console.warn("THREE.Material: .overdraw has been removed.")},set:function(){console.warn("THREE.Material: .overdraw has been removed.")}},wrapRGB:{get:function(){return console.warn("THREE.Material: .wrapRGB has been removed."),new tn}},shading:{get:function(){console.error("THREE."+this.type+": .shading has been removed. Use the boolean .flatShading instead.")},set:function(t){console.warn("THREE."+this.type+": .shading has been removed. Use the boolean .flatShading instead."),this.flatShading=1===t}},stencilMask:{get:function(){return console.warn("THREE."+this.type+": .stencilMask has been removed. Use .stencilFuncMask instead."),this.stencilFuncMask},set:function(t){console.warn("THREE."+this.type+": .stencilMask has been removed. Use .stencilFuncMask instead."),this.stencilFuncMask=t}}}),Object.defineProperties(Jn.prototype,{derivatives:{get:function(){return console.warn("THREE.ShaderMaterial: .derivatives has been moved to .extensions.derivatives."),this.extensions.derivatives},set:function(t){console.warn("THREE. ShaderMaterial: .derivatives has been moved to .extensions.derivatives."),this.extensions.derivatives=t}}}),ws.prototype.clearTarget=function(t,e,n,i){console.warn("THREE.WebGLRenderer: .clearTarget() has been deprecated. Use .setRenderTarget() and .clear() instead."),this.setRenderTarget(t),this.clear(e,n,i)},ws.prototype.animate=function(t){console.warn("THREE.WebGLRenderer: .animate() is now .setAnimationLoop()."),this.setAnimationLoop(t)},ws.prototype.getCurrentRenderTarget=function(){return console.warn("THREE.WebGLRenderer: .getCurrentRenderTarget() is now .getRenderTarget()."),this.getRenderTarget()},ws.prototype.getMaxAnisotropy=function(){return console.warn("THREE.WebGLRenderer: .getMaxAnisotropy() is now .capabilities.getMaxAnisotropy()."),this.capabilities.getMaxAnisotropy()},ws.prototype.getPrecision=function(){return console.warn("THREE.WebGLRenderer: .getPrecision() is now .capabilities.precision."),this.capabilities.precision},ws.prototype.resetGLState=function(){return console.warn("THREE.WebGLRenderer: .resetGLState() is now .state.reset()."),this.state.reset()},ws.prototype.supportsFloatTextures=function(){return console.warn("THREE.WebGLRenderer: .supportsFloatTextures() is now .extensions.get( 'OES_texture_float' )."),this.extensions.get("OES_texture_float")},ws.prototype.supportsHalfFloatTextures=function(){return console.warn("THREE.WebGLRenderer: .supportsHalfFloatTextures() is now .extensions.get( 'OES_texture_half_float' )."),this.extensions.get("OES_texture_half_float")},ws.prototype.supportsStandardDerivatives=function(){return console.warn("THREE.WebGLRenderer: .supportsStandardDerivatives() is now .extensions.get( 'OES_standard_derivatives' )."),this.extensions.get("OES_standard_derivatives")},ws.prototype.supportsCompressedTextureS3TC=function(){return console.warn("THREE.WebGLRenderer: .supportsCompressedTextureS3TC() is now .extensions.get( 'WEBGL_compressed_texture_s3tc' )."),this.extensions.get("WEBGL_compressed_texture_s3tc")},ws.prototype.supportsCompressedTexturePVRTC=function(){return console.warn("THREE.WebGLRenderer: .supportsCompressedTexturePVRTC() is now .extensions.get( 'WEBGL_compressed_texture_pvrtc' )."),this.extensions.get("WEBGL_compressed_texture_pvrtc")},ws.prototype.supportsBlendMinMax=function(){return console.warn("THREE.WebGLRenderer: .supportsBlendMinMax() is now .extensions.get( 'EXT_blend_minmax' )."),this.extensions.get("EXT_blend_minmax")},ws.prototype.supportsVertexTextures=function(){return console.warn("THREE.WebGLRenderer: .supportsVertexTextures() is now .capabilities.vertexTextures."),this.capabilities.vertexTextures},ws.prototype.supportsInstancedArrays=function(){return console.warn("THREE.WebGLRenderer: .supportsInstancedArrays() is now .extensions.get( 'ANGLE_instanced_arrays' )."),this.extensions.get("ANGLE_instanced_arrays")},ws.prototype.enableScissorTest=function(t){console.warn("THREE.WebGLRenderer: .enableScissorTest() is now .setScissorTest()."),this.setScissorTest(t)},ws.prototype.initMaterial=function(){console.warn("THREE.WebGLRenderer: .initMaterial() has been removed.")},ws.prototype.addPrePlugin=function(){console.warn("THREE.WebGLRenderer: .addPrePlugin() has been removed.")},ws.prototype.addPostPlugin=function(){console.warn("THREE.WebGLRenderer: .addPostPlugin() has been removed.")},ws.prototype.updateShadowMap=function(){console.warn("THREE.WebGLRenderer: .updateShadowMap() has been removed.")},ws.prototype.setFaceCulling=function(){console.warn("THREE.WebGLRenderer: .setFaceCulling() has been removed.")},ws.prototype.allocTextureUnit=function(){console.warn("THREE.WebGLRenderer: .allocTextureUnit() has been removed.")},ws.prototype.setTexture=function(){console.warn("THREE.WebGLRenderer: .setTexture() has been removed.")},ws.prototype.setTexture2D=function(){console.warn("THREE.WebGLRenderer: .setTexture2D() has been removed.")},ws.prototype.setTextureCube=function(){console.warn("THREE.WebGLRenderer: .setTextureCube() has been removed.")},ws.prototype.getActiveMipMapLevel=function(){return console.warn("THREE.WebGLRenderer: .getActiveMipMapLevel() is now .getActiveMipmapLevel()."),this.getActiveMipmapLevel()},Object.defineProperties(ws.prototype,{shadowMapEnabled:{get:function(){return this.shadowMap.enabled},set:function(t){console.warn("THREE.WebGLRenderer: .shadowMapEnabled is now .shadowMap.enabled."),this.shadowMap.enabled=t}},shadowMapType:{get:function(){return this.shadowMap.type},set:function(t){console.warn("THREE.WebGLRenderer: .shadowMapType is now .shadowMap.type."),this.shadowMap.type=t}},shadowMapCullFace:{get:function(){console.warn("THREE.WebGLRenderer: .shadowMapCullFace has been removed. Set Material.shadowSide instead.")},set:function(){console.warn("THREE.WebGLRenderer: .shadowMapCullFace has been removed. Set Material.shadowSide instead.")}},context:{get:function(){return console.warn("THREE.WebGLRenderer: .context has been removed. Use .getContext() instead."),this.getContext()}},vr:{get:function(){return console.warn("THREE.WebGLRenderer: .vr has been renamed to .xr"),this.xr}},gammaInput:{get:function(){return console.warn("THREE.WebGLRenderer: .gammaInput has been removed. Set the encoding for textures via Texture.encoding instead."),!1},set:function(){console.warn("THREE.WebGLRenderer: .gammaInput has been removed. Set the encoding for textures via Texture.encoding instead.")}},gammaOutput:{get:function(){return console.warn("THREE.WebGLRenderer: .gammaOutput has been removed. Set WebGLRenderer.outputEncoding instead."),!1},set:function(t){console.warn("THREE.WebGLRenderer: .gammaOutput has been removed. Set WebGLRenderer.outputEncoding instead."),this.outputEncoding=!0===t?Y:X}},toneMappingWhitePoint:{get:function(){return console.warn("THREE.WebGLRenderer: .toneMappingWhitePoint has been removed."),1},set:function(){console.warn("THREE.WebGLRenderer: .toneMappingWhitePoint has been removed.")}}}),Object.defineProperties(us.prototype,{cullFace:{get:function(){console.warn("THREE.WebGLRenderer: .shadowMap.cullFace has been removed. Set Material.shadowSide instead.")},set:function(){console.warn("THREE.WebGLRenderer: .shadowMap.cullFace has been removed. Set Material.shadowSide instead.")}},renderReverseSided:{get:function(){console.warn("THREE.WebGLRenderer: .shadowMap.renderReverseSided has been removed. Set Material.shadowSide instead.")},set:function(){console.warn("THREE.WebGLRenderer: .shadowMap.renderReverseSided has been removed. Set Material.shadowSide instead.")}},renderSingleSided:{get:function(){console.warn("THREE.WebGLRenderer: .shadowMap.renderSingleSided has been removed. Set Material.shadowSide instead.")},set:function(){console.warn("THREE.WebGLRenderer: .shadowMap.renderSingleSided has been removed. Set Material.shadowSide instead.")}}}),Object.defineProperties(Tt.prototype,{wrapS:{get:function(){return console.warn("THREE.WebGLRenderTarget: .wrapS is now .texture.wrapS."),this.texture.wrapS},set:function(t){console.warn("THREE.WebGLRenderTarget: .wrapS is now .texture.wrapS."),this.texture.wrapS=t}},wrapT:{get:function(){return console.warn("THREE.WebGLRenderTarget: .wrapT is now .texture.wrapT."),this.texture.wrapT},set:function(t){console.warn("THREE.WebGLRenderTarget: .wrapT is now .texture.wrapT."),this.texture.wrapT=t}},magFilter:{get:function(){return console.warn("THREE.WebGLRenderTarget: .magFilter is now .texture.magFilter."),this.texture.magFilter},set:function(t){console.warn("THREE.WebGLRenderTarget: .magFilter is now .texture.magFilter."),this.texture.magFilter=t}},minFilter:{get:function(){return console.warn("THREE.WebGLRenderTarget: .minFilter is now .texture.minFilter."),this.texture.minFilter},set:function(t){console.warn("THREE.WebGLRenderTarget: .minFilter is now .texture.minFilter."),this.texture.minFilter=t}},anisotropy:{get:function(){return console.warn("THREE.WebGLRenderTarget: .anisotropy is now .texture.anisotropy."),this.texture.anisotropy},set:function(t){console.warn("THREE.WebGLRenderTarget: .anisotropy is now .texture.anisotropy."),this.texture.anisotropy=t}},offset:{get:function(){return console.warn("THREE.WebGLRenderTarget: .offset is now .texture.offset."),this.texture.offset},set:function(t){console.warn("THREE.WebGLRenderTarget: .offset is now .texture.offset."),this.texture.offset=t}},repeat:{get:function(){return console.warn("THREE.WebGLRenderTarget: .repeat is now .texture.repeat."),this.texture.repeat},set:function(t){console.warn("THREE.WebGLRenderTarget: .repeat is now .texture.repeat."),this.texture.repeat=t}},format:{get:function(){return console.warn("THREE.WebGLRenderTarget: .format is now .texture.format."),this.texture.format},set:function(t){console.warn("THREE.WebGLRenderTarget: .format is now .texture.format."),this.texture.format=t}},type:{get:function(){return console.warn("THREE.WebGLRenderTarget: .type is now .texture.type."),this.texture.type},set:function(t){console.warn("THREE.WebGLRenderTarget: .type is now .texture.type."),this.texture.type=t}},generateMipmaps:{get:function(){return console.warn("THREE.WebGLRenderTarget: .generateMipmaps is now .texture.generateMipmaps."),this.texture.generateMipmaps},set:function(t){console.warn("THREE.WebGLRenderTarget: .generateMipmaps is now .texture.generateMipmaps."),this.texture.generateMipmaps=t}}}),Lc.prototype.load=function(t){console.warn("THREE.Audio: .load has been deprecated. Use THREE.AudioLoader instead.");const e=this;return(new vc).load(t,(function(t){e.setBuffer(t)})),this},Ic.prototype.getData=function(){return console.warn("THREE.AudioAnalyser: .getData() is now .getFrequencyData()."),this.getFrequencyData()},ti.prototype.updateCubeMap=function(t,e){return console.warn("THREE.CubeCamera: .updateCubeMap() is now .update()."),this.update(t,e)},ti.prototype.clear=function(t,e,n,i){return console.warn("THREE.CubeCamera: .clear() is now .renderTarget.clear()."),this.renderTarget.clear(t,e,n,i)},_t.crossOrigin=void 0,_t.loadTexture=function(t,e,n,i){console.warn("THREE.ImageUtils.loadTexture has been deprecated. Use THREE.TextureLoader() instead.");const r=new pl;r.setCrossOrigin(this.crossOrigin);const s=r.load(t,n,void 0,i);return e&&(s.mapping=e),s},_t.loadTextureCube=function(t,e,n,i){console.warn("THREE.ImageUtils.loadTextureCube has been deprecated. Use THREE.CubeTextureLoader() instead.");const r=new ul;r.setCrossOrigin(this.crossOrigin);const s=r.load(t,n,void 0,i);return e&&(s.mapping=e),s},_t.loadCompressedTexture=function(){console.error("THREE.ImageUtils.loadCompressedTexture has been removed. Use THREE.DDSLoader instead.")},_t.loadCompressedTextureCube=function(){console.error("THREE.ImageUtils.loadCompressedTextureCube has been removed. Use THREE.DDSLoader instead.")};const eu={createMultiMaterialObject:function(){console.error("THREE.SceneUtils has been moved to /examples/jsm/utils/SceneUtils.js")},detach:function(){console.error("THREE.SceneUtils has been moved to /examples/jsm/utils/SceneUtils.js")},attach:function(){console.error("THREE.SceneUtils has been moved to /examples/jsm/utils/SceneUtils.js")}};"undefined"!=typeof __THREE_DEVTOOLS__&&__THREE_DEVTOOLS__.dispatchEvent(new CustomEvent("register",{detail:{revision:e}})),"undefined"!=typeof window&&(window.__THREE__?console.warn("WARNING: Multiple instances of Three.js being imported."):window.__THREE__=e),t.ACESFilmicToneMapping=4,t.AddEquation=n,t.AddOperation=2,t.AdditiveAnimationBlendMode=q,t.AdditiveBlending=2,t.AlphaFormat=1021,t.AlwaysDepth=1,t.AlwaysStencilFunc=519,t.AmbientLight=$l,t.AmbientLightProbe=xc,t.AnimationClip=nl,t.AnimationLoader=class extends ol{constructor(t){super(t)}load(t,e,n,i){const r=this,s=new cl(this.manager);s.setPath(this.path),s.setRequestHeader(this.requestHeader),s.setWithCredentials(this.withCredentials),s.load(t,(function(n){try{e(r.parse(JSON.parse(n)))}catch(e){i?i(e):console.error(e),r.manager.itemError(t)}}),n,i)}parse(t){const e=[];for(let n=0;n.99999)this.quaternion.set(0,0,0,1);else if(t.y<-.99999)this.quaternion.set(1,0,0,0);else{Sh.set(t.z,0,-t.x).normalize();const e=Math.acos(t.y);this.quaternion.setFromAxisAngle(Sh,e)}}setLength(t,e=.2*t,n=.2*e){this.line.scale.set(1,Math.max(1e-4,t-e),1),this.line.updateMatrix(),this.cone.scale.set(n,e,n),this.cone.position.y=t,this.cone.updateMatrix()}setColor(t){this.line.material.color.set(t),this.cone.material.color.set(t)}copy(t){return super.copy(t,!1),this.line.copy(t.line),this.cone.copy(t.cone),this}},t.Audio=Lc,t.AudioAnalyser=Ic,t.AudioContext=gc,t.AudioListener=class extends Ce{constructor(){super(),this.type="AudioListener",this.context=gc.getContext(),this.gain=this.context.createGain(),this.gain.connect(this.context.destination),this.filter=null,this.timeDelta=0,this._clock=new bc}getInput(){return this.gain}removeFilter(){return null!==this.filter&&(this.gain.disconnect(this.filter),this.filter.disconnect(this.context.destination),this.gain.connect(this.context.destination),this.filter=null),this}getFilter(){return this.filter}setFilter(t){return null!==this.filter?(this.gain.disconnect(this.filter),this.filter.disconnect(this.context.destination)):this.gain.disconnect(this.context.destination),this.filter=t,this.gain.connect(this.filter),this.filter.connect(this.context.destination),this}getMasterVolume(){return this.gain.gain.value}setMasterVolume(t){return this.gain.gain.setTargetAtTime(t,this.context.currentTime,.01),this}updateMatrixWorld(t){super.updateMatrixWorld(t);const e=this.context.listener,n=this.up;if(this.timeDelta=this._clock.getDelta(),this.matrixWorld.decompose(Sc,Tc,Ec),Ac.set(0,0,-1).applyQuaternion(Tc),e.positionX){const t=this.context.currentTime+this.timeDelta;e.positionX.linearRampToValueAtTime(Sc.x,t),e.positionY.linearRampToValueAtTime(Sc.y,t),e.positionZ.linearRampToValueAtTime(Sc.z,t),e.forwardX.linearRampToValueAtTime(Ac.x,t),e.forwardY.linearRampToValueAtTime(Ac.y,t),e.forwardZ.linearRampToValueAtTime(Ac.z,t),e.upX.linearRampToValueAtTime(n.x,t),e.upY.linearRampToValueAtTime(n.y,t),e.upZ.linearRampToValueAtTime(n.z,t)}else e.setPosition(Sc.x,Sc.y,Sc.z),e.setOrientation(Ac.x,Ac.y,Ac.z,n.x,n.y,n.z)}},t.AudioLoader=vc,t.AxesHelper=Ah,t.AxisHelper=function(t){return console.warn("THREE.AxisHelper has been renamed to THREE.AxesHelper."),new Ah(t)},t.BackSide=1,t.BasicDepthPacking=3200,t.BasicShadowMap=0,t.BinaryTextureLoader=function(t){return console.warn("THREE.BinaryTextureLoader has been renamed to THREE.DataTextureLoader."),new dl(t)},t.Bone=ta,t.BooleanKeyframeTrack=Zo,t.BoundingBoxHelper=function(t,e){return console.warn("THREE.BoundingBoxHelper has been deprecated. Creating a THREE.BoxHelper instead."),new Mh(t,e)},t.Box2=eh,t.Box3=Pt,t.Box3Helper=class extends ya{constructor(t,e=16776960){const n=new Uint16Array([0,1,1,2,2,3,3,0,4,5,5,6,6,7,7,4,0,4,1,5,2,6,3,7]),i=new En;i.setIndex(new sn(n,1)),i.setAttribute("position",new mn([1,1,1,-1,1,1,-1,-1,1,1,-1,1,1,1,-1,-1,1,-1,-1,-1,-1,1,-1,-1],3)),super(i,new ca({color:e,toneMapped:!1})),this.box=t,this.type="Box3Helper",this.geometry.computeBoundingSphere()}updateMatrixWorld(t){const e=this.box;e.isEmpty()||(e.getCenter(this.position),e.getSize(this.scale),this.scale.multiplyScalar(.5),super.updateMatrixWorld(t))}},t.BoxBufferGeometry=qn,t.BoxGeometry=qn,t.BoxHelper=Mh,t.BufferAttribute=sn,t.BufferGeometry=En,t.BufferGeometryLoader=oc,t.ByteType=1010,t.Cache=rl,t.Camera=Qn,t.CameraHelper=class extends ya{constructor(t){const e=new En,n=new ca({color:16777215,vertexColors:!0,toneMapped:!1}),i=[],r=[],s={},a=new tn(16755200),o=new tn(16711680),l=new tn(43775),c=new tn(16777215),h=new tn(3355443);function u(t,e,n){d(t,n),d(e,n)}function d(t,e){i.push(0,0,0),r.push(e.r,e.g,e.b),void 0===s[t]&&(s[t]=[]),s[t].push(i.length/3-1)}u("n1","n2",a),u("n2","n4",a),u("n4","n3",a),u("n3","n1",a),u("f1","f2",a),u("f2","f4",a),u("f4","f3",a),u("f3","f1",a),u("n1","f1",a),u("n2","f2",a),u("n3","f3",a),u("n4","f4",a),u("p","n1",o),u("p","n2",o),u("p","n3",o),u("p","n4",o),u("u1","u2",l),u("u2","u3",l),u("u3","u1",l),u("c","t",c),u("p","c",h),u("cn1","cn2",h),u("cn3","cn4",h),u("cf1","cf2",h),u("cf3","cf4",h),e.setAttribute("position",new mn(i,3)),e.setAttribute("color",new mn(r,3)),super(e,n),this.type="CameraHelper",this.camera=t,this.camera.updateProjectionMatrix&&this.camera.updateProjectionMatrix(),this.matrix=t.matrixWorld,this.matrixAutoUpdate=!1,this.pointMap=s,this.update()}update(){const t=this.geometry,e=this.pointMap;_h.projectionMatrixInverse.copy(this.camera.projectionMatrixInverse),wh("c",e,t,_h,0,0,-1),wh("t",e,t,_h,0,0,1),wh("n1",e,t,_h,-1,-1,-1),wh("n2",e,t,_h,1,-1,-1),wh("n3",e,t,_h,-1,1,-1),wh("n4",e,t,_h,1,1,-1),wh("f1",e,t,_h,-1,-1,1),wh("f2",e,t,_h,1,-1,1),wh("f3",e,t,_h,-1,1,1),wh("f4",e,t,_h,1,1,1),wh("u1",e,t,_h,.7,1.1,-1),wh("u2",e,t,_h,-.7,1.1,-1),wh("u3",e,t,_h,0,2,-1),wh("cf1",e,t,_h,-1,0,1),wh("cf2",e,t,_h,1,0,1),wh("cf3",e,t,_h,0,-1,1),wh("cf4",e,t,_h,0,1,1),wh("cn1",e,t,_h,-1,0,-1),wh("cn2",e,t,_h,1,0,-1),wh("cn3",e,t,_h,0,-1,-1),wh("cn4",e,t,_h,0,1,-1),t.getAttribute("position").needsUpdate=!0}dispose(){this.geometry.dispose(),this.material.dispose()}},t.CanvasRenderer=function(){console.error("THREE.CanvasRenderer has been removed")},t.CanvasTexture=Ra,t.CatmullRomCurve3=bl,t.CineonToneMapping=3,t.CircleBufferGeometry=Pa,t.CircleGeometry=Pa,t.ClampToEdgeWrapping=u,t.Clock=bc,t.Color=tn,t.ColorKeyframeTrack=Jo,t.CompressedTexture=La,t.CompressedTextureLoader=class extends ol{constructor(t){super(t)}load(t,e,n,i){const r=this,s=[],a=new La,o=new cl(this.manager);o.setPath(this.path),o.setResponseType("arraybuffer"),o.setRequestHeader(this.requestHeader),o.setWithCredentials(r.withCredentials);let l=0;function c(c){o.load(t[c],(function(t){const n=r.parse(t,!0);s[c]={width:n.width,height:n.height,format:n.format,mipmaps:n.mipmaps},l+=1,6===l&&(1===n.mipmapCount&&(a.minFilter=g),a.image=s,a.format=n.format,a.needsUpdate=!0,e&&e(a))}),n,i)}if(Array.isArray(t))for(let e=0,n=t.length;e>16&32768,i=e>>12&2047;const r=e>>23&255;return r<103?n:r>142?(n|=31744,n|=(255==r?0:1)&&8388607&e,n):r<113?(i|=2048,n|=(i>>114-r)+(i>>113-r&1),n):(n|=r-112<<10|i>>1,n+=1&i,n)}},t.DecrementStencilOp=7683,t.DecrementWrapStencilOp=34056,t.DefaultLoadingManager=al,t.DepthFormat=A,t.DepthStencilFormat=L,t.DepthTexture=Ca,t.DirectionalLight=Kl,t.DirectionalLightHelper=class extends Ce{constructor(t,e,n){super(),this.light=t,this.light.updateMatrixWorld(),this.matrix=t.matrixWorld,this.matrixAutoUpdate=!1,this.color=n,void 0===e&&(e=1);let i=new En;i.setAttribute("position",new mn([-e,e,0,e,e,0,e,-e,0,-e,-e,0,-e,e,0],3));const r=new ca({fog:!1,toneMapped:!1});this.lightPlane=new fa(i,r),this.add(this.lightPlane),i=new En,i.setAttribute("position",new mn([0,0,0,0,0,1],3)),this.targetLine=new fa(i,r),this.add(this.targetLine),this.update()}dispose(){this.lightPlane.geometry.dispose(),this.lightPlane.material.dispose(),this.targetLine.geometry.dispose(),this.targetLine.material.dispose()}update(){gh.setFromMatrixPosition(this.light.matrixWorld),vh.setFromMatrixPosition(this.light.target.matrixWorld),yh.subVectors(vh,gh),this.lightPlane.lookAt(vh),void 0!==this.color?(this.lightPlane.material.color.set(this.color),this.targetLine.material.color.set(this.color)):(this.lightPlane.material.color.copy(this.light.color),this.targetLine.material.color.copy(this.light.color)),this.targetLine.lookAt(vh),this.targetLine.scale.z=yh.length()}},t.DiscreteInterpolant=Xo,t.DodecahedronBufferGeometry=Ba,t.DodecahedronGeometry=Ba,t.DoubleSide=2,t.DstAlphaFactor=206,t.DstColorFactor=208,t.DynamicBufferAttribute=function(t,e){return console.warn("THREE.DynamicBufferAttribute has been removed. Use new THREE.BufferAttribute().setUsage( THREE.DynamicDrawUsage ) instead."),new sn(t,e).setUsage(nt)},t.DynamicCopyUsage=35050,t.DynamicDrawUsage=nt,t.DynamicReadUsage=35049,t.EdgesGeometry=Ga,t.EdgesHelper=function(t,e){return console.warn("THREE.EdgesHelper has been removed. Use THREE.EdgesGeometry instead."),new ya(new Ga(t.geometry),new ca({color:void 0!==e?e:16777215}))},t.EllipseCurve=fl,t.EqualDepth=4,t.EqualStencilFunc=514,t.EquirectangularReflectionMapping=a,t.EquirectangularRefractionMapping=o,t.Euler=fe,t.EventDispatcher=rt,t.ExtrudeBufferGeometry=go,t.ExtrudeGeometry=go,t.FaceColors=1,t.FileLoader=cl,t.FlatShading=1,t.Float16BufferAttribute=pn,t.Float32Attribute=function(t,e){return console.warn("THREE.Float32Attribute has been removed. Use new THREE.Float32BufferAttribute() instead."),new mn(t,e)},t.Float32BufferAttribute=mn,t.Float64Attribute=function(t,e){return console.warn("THREE.Float64Attribute has been removed. Use new THREE.Float64BufferAttribute() instead."),new fn(t,e)},t.Float64BufferAttribute=fn,t.FloatType=b,t.Fog=Ss,t.FogExp2=Ms,t.Font=pc,t.FontLoader=class extends ol{constructor(t){super(t)}load(t,e,n,i){const r=this,s=new cl(this.manager);s.setPath(this.path),s.setRequestHeader(this.requestHeader),s.setWithCredentials(r.withCredentials),s.load(t,(function(t){let n;try{n=JSON.parse(t)}catch(e){console.warn("THREE.FontLoader: typeface.js support is being deprecated. Use typeface.json instead."),n=JSON.parse(t.substring(65,t.length-2))}const i=r.parse(n);e&&e(i)}),n,i)}parse(t){return new pc(t)}},t.FrontSide=0,t.Frustum=ai,t.GLBufferAttribute=Qc,t.GLSL1="100",t.GLSL3=it,t.GammaEncoding=Z,t.GreaterDepth=6,t.GreaterEqualDepth=5,t.GreaterEqualStencilFunc=518,t.GreaterStencilFunc=516,t.GridHelper=fh,t.Group=gs,t.HalfFloatType=M,t.HemisphereLight=Ol,t.HemisphereLightHelper=class extends Ce{constructor(t,e,n){super(),this.light=t,this.light.updateMatrixWorld(),this.matrix=t.matrixWorld,this.matrixAutoUpdate=!1,this.color=n;const i=new _o(e);i.rotateY(.5*Math.PI),this.material=new en({wireframe:!0,fog:!1,toneMapped:!1}),void 0===this.color&&(this.material.vertexColors=!0);const r=i.getAttribute("position"),s=new Float32Array(3*r.count);i.setAttribute("color",new sn(s,3)),this.add(new Wn(i,this.material)),this.update()}dispose(){this.children[0].geometry.dispose(),this.children[0].material.dispose()}update(){const t=this.children[0];if(void 0!==this.color)this.material.color.set(this.color);else{const e=t.geometry.getAttribute("color");ph.copy(this.light.color),mh.copy(this.light.groundColor);for(let t=0,n=e.count;t0){const n=new sl(e);r=new hl(n),r.setCrossOrigin(this.crossOrigin);for(let e=0,n=t.length;e\n\n\t\t\tvec3 getSample( float theta, vec3 axis ) {\n\n\t\t\t\tfloat cosTheta = cos( theta );\n\t\t\t\t// Rodrigues' axis-angle rotation\n\t\t\t\tvec3 sampleDirection = vOutputDirection * cosTheta\n\t\t\t\t\t+ cross( axis, vOutputDirection ) * sin( theta )\n\t\t\t\t\t+ axis * dot( axis, vOutputDirection ) * ( 1.0 - cosTheta );\n\n\t\t\t\treturn bilinearCubeUV( envMap, sampleDirection, mipInt );\n\n\t\t\t}\n\n\t\t\tvoid main() {\n\n\t\t\t\tvec3 axis = latitudinal ? poleAxis : cross( poleAxis, vOutputDirection );\n\n\t\t\t\tif ( all( equal( axis, vec3( 0.0 ) ) ) ) {\n\n\t\t\t\t\taxis = vec3( vOutputDirection.z, 0.0, - vOutputDirection.x );\n\n\t\t\t\t}\n\n\t\t\t\taxis = normalize( axis );\n\n\t\t\t\tgl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t\t\t\tgl_FragColor.rgb += weights[ 0 ] * getSample( 0.0, axis );\n\n\t\t\t\tfor ( int i = 1; i < n; i++ ) {\n\n\t\t\t\t\tif ( i >= samples ) {\n\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\t}\n\n\t\t\t\t\tfloat theta = dTheta * float( i );\n\t\t\t\t\tgl_FragColor.rgb += weights[ i ] * getSample( -1.0 * theta, axis );\n\t\t\t\t\tgl_FragColor.rgb += weights[ i ] * getSample( theta, axis );\n\n\t\t\t\t}\n\n\t\t\t\tgl_FragColor = linearToOutputTexel( gl_FragColor );\n\n\t\t\t}\n\t\t`,blending:0,depthTest:!1,depthWrite:!1})}(Ih),this._equirectShader=null,this._cubemapShader=null,this._compileMaterial(this._blurMaterial)}fromScene(t,e=0,n=.1,i=100){kh=this._renderer.getRenderTarget();const r=this._allocateTargets();return this._sceneToCubeUV(t,n,i,r),e>0&&this._blur(r,0,0,e),this._applyPMREM(r),this._cleanup(r),r}fromEquirectangular(t){return this._fromTexture(t)}fromCubemap(t){return this._fromTexture(t)}compileCubemapShader(){null===this._cubemapShader&&(this._cubemapShader=Kh(),this._compileMaterial(this._cubemapShader))}compileEquirectangularShader(){null===this._equirectShader&&(this._equirectShader=Qh(),this._compileMaterial(this._equirectShader))}dispose(){this._blurMaterial.dispose(),null!==this._cubemapShader&&this._cubemapShader.dispose(),null!==this._equirectShader&&this._equirectShader.dispose();for(let t=0;t2?Ch:0,Ch,Ch),o.setRenderTarget(i),u&&o.render(zh,r),o.render(t,r)}o.toneMapping=h,o.outputEncoding=c,o.autoClear=l}_textureToCubeUV(t,e){const n=this._renderer;t.isCubeTexture?null==this._cubemapShader&&(this._cubemapShader=Kh()):null==this._equirectShader&&(this._equirectShader=Qh());const i=t.isCubeTexture?this._cubemapShader:this._equirectShader,r=new Wn(Oh[0],i),s=i.uniforms;s.envMap.value=t,t.isCubeTexture||s.texelSize.value.set(1/t.image.width,1/t.image.height),s.inputEncoding.value=Nh[t.encoding],s.outputEncoding.value=Nh[e.texture.encoding],Jh(e,0,0,3*Ch,2*Ch),n.setRenderTarget(e),n.render(r,Fh)}_applyPMREM(t){const e=this._renderer,n=e.autoClear;e.autoClear=!1;for(let e=1;eIh&&console.warn(`sigmaRadians, ${r}, is too large and will clip, as it requested ${m} samples when the maximum is set to 20`);const f=[];let g=0;for(let t=0;t4?i-8+4:0),3*v,2*v),o.setRenderTarget(e),o.render(c,Fh)}},t.ParametricBufferGeometry=wo,t.ParametricGeometry=wo,t.Particle=function(t){return console.warn("THREE.Particle has been renamed to THREE.Sprite."),new Vs(t)},t.ParticleBasicMaterial=function(t){return console.warn("THREE.ParticleBasicMaterial has been renamed to THREE.PointsMaterial."),new _a(t)},t.ParticleSystem=function(t,e){return console.warn("THREE.ParticleSystem has been renamed to THREE.Points."),new Ta(t,e)},t.ParticleSystemMaterial=function(t){return console.warn("THREE.ParticleSystemMaterial has been renamed to THREE.PointsMaterial."),new _a(t)},t.Path=Bl,t.PerspectiveCamera=Kn,t.Plane=Ne,t.PlaneBufferGeometry=ci,t.PlaneGeometry=ci,t.PlaneHelper=class extends fa{constructor(t,e=1,n=16776960){const i=n,r=new En;r.setAttribute("position",new mn([1,-1,1,-1,1,1,-1,-1,1,1,1,1,-1,1,1,-1,-1,1,1,-1,1,1,1,1,0,0,1,0,0,0],3)),r.computeBoundingSphere(),super(r,new ca({color:i,toneMapped:!1})),this.type="PlaneHelper",this.plane=t,this.size=e;const s=new En;s.setAttribute("position",new mn([1,1,1,-1,1,1,-1,-1,1,1,1,1,-1,-1,1,1,-1,1],3)),s.computeBoundingSphere(),this.add(new Wn(s,new en({color:i,opacity:.2,transparent:!0,depthWrite:!1,toneMapped:!1})))}updateMatrixWorld(t){let e=-this.plane.constant;Math.abs(e)<1e-8&&(e=1e-8),this.scale.set(.5*this.size,.5*this.size,e),this.children[0].material.side=e<0?1:0,this.lookAt(this.plane.normal),super.updateMatrixWorld(t)}},t.PointCloud=function(t,e){return console.warn("THREE.PointCloud has been renamed to THREE.Points."),new Ta(t,e)},t.PointCloudMaterial=function(t){return console.warn("THREE.PointCloudMaterial has been renamed to THREE.PointsMaterial."),new _a(t)},t.PointLight=Zl,t.PointLightHelper=class extends Wn{constructor(t,e,n){super(new So(e,4,2),new en({wireframe:!0,fog:!1,toneMapped:!1})),this.light=t,this.light.updateMatrixWorld(),this.color=n,this.type="PointLightHelper",this.matrix=this.light.matrixWorld,this.matrixAutoUpdate=!1,this.update()}dispose(){this.geometry.dispose(),this.material.dispose()}update(){void 0!==this.color?this.material.color.set(this.color):this.material.color.copy(this.light.color)}},t.Points=Ta,t.PointsMaterial=_a,t.PolarGridHelper=class extends ya{constructor(t=10,e=16,n=8,i=64,r=4473924,s=8947848){r=new tn(r),s=new tn(s);const a=[],o=[];for(let n=0;n<=e;n++){const i=n/e*(2*Math.PI),l=Math.sin(i)*t,c=Math.cos(i)*t;a.push(0,0,0),a.push(l,0,c);const h=1&n?r:s;o.push(h.r,h.g,h.b),o.push(h.r,h.g,h.b)}for(let e=0;e<=n;e++){const l=1&e?r:s,c=t-t/n*e;for(let t=0;t + + + + + EarthBuild Logo Concepts — Brand Identity Showcase + + + + + + +
+
+
Brand Architecture & Exploration
+

EarthBuild Logo Candidates

+

Engineered to meet the 16px Favicon Test, 1-Color Monochrome Stamp Test, and 5-Second Whiteboard Test. Razor-sharp, high-velocity developer marks.

+
+ +
+ +
+
+
1. The Kinetic Hex
+ DECONSTRUCTED CONTAINER +
+

+ An isometric container hexagon decomposed into three dynamic diagonal velocity planes. Bridges container sandboxing with sub-second DAG execution. +

+
+
+ Dark Surface + The Kinetic Hex +
+
+ Light Surface + The Kinetic Hex +
+
+
+
+ + 16px Tab +
+
+ + 24px Nav +
+
+ + 32px Icon +
+
+ + 48px App +
+
+ + 64px Avatar +
+
+
+ Mark +
EarthBuild
+ Navbar Lockup Preview +
+
+ + +
+
+
2. The Prism Container
+ HERMETIC ISOMETRIC CUBE +
+

+ An ultra-clean isometric cube segmented by precision laser-cut channels. Represents the 3 pillars of EarthBuild: declared inputs (Cyan), isolated execution (Mint), and cached output (Emerald). +

+
+
+ Dark Surface + The Prism Container +
+
+ Light Surface + The Prism Container +
+
+
+
+ + 16px Tab +
+
+ + 24px Nav +
+
+ + 32px Icon +
+
+ + 48px App +
+
+ + 64px Avatar +
+
+
+ Mark +
EarthBuild
+ Navbar Lockup Preview +
+
+ + +
+
+
3. The Delta DAG
+ PARALLEL CONVERGENCE +
+

+ Two concurrent pipeline branches (Cyan & Mint) converging into a central forward spearhead (Emerald). Direct visual translation of multi-target graph compilation. +

+
+
+ Dark Surface + The Delta DAG +
+
+ Light Surface + The Delta DAG +
+
+
+
+ + 16px Tab +
+
+ + 24px Nav +
+
+ + 32px Icon +
+
+ + 48px App +
+
+ + 64px Avatar +
+
+
+ Mark +
EarthBuild
+ Navbar Lockup Preview +
+
+ + +
+
+
4. The Tectonic Vector
+ ACCELERATED DAG LAYERS +
+

+ Three precision-nested aerodynamic chevrons moving forward in unison. Minimalist, aggressive, and engineered for high-velocity build pipelines. +

+
+
+ Dark Surface + The Tectonic Vector +
+
+ Light Surface + The Tectonic Vector +
+
+
+
+ + 16px Tab +
+
+ + 24px Nav +
+
+ + 32px Icon +
+
+ + 48px App +
+
+ + 64px Avatar +
+
+
+ Mark +
EarthBuild
+ Navbar Lockup Preview +
+
+
+
+ + diff --git a/static/simple-logos.html b/static/simple-logos.html new file mode 100644 index 0000000..ab93dea --- /dev/null +++ b/static/simple-logos.html @@ -0,0 +1,375 @@ + + + + + + EarthBuild — Ultra-Simple Logo Candidates + + + + + + +
+
+
Radical Simplicity
+

Ultra-Simple Logo Marks

+

Earth seen from space, half in shadow, where the shadow is digital light in green & blue. Stripped down to essential, iconic vector geometry.

+
+ +
+ +
+
+
1. The Circuit Hemisphere
+ PURE ICONIC SILHOUETTE +
+

Left half is solid blue Earth; right half is 3 bold digital circuit tracks in electric cyan, emerald, and mint ending in glowing node terminals.

+
+
+ Dark + Circuit Hemisphere +
+
+ Light + Circuit Hemisphere +
+
+
+
+ + 16px Tab +
+
+ + 24px Nav +
+
+ + 32px App +
+
+ + 48px Icon +
+
+ +
+ + +
+
+
2. The Digital Meridian
+ GEODESIC COORDINATES +
+

Left half is solid oceanic blue; right half forms a clean planetary coordinate grid with glowing green and cyan intersection nodes.

+
+
+ Dark + Digital Meridian +
+
+ Light + Digital Meridian +
+
+
+
+ + 16px Tab +
+
+ + 24px Nav +
+
+ + 32px App +
+
+ + 48px Icon +
+
+ +
+ + +
+
+
3. Container Lattice
+ HERMETIC CUBE NODES +
+

Left half is clean celestial blue; right half is formed by two bold isometric container box planes (Cyan & Emerald) and a forward node.

+
+
+ Dark + Container Lattice +
+
+ Light + Container Lattice +
+
+
+
+ + 16px Tab +
+
+ + 24px Nav +
+
+ + 32px App +
+
+ + 48px Icon +
+
+ +
+ + +
+
+
4. Minimal Terminator
+ SAUL BASS / PAUL RAND STYLE +
+

Planetary ring enclosing a solid blue day side and two minimal, bold circuit lines (Cyan & Emerald) shining in the night shadow.

+
+
+ Dark + Minimal Terminator +
+
+ Light + Minimal Terminator +
+
+
+
+ + 16px Tab +
+
+ + 24px Nav +
+
+ + 32px App +
+
+ + 48px Icon +
+
+ +
+
+
+ + diff --git a/static/wireframes.html b/static/wireframes.html new file mode 100644 index 0000000..d310288 --- /dev/null +++ b/static/wireframes.html @@ -0,0 +1,503 @@ + + + + + + EarthBuild — 20 Logo Wireframe Explorations + + + + + + +
+
+
Structural Identity Phase
+

20 Wireframe Logo Candidates

+

Pure monochrome vector silhouettes. No colors, no gradients, no lighting effects. Choose the structural idea that has the right bones.

+
+ +
+ +
+
+ 01 + Container & Isolation +
+
+ + + + + + +
+
Structural Wireframe Cube
+

Pure isometric wireframe cube with a reinforced inner central vertex.

+
+ +
+
+ 02 + Container & Isolation +
+
+ + + + + +
+
Deconstructed Hex Shards
+

Three floating isometric facet planes with laser-cut negative-space channels.

+
+ +
+
+ 03 + Container & Isolation +
+
+ + + + +
+
The Build Chamber
+

Outer container chamber sheltering an inner suspended target core.

+
+ +
+
+ 04 + Container & Isolation +
+
+ + + + + + + +
+
Sandboxed Brackets
+

Four opposing precision corner brackets defining a secure container boundary.

+
+ +
+
+ 05 + Container & Isolation +
+
+ + + + + + + + + +
+
Nested Container Parity
+

Dual concentric isometric cubes representing the host environment and container runtime.

+
+ +
+
+ 06 + DAG & Pipeline +
+
+ + + + + + + +
+
Tri-Node Directed Graph
+

Classic Directed Acyclic Graph: two concurrent input nodes resolving into an apex target.

+
+ +
+
+ 07 + DAG & Pipeline +
+
+ + + + + + +
+
The Delta Convergence
+

Three parallel pipeline beams converging into an aerodynamic forward arrow.

+
+ +
+
+ 08 + DAG & Pipeline +
+
+ + + + + + + + + + +
+
Graph Split & Join
+

Pipeline splits into parallel paths, executes concurrently, then joins at build completion.

+
+ +
+
+ 09 + DAG & Pipeline +
+
+ + + + + +
+
Triple Speed Chevrons
+

Layered forward-moving chevrons symbolizing sub-second incremental builds.

+
+ +
+
+ 10 + DAG & Pipeline +
+
+ + + + + + +
+
Concurrency Lattice
+

Diamond lattice nodes executing across all CPU cores simultaneously.

+
+ +
+
+ 11 + Earth & Planetary +
+
+ + + + +
+
Planetary Horizon Block
+

A bold minimalist cube bisected by an expansive planetary horizon curve.

+
+ +
+
+ 12 + Earth & Planetary +
+
+ + + + + + + +
+
The Orbital Container
+

Isometric container encircled by a high-velocity planetary orbit loop.

+
+ +
+
+ 13 + Earth & Planetary +
+
+ + + + + + +
+
Geodesic Meridian Hex
+

Hexagonal container mapped with internal geodesic planetary meridians.

+
+ +
+
+ 14 + Earth & Planetary +
+
+ + + +
+
The Eclipse Aperture
+

Bold circle with an offset precision negative-space triangular cut.

+
+ +
+
+ 15 + Earth & Planetary +
+
+ + + + + +
+
Tectonic Plates
+

Three shifting geometric tectonic plates forming a dynamic hexagonal earth.

+
+ +
+
+ 16 + Monograms & Typographic +
+
+ + + + + + +
+
Cantilevered Tier 'E'
+

Three stacked architectural container tiers forming a geometric 'E'.

+
+ +
+
+ 17 + Monograms & Typographic +
+
+ + + + +
+
Parity Infinity Loop
+

Angular ribbon loop signifying guaranteed parity between local machines and CI runners.

+
+ +
+
+ 18 + Monograms & Typographic +
+
+ + + + + + + + + + +
+
Hexagonal 'EB' Monogram
+

Isometric cube where the left facet shapes an 'E' and the right facet shapes a 'B'.

+
+ +
+
+ 19 + Monograms & Typographic +
+
+ + + + + +
+
Stepped Build Plinth
+

Hermetic step-block foundation, communicating rock-solid reliability and structure.

+
+ +
+
+ 20 + Monograms & Typographic +
+
+ + + + + +
+
The Kinetic Arrowhead
+

Sharp forward delta sliced by parallel negative-space velocity channels.

+
+ +
+
+ + \ No newline at end of file diff --git a/tests/anchor-links.test.mjs b/tests/anchor-links.test.mjs new file mode 100644 index 0000000..33d6d23 --- /dev/null +++ b/tests/anchor-links.test.mjs @@ -0,0 +1,82 @@ +import { describe, it, before, after } from 'node:test'; +import assert from 'node:assert/strict'; +import { createStaticServer } from './helpers/server.mjs'; +import { launchBrowser } from './helpers/browser.mjs'; + +describe('Heading Anchor Links & Geometry E2E', () => { + let server; + let browser; + + before(async () => { + server = await createStaticServer(); + browser = await launchBrowser(); + }); + + after(async () => { + if (browser) await browser.close(); + if (server) await server.close(); + }); + + it('ensures anchor buttons do not overflow on mobile viewport (390px)', async () => { + const page = await browser.newPage(); + await page.setViewport({ width: 390, height: 844 }); + await page.goto(`${server.baseUrl}/`, { waitUntil: 'networkidle0' }); + + const mobileGeom = await page.evaluate(() => { + return Array.from(document.querySelectorAll('.heading-anchor-btn')).map((b) => { + const r = b.getBoundingClientRect(); + return { + href: b.getAttribute('href'), + right: Math.round(r.x + r.width), + windowWidth: window.innerWidth, + isOffscreen: r.x + r.width > window.innerWidth, + }; + }); + }); + + assert.ok(mobileGeom.length >= 7, 'Should have heading anchor buttons across main sections'); + const offscreenButtons = mobileGeom.filter((b) => b.isOffscreen); + assert.equal( + offscreenButtons.length, + 0, + `No buttons should be offscreen on mobile: ${JSON.stringify(offscreenButtons)}`, + ); + await page.close(); + }); + + it('copies URL to clipboard and triggers toast on click', async () => { + const page = await browser.newPage(); + await page.setViewport({ width: 1440, height: 900 }); + await page.goto(`${server.baseUrl}/`, { waitUntil: 'networkidle0' }); + + try { + const context = browser.defaultBrowserContext(); + await context.overridePermissions(server.baseUrl, ['clipboard-read', 'clipboard-write']); + } catch { + // Permission API may vary in headless + } + + const targetSection = '#problem-solution'; + const btn = await page.$(`a.heading-anchor-btn[href="${targetSection}"]`); + assert.ok(btn, `Button for ${targetSection} should exist`); + + await btn.click(); + + await page.waitForFunction( + (target) => { + const b = document.querySelector(`a.heading-anchor-btn[href="${target}"]`); + const toast = document.getElementById('copyToast'); + return b?.classList.contains('copied') || toast?.classList.contains('show'); + }, + {}, + targetSection, + ); + + const isCopied = await page.$eval(`a.heading-anchor-btn[href="${targetSection}"]`, (el) => + el.classList.contains('copied'), + ); + assert.ok(isCopied, "Button should have 'copied' class after click"); + + await page.close(); + }); +}); diff --git a/tests/helpers/browser.mjs b/tests/helpers/browser.mjs new file mode 100644 index 0000000..38e825a --- /dev/null +++ b/tests/helpers/browser.mjs @@ -0,0 +1,39 @@ +import puppeteer from 'puppeteer-core'; +import fs from 'node:fs'; + +export function findChromePath() { + if (process.env.CHROME_PATH && fs.existsSync(process.env.CHROME_PATH)) { + return process.env.CHROME_PATH; + } + + const macPaths = [ + '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', + '/Applications/Chromium.app/Contents/MacOS/Chromium', + ]; + for (const p of macPaths) { + if (fs.existsSync(p)) return p; + } + + const linuxPaths = [ + '/usr/bin/chromium-browser', + '/usr/bin/chromium', + '/usr/bin/google-chrome-stable', + '/usr/bin/google-chrome', + ]; + for (const p of linuxPaths) { + if (fs.existsSync(p)) return p; + } + + throw new Error( + 'Could not find a Chrome or Chromium executable. Set the CHROME_PATH environment variable.', + ); +} + +export async function launchBrowser() { + const executablePath = findChromePath(); + return puppeteer.launch({ + headless: true, + executablePath, + args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-gpu'], + }); +} diff --git a/tests/helpers/server.mjs b/tests/helpers/server.mjs new file mode 100644 index 0000000..d09abe0 --- /dev/null +++ b/tests/helpers/server.mjs @@ -0,0 +1,66 @@ +import http from 'node:http'; +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const PUBLIC_DIR = path.resolve(__dirname, '../../public'); + +const MIME_TYPES = { + '.html': 'text/html; charset=utf-8', + '.css': 'text/css; charset=utf-8', + '.js': 'application/javascript; charset=utf-8', + '.json': 'application/json; charset=utf-8', + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.svg': 'image/svg+xml', + '.webp': 'image/webp', + '.cast': 'text/plain; charset=utf-8', + '.ico': 'image/x-icon', +}; + +/** + * Creates an ephemeral static HTTP server serving the compiled Hugo public/ directory. + */ +export function createStaticServer(publicDir = PUBLIC_DIR) { + const server = http.createServer((req, res) => { + const url = new URL(req.url, `http://${req.headers.host || 'localhost'}`); + let filePath = path.join(publicDir, decodeURIComponent(url.pathname)); + + if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) { + filePath = path.join(filePath, 'index.html'); + } else if (!fs.existsSync(filePath) && fs.existsSync(`${filePath}.html`)) { + filePath = `${filePath}.html`; + } + + if (!fs.existsSync(filePath) || fs.statSync(filePath).isDirectory()) { + res.writeHead(404, { 'Content-Type': 'text/plain' }); + res.end('404 Not Found'); + return; + } + + const ext = path.extname(filePath).toLowerCase(); + const contentType = MIME_TYPES[ext] || 'application/octet-stream'; + + res.writeHead(200, { + 'Content-Type': contentType, + 'Cache-Control': 'no-store', + }); + fs.createReadStream(filePath).pipe(res); + }); + + return new Promise((resolve, reject) => { + server.listen(0, '127.0.0.1', () => { + const port = server.address().port; + const baseUrl = `http://127.0.0.1:${port}`; + resolve({ + server, + port, + baseUrl, + close: () => new Promise((res) => server.close(res)), + }); + }); + server.on('error', reject); + }); +} diff --git a/tests/install-and-nav.test.mjs b/tests/install-and-nav.test.mjs new file mode 100644 index 0000000..2a7377a --- /dev/null +++ b/tests/install-and-nav.test.mjs @@ -0,0 +1,79 @@ +import { describe, it, before, after } from 'node:test'; +import assert from 'node:assert/strict'; +import { createStaticServer } from './helpers/server.mjs'; +import { launchBrowser } from './helpers/browser.mjs'; + +describe('Install Tabs & Mobile Navigation E2E', () => { + let server; + let browser; + + before(async () => { + server = await createStaticServer(); + browser = await launchBrowser(); + }); + + after(async () => { + if (browser) await browser.close(); + if (server) await server.close(); + }); + + it('switches install command tabs (brew -> curl -> nix)', async () => { + const page = await browser.newPage(); + await page.setViewport({ width: 1440, height: 900 }); + await page.goto(`${server.baseUrl}/`, { waitUntil: 'networkidle0' }); + + // Initially Homebrew snippet is shown + const initialCode = await page.$eval('#installCodeSnippet', (el) => el.textContent.trim()); + assert.ok(initialCode.includes('brew install'), 'Default snippet should be brew install'); + + // Click cURL / Shell tab + const curlTab = await page.$('.install-tab-btn[data-install-type="curl"]'); + assert.ok(curlTab, 'Curl tab should exist'); + await curlTab.click(); + + await page.waitForFunction(() => { + const code = document.getElementById('installCodeSnippet'); + return code && code.textContent.includes('curl'); + }); + + const curlCode = await page.$eval('#installCodeSnippet', (el) => el.textContent.trim()); + assert.ok(curlCode.includes('curl -fsSL'), 'Snippet should update to curl command'); + + await page.close(); + }); + + it('toggles mobile navigation drawer on compact viewport', async () => { + const page = await browser.newPage(); + await page.setViewport({ width: 390, height: 844 }); + await page.goto(`${server.baseUrl}/`, { waitUntil: 'networkidle0' }); + + const toggleBtn = await page.$('#mobileMenuToggle'); + assert.ok(toggleBtn, 'Mobile hamburger toggle button should exist'); + + // Click toggle to open drawer + await toggleBtn.click(); + + await page.waitForFunction(() => { + const drawer = document.getElementById('mobileNavDrawer'); + return drawer && drawer.classList.contains('open'); + }); + + const isOpen = await page.$eval('#mobileNavDrawer', (el) => el.classList.contains('open')); + assert.ok(isOpen, 'Mobile drawer should have open class'); + + // Click a link inside mobile drawer + const drawerLink = await page.$('#mobileNavDrawer a'); + assert.ok(drawerLink, 'Link in mobile drawer should exist'); + await drawerLink.click(); + + await page.waitForFunction(() => { + const drawer = document.getElementById('mobileNavDrawer'); + return !drawer || !drawer.classList.contains('open'); + }); + + const isClosed = await page.$eval('#mobileNavDrawer', (el) => !el.classList.contains('open')); + assert.ok(isClosed, 'Mobile drawer should close after link click'); + + await page.close(); + }); +}); diff --git a/tests/migration-tabs.test.mjs b/tests/migration-tabs.test.mjs new file mode 100644 index 0000000..ff339b5 --- /dev/null +++ b/tests/migration-tabs.test.mjs @@ -0,0 +1,124 @@ +import { describe, it, before, after } from 'node:test'; +import assert from 'node:assert/strict'; +import { createStaticServer } from './helpers/server.mjs'; +import { launchBrowser } from './helpers/browser.mjs'; + +describe('Migration Guide & Comparison Tabs E2E', () => { + let server; + let browser; + let page; + + before(async () => { + server = await createStaticServer(); + browser = await launchBrowser(); + page = await browser.newPage(); + await page.setViewport({ width: 1440, height: 900 }); + await page.goto(`${server.baseUrl}/#migration`, { waitUntil: 'networkidle0' }); + }); + + after(async () => { + if (page) await page.close(); + if (browser) await browser.close(); + if (server) await server.close(); + }); + + it('renders migration section with all language and tool tabs', async () => { + const langTabs = await page.$$eval('#migLangTabs .race-tab-btn', (els) => + els.map((e) => e.dataset.lang), + ); + assert.deepEqual(langTabs, ['python', 'js', 'typescript', 'java', 'cpp', 'go', 'rust', 'zig']); + + const toolTabs = await page.$$eval('.migration-tab-btn', (els) => + els.map((e) => e.dataset.target), + ); + assert.deepEqual(toolTabs, [ + 'mig-dockerfile', + 'mig-bake', + 'mig-makefile', + 'mig-dagger', + 'mig-gha', + ]); + }); + + it('updates title and code when selecting Rust + From Dagger', async () => { + // Select Rust + await page.click('#migLangTabs .race-tab-btn[data-lang="rust"]'); + // Select Dagger tool tab + await page.click('.migration-tab-btn[data-target="mig-dagger"]'); + + await page.waitForFunction(() => { + const title = document.querySelector('#mig-dagger .mig-legacy-title'); + return title && title.textContent.includes('Dagger (Go SDK)'); + }); + + const legacyTitle = await page.$eval('#mig-dagger .mig-legacy-title', (el) => + el.textContent.trim(), + ); + const legacyCode = await page.$eval('#mig-dagger .mig-legacy-code', (el) => el.textContent); + const earthfileCode = await page.$eval( + '#mig-dagger .mig-earthfile-code', + (el) => el.textContent, + ); + + assert.equal(legacyTitle, 'Dagger (Go SDK)'); + assert.ok(legacyCode.includes('cargo'), 'Dagger code should orchestrate cargo'); + assert.ok(legacyCode.includes('rust:1.85-alpine3.24'), 'Should pin rust:1.85-alpine3.24'); + assert.ok(legacyCode.includes('Test('), 'Should expose Test target'); + assert.ok(legacyCode.includes('Build('), 'Should expose Build target'); + assert.ok(legacyCode.includes('Publish('), 'Should expose Publish target'); + assert.ok(earthfileCode.includes('VERSION 0.8'), 'Earthfile should use VERSION 0.8'); + assert.ok( + earthfileCode.includes('rust:1.85-alpine3.24'), + 'Earthfile should pin rust:1.85-alpine3.24', + ); + }); + + it('updates title when selecting Zig + From Dagger', async () => { + await page.click('#migLangTabs .race-tab-btn[data-lang="zig"]'); + + await page.waitForFunction(() => { + const title = document.querySelector('#mig-dagger .mig-legacy-title'); + return title && title.textContent.includes('Zig'); + }); + + const legacyTitle = await page.$eval('#mig-dagger .mig-legacy-title', (el) => + el.textContent.trim(), + ); + assert.ok(legacyTitle.includes('No Native Zig SDK'), 'Should note absence of native Zig SDK'); + }); + + it('supports multi-file tab switching in Docker Bake', async () => { + await page.click('#migLangTabs .race-tab-btn[data-lang="go"]'); + await page.click('.migration-tab-btn[data-target="mig-bake"]'); + + // Wait for multi-file tab container to become visible + await page.waitForSelector('#mig-bake .mig-legacy-file-tabs .window-file-tab-btn'); + + const fileTabs = await page.$$eval( + '#mig-bake .mig-legacy-file-tabs .window-file-tab-btn', + (els) => els.map((e) => e.textContent.trim()), + ); + assert.ok(fileTabs.length >= 2, 'Should have at least 2 file tabs'); + assert.ok(fileTabs.includes('docker-bake.hcl'), 'Should include docker-bake.hcl'); + assert.ok(fileTabs.includes('Dockerfile'), 'Should include Dockerfile'); + + // Initially docker-bake.hcl is active + let activeCode = await page.$eval('#mig-bake .mig-legacy-code', (el) => el.textContent); + assert.ok( + activeCode.includes('target') || activeCode.includes('group'), + 'Should show HCL code', + ); + + // Click the second file tab (Dockerfile) + const secondTab = (await page.$$('#mig-bake .mig-legacy-file-tabs .window-file-tab-btn'))[1]; + await secondTab.click(); + + await page.waitForFunction(() => { + const code = document.querySelector('#mig-bake .mig-legacy-code'); + return code && code.textContent.includes('FROM'); + }); + + activeCode = await page.$eval('#mig-bake .mig-legacy-code', (el) => el.textContent); + assert.ok(activeCode.includes('FROM'), 'Switched code should show Dockerfile'); + }); +}); diff --git a/tests/race-runner.test.mjs b/tests/race-runner.test.mjs new file mode 100644 index 0000000..8bd1269 --- /dev/null +++ b/tests/race-runner.test.mjs @@ -0,0 +1,65 @@ +import { describe, it, before, after } from 'node:test'; +import assert from 'node:assert/strict'; +import { createStaticServer } from './helpers/server.mjs'; +import { launchBrowser } from './helpers/browser.mjs'; + +describe('Race Runner 3-Way Terminal E2E', () => { + let server; + let browser; + let page; + + before(async () => { + server = await createStaticServer(); + browser = await launchBrowser(); + page = await browser.newPage(); + await page.setViewport({ width: 1440, height: 900 }); + await page.goto(`${server.baseUrl}/#code-explorer`, { waitUntil: 'networkidle0' }); + }); + + after(async () => { + if (page) await page.close(); + if (browser) await browser.close(); + if (server) await server.close(); + }); + + it('renders all language tabs in the race runner', async () => { + const langs = await page.$$eval('#raceLangTabs .race-tab-btn', (els) => + els.map((e) => e.dataset.lang), + ); + assert.deepEqual(langs, ['python', 'js', 'typescript', 'java', 'cpp', 'go', 'rust', 'zig']); + }); + + it('updates metrics and Earthfile preview when switching language tab', async () => { + // Select Rust + await page.click('#raceLangTabs .race-tab-btn[data-lang="rust"]'); + + await page.waitForFunction(() => { + const activeTab = document.querySelector('#raceLangTabs .race-tab-btn.active'); + return activeTab && activeTab.getAttribute('data-lang') === 'rust'; + }); + + const rustTabActive = await page.$eval('#raceLangTabs .race-tab-btn[data-lang="rust"]', (el) => + el.classList.contains('active'), + ); + assert.ok(rustTabActive, 'Rust tab should be active'); + + // Verify Earthfile code preview updates to Rust + await page.waitForFunction(() => { + const code = document.getElementById('raceCodePane'); + return code && code.textContent.includes('rust'); + }); + + const codeText = await page.$eval('#raceCodePane', (el) => el.textContent); + assert.ok(codeText.includes('rust:1.85-alpine'), 'Should display Rust Earthfile snippet'); + }); + + it('renders the 3 execution windows (clean, incremental, zero-work)', async () => { + const windows = await page.$$('.cr-feature-card'); + assert.equal(windows.length, 3, 'Should render 3 parallel terminal windows'); + + const hasCold = await page.$('#coldWindow'); + const hasEdit = await page.$('#editWindow'); + const hasCached = await page.$('#cachedWindow'); + assert.ok(hasCold && hasEdit && hasCached, 'All three execution windows must exist'); + }); +}); diff --git a/tests/roi-calculator.test.mjs b/tests/roi-calculator.test.mjs new file mode 100644 index 0000000..db9f068 --- /dev/null +++ b/tests/roi-calculator.test.mjs @@ -0,0 +1,105 @@ +import { describe, it, before, after } from 'node:test'; +import assert from 'node:assert/strict'; +import { createStaticServer } from './helpers/server.mjs'; +import { launchBrowser } from './helpers/browser.mjs'; + +describe('ROI Calculator E2E', () => { + let server; + let browser; + let page; + + before(async () => { + server = await createStaticServer(); + browser = await launchBrowser(); + page = await browser.newPage(); + await page.setViewport({ width: 1440, height: 900 }); + }); + + after(async () => { + if (page) await page.close(); + if (browser) await browser.close(); + if (server) await server.close(); + }); + + it('renders default values and recalculates on input changes', async () => { + await page.goto(`${server.baseUrl}/#calculator`, { waitUntil: 'networkidle0' }); + + const initialHours = await page.$eval('#roiHoursSaved', (el) => el.textContent.trim()); + const initialRunnerMins = await page.$eval('#roiRunnerMinutesSaved', (el) => + el.textContent.trim(), + ); + assert.ok(initialHours.includes('hrs'), 'Hours saved should be displayed'); + assert.ok(initialRunnerMins.includes('mins'), 'Runner minutes saved should be displayed'); + + // Change slider values to team=40, builds=15, mins=25 + await page.$eval('#roiTeamSize', (el) => { + el.value = '40'; + el.dispatchEvent(new Event('input', { bubbles: true })); + }); + await page.$eval('#roiBuildsPerDay', (el) => { + el.value = '15'; + el.dispatchEvent(new Event('input', { bubbles: true })); + }); + await page.$eval('#roiMinutesSaved', (el) => { + el.value = '25'; + el.dispatchEvent(new Event('input', { bubbles: true })); + }); + + // Wait for DOM update + await page.waitForFunction( + (oldHours, oldMins) => { + const h = document.getElementById('roiHoursSaved')?.textContent?.trim(); + const m = document.getElementById('roiRunnerMinutesSaved')?.textContent?.trim(); + return h !== oldHours && m !== oldMins; + }, + {}, + initialHours, + initialRunnerMins, + ); + + const updatedHours = await page.$eval('#roiHoursSaved', (el) => el.textContent.trim()); + const updatedRunnerMins = await page.$eval('#roiRunnerMinutesSaved', (el) => + el.textContent.trim(), + ); + assert.notEqual(updatedHours, initialHours, 'Hours should change'); + assert.notEqual(updatedRunnerMins, initialRunnerMins, 'Runner minutes should change'); + }); + + it('populates inputs when navigated with URL query parameters', async () => { + await page.goto(`${server.baseUrl}/?team=60&builds=10&mins=20#calculator`, { + waitUntil: 'networkidle0', + }); + + const teamVal = await page.$eval('#roiTeamSize', (el) => el.value); + const buildsVal = await page.$eval('#roiBuildsPerDay', (el) => el.value); + const minsVal = await page.$eval('#roiMinutesSaved', (el) => el.value); + + assert.equal(teamVal, '60'); + assert.equal(buildsVal, '10'); + assert.equal(minsVal, '20'); + }); + + it('triggers copy and shows toast when clicking Share Calculations', async () => { + await page.goto(`${server.baseUrl}/#calculator`, { waitUntil: 'networkidle0' }); + + // Grant clipboard permissions in browser context + try { + const context = browser.defaultBrowserContext(); + await context.overridePermissions(server.baseUrl, ['clipboard-read', 'clipboard-write']); + } catch { + // Permission API may vary across headless environments + } + + await page.click('#roiCopyShareBtn'); + + await page.waitForFunction(() => { + const btn = document.getElementById('roiCopyShareBtn'); + return btn && btn.classList.contains('copied'); + }); + + const btnHasCopiedClass = await page.$eval('#roiCopyShareBtn', (el) => + el.classList.contains('copied'), + ); + assert.ok(btnHasCopiedClass, "Share button should have 'copied' class"); + }); +});