From 98073dc636a56e8ef20c4e90d898e733b40f4ab6 Mon Sep 17 00:00:00 2001 From: Germaine Brown Date: Sun, 9 Aug 2026 23:49:45 -0400 Subject: [PATCH] feat: initial itpp-docs setup with MkDocs Material - mkdocs.yml with dark slate theme, nav for 12 ITPP projects - build-docs.sh aggregates docs from all project repos - .gitea/workflows/docs-publish.yml for nightly rebuild+deploy - README and CHANGELOG for the itpp-docs repo itself - docs-source/ populated from all 12 repos - site/ ready for deployment to docs.itpropartner.com --- .gitea/workflows/docs-publish.yml | 20 + CHANGELOG.md | 8 + README.md | 44 +- build-docs.sh | 113 + docs-source/apex-track/CHANGELOG.md | 5 + docs-source/apex-track/index.md | 24 + docs-source/boxpilot/CHANGELOG.md | 5 + docs-source/boxpilot/index.md | 24 + docs-source/fleettracker360/CHANGELOG.md | 23 + docs-source/fleettracker360/index.md | 40 + docs-source/homelab/CHANGELOG.md | 18 + docs-source/homelab/index.md | 75 + docs-source/index.md | 26 + docs-source/itpp-infrastructure/CHANGELOG.md | 6 + .../app2-caddyfile-audit-2026-07-21.md | 27 + .../backup-restore/ARCHITECTURE.md | 158 + .../backup-restore/CHANGELOG.md | 30 + .../backup-restore/README.md | 50 + .../cost-control-rollout-2026-07-24.md | 37 + docs-source/itpp-infrastructure/index.md | 278 + .../itpp-infrastructure/key-inventory.md | 252 + .../irs-name-change-letter-2026-07-21.txt | 29 + .../itpp-infrastructure/model-chain.md | 18 + .../ops-portal/ARCHITECTURE.md | 112 + .../ops-portal/CHANGELOG.md | 39 + .../itpp-infrastructure/ops-portal/README.md | 55 + .../itpp-infrastructure/project-log.md | 68 + .../projects-master-readme.md | 10 + .../super-search-cf-bypass.md | 64 + docs-source/itpp-standards/CHANGELOG.md | 8 + docs-source/itpp-standards/index.md | 40 + docs-source/launchcheck/CHANGELOG.md | 7 + .../launchcheck/competitive-analysis.md | 670 ++ docs-source/launchcheck/index.md | 34 + .../launchcheck-business-proposal.md | 561 ++ docs-source/osint-tool/CHANGELOG.md | 13 + docs-source/osint-tool/index.md | 33 + docs-source/scripts/CHANGELOG.md | 19 + docs-source/scripts/index.md | 157 + docs-source/shark-game/CHANGELOG.md | 23 + docs-source/shark-game/index.md | 19 + docs-source/transitpin/CHANGELOG.md | 45 + docs-source/transitpin/index.md | 42 + docs-source/verdicttank/CHANGELOG.md | 48 + docs-source/verdicttank/index.md | 70 + mkdocs.yml | 47 + site/404.html | 764 ++ site/apex-track/CHANGELOG/index.html | 815 ++ site/apex-track/index.html | 943 +++ site/assets/images/favicon.png | Bin 0 -> 1870 bytes .../assets/javascripts/bundle.d7400e89.min.js | 16 + .../javascripts/bundle.d7400e89.min.js.map | 7 + .../javascripts/lunr/min/lunr.ar.min.js | 1 + .../javascripts/lunr/min/lunr.da.min.js | 18 + .../javascripts/lunr/min/lunr.de.min.js | 18 + .../javascripts/lunr/min/lunr.du.min.js | 18 + .../javascripts/lunr/min/lunr.el.min.js | 1 + .../javascripts/lunr/min/lunr.es.min.js | 18 + .../javascripts/lunr/min/lunr.fi.min.js | 18 + .../javascripts/lunr/min/lunr.fr.min.js | 18 + .../javascripts/lunr/min/lunr.he.min.js | 1 + .../javascripts/lunr/min/lunr.hi.min.js | 1 + .../javascripts/lunr/min/lunr.hu.min.js | 18 + .../javascripts/lunr/min/lunr.hy.min.js | 1 + .../javascripts/lunr/min/lunr.it.min.js | 18 + .../javascripts/lunr/min/lunr.ja.min.js | 1 + .../javascripts/lunr/min/lunr.jp.min.js | 1 + .../javascripts/lunr/min/lunr.kn.min.js | 1 + .../javascripts/lunr/min/lunr.ko.min.js | 1 + .../javascripts/lunr/min/lunr.multi.min.js | 1 + .../javascripts/lunr/min/lunr.nl.min.js | 18 + .../javascripts/lunr/min/lunr.no.min.js | 18 + .../javascripts/lunr/min/lunr.pt.min.js | 18 + .../javascripts/lunr/min/lunr.ro.min.js | 18 + .../javascripts/lunr/min/lunr.ru.min.js | 18 + .../javascripts/lunr/min/lunr.sa.min.js | 1 + .../lunr/min/lunr.stemmer.support.min.js | 1 + .../javascripts/lunr/min/lunr.sv.min.js | 18 + .../javascripts/lunr/min/lunr.ta.min.js | 1 + .../javascripts/lunr/min/lunr.te.min.js | 1 + .../javascripts/lunr/min/lunr.th.min.js | 1 + .../javascripts/lunr/min/lunr.tr.min.js | 18 + .../javascripts/lunr/min/lunr.vi.min.js | 1 + .../javascripts/lunr/min/lunr.zh.min.js | 1 + site/assets/javascripts/lunr/tinyseg.js | 206 + site/assets/javascripts/lunr/wordcut.js | 6708 +++++++++++++++++ .../workers/search.2c215733.min.js | 42 + .../workers/search.2c215733.min.js.map | 7 + site/assets/stylesheets/main.ec1eaa64.min.css | 1 + .../stylesheets/main.ec1eaa64.min.css.map | 1 + .../stylesheets/palette.ab4e12ef.min.css | 1 + .../stylesheets/palette.ab4e12ef.min.css.map | 1 + site/boxpilot/CHANGELOG/index.html | 815 ++ site/boxpilot/index.html | 943 +++ site/fleettracker360/CHANGELOG/index.html | 874 +++ site/fleettracker360/index.html | 1003 +++ site/homelab/CHANGELOG/index.html | 869 +++ site/homelab/index.html | 1268 ++++ site/index.html | 972 +++ site/itpp-infrastructure/CHANGELOG/index.html | 816 ++ .../index.html | 858 +++ .../backup-restore/ARCHITECTURE/index.html | 1126 +++ .../backup-restore/CHANGELOG/index.html | 893 +++ .../backup-restore/index.html | 949 +++ .../index.html | 923 +++ site/itpp-infrastructure/index.html | 2026 +++++ .../key-inventory/index.html | 1805 +++++ .../irs-name-change-letter-2026-07-21.txt | 29 + .../model-chain/index.html | 872 +++ .../ops-portal/ARCHITECTURE/index.html | 1064 +++ .../ops-portal/CHANGELOG/index.html | 902 +++ .../itpp-infrastructure/ops-portal/index.html | 1015 +++ .../project-log/index.html | 1042 +++ .../projects-master-readme/index.html | 801 ++ .../super-search-cf-bypass/index.html | 1013 +++ site/itpp-standards/CHANGELOG/index.html | 818 ++ site/itpp-standards/index.html | 981 +++ site/launchcheck/CHANGELOG/index.html | 817 ++ .../competitive-analysis/index.html | 2520 +++++++ site/launchcheck/index.html | 973 +++ .../launchcheck-business-proposal/index.html | 2658 +++++++ site/osint-tool/CHANGELOG/index.html | 845 +++ site/osint-tool/index.html | 974 +++ site/scripts/CHANGELOG/index.html | 851 +++ site/scripts/index.html | 2005 +++++ site/search/search_index.json | 1 + site/shark-game/CHANGELOG/index.html | 905 +++ site/shark-game/index.html | 918 +++ site/sitemap.xml | 163 + site/sitemap.xml.gz | Bin 0 -> 534 bytes site/transitpin/CHANGELOG/index.html | 928 +++ site/transitpin/index.html | 1010 +++ site/verdicttank/CHANGELOG/index.html | 911 +++ site/verdicttank/index.html | 1087 +++ 134 files changed, 55534 insertions(+), 2 deletions(-) create mode 100644 .gitea/workflows/docs-publish.yml create mode 100644 CHANGELOG.md create mode 100755 build-docs.sh create mode 100644 docs-source/apex-track/CHANGELOG.md create mode 100644 docs-source/apex-track/index.md create mode 100644 docs-source/boxpilot/CHANGELOG.md create mode 100644 docs-source/boxpilot/index.md create mode 100644 docs-source/fleettracker360/CHANGELOG.md create mode 100644 docs-source/fleettracker360/index.md create mode 100644 docs-source/homelab/CHANGELOG.md create mode 100644 docs-source/homelab/index.md create mode 100644 docs-source/index.md create mode 100644 docs-source/itpp-infrastructure/CHANGELOG.md create mode 100644 docs-source/itpp-infrastructure/app2-caddyfile-audit-2026-07-21.md create mode 100644 docs-source/itpp-infrastructure/backup-restore/ARCHITECTURE.md create mode 100644 docs-source/itpp-infrastructure/backup-restore/CHANGELOG.md create mode 100644 docs-source/itpp-infrastructure/backup-restore/README.md create mode 100644 docs-source/itpp-infrastructure/cost-control-rollout-2026-07-24.md create mode 100644 docs-source/itpp-infrastructure/index.md create mode 100644 docs-source/itpp-infrastructure/key-inventory.md create mode 100644 docs-source/itpp-infrastructure/legal/irs-name-change-letter-2026-07-21.txt create mode 100644 docs-source/itpp-infrastructure/model-chain.md create mode 100644 docs-source/itpp-infrastructure/ops-portal/ARCHITECTURE.md create mode 100644 docs-source/itpp-infrastructure/ops-portal/CHANGELOG.md create mode 100644 docs-source/itpp-infrastructure/ops-portal/README.md create mode 100644 docs-source/itpp-infrastructure/project-log.md create mode 100644 docs-source/itpp-infrastructure/projects-master-readme.md create mode 100644 docs-source/itpp-infrastructure/super-search-cf-bypass.md create mode 100644 docs-source/itpp-standards/CHANGELOG.md create mode 100644 docs-source/itpp-standards/index.md create mode 100644 docs-source/launchcheck/CHANGELOG.md create mode 100644 docs-source/launchcheck/competitive-analysis.md create mode 100644 docs-source/launchcheck/index.md create mode 100644 docs-source/launchcheck/launchcheck-business-proposal.md create mode 100644 docs-source/osint-tool/CHANGELOG.md create mode 100644 docs-source/osint-tool/index.md create mode 100644 docs-source/scripts/CHANGELOG.md create mode 100644 docs-source/scripts/index.md create mode 100644 docs-source/shark-game/CHANGELOG.md create mode 100644 docs-source/shark-game/index.md create mode 100644 docs-source/transitpin/CHANGELOG.md create mode 100644 docs-source/transitpin/index.md create mode 100644 docs-source/verdicttank/CHANGELOG.md create mode 100644 docs-source/verdicttank/index.md create mode 100644 mkdocs.yml create mode 100644 site/404.html create mode 100644 site/apex-track/CHANGELOG/index.html create mode 100644 site/apex-track/index.html create mode 100644 site/assets/images/favicon.png create mode 100644 site/assets/javascripts/bundle.d7400e89.min.js create mode 100644 site/assets/javascripts/bundle.d7400e89.min.js.map create mode 100644 site/assets/javascripts/lunr/min/lunr.ar.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.da.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.de.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.du.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.el.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.es.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.fi.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.fr.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.he.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.hi.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.hu.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.hy.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.it.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.ja.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.jp.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.kn.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.ko.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.multi.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.nl.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.no.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.pt.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.ro.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.ru.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.sa.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.stemmer.support.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.sv.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.ta.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.te.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.th.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.tr.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.vi.min.js create mode 100644 site/assets/javascripts/lunr/min/lunr.zh.min.js create mode 100644 site/assets/javascripts/lunr/tinyseg.js create mode 100644 site/assets/javascripts/lunr/wordcut.js create mode 100644 site/assets/javascripts/workers/search.2c215733.min.js create mode 100644 site/assets/javascripts/workers/search.2c215733.min.js.map create mode 100644 site/assets/stylesheets/main.ec1eaa64.min.css create mode 100644 site/assets/stylesheets/main.ec1eaa64.min.css.map create mode 100644 site/assets/stylesheets/palette.ab4e12ef.min.css create mode 100644 site/assets/stylesheets/palette.ab4e12ef.min.css.map create mode 100644 site/boxpilot/CHANGELOG/index.html create mode 100644 site/boxpilot/index.html create mode 100644 site/fleettracker360/CHANGELOG/index.html create mode 100644 site/fleettracker360/index.html create mode 100644 site/homelab/CHANGELOG/index.html create mode 100644 site/homelab/index.html create mode 100644 site/index.html create mode 100644 site/itpp-infrastructure/CHANGELOG/index.html create mode 100644 site/itpp-infrastructure/app2-caddyfile-audit-2026-07-21/index.html create mode 100644 site/itpp-infrastructure/backup-restore/ARCHITECTURE/index.html create mode 100644 site/itpp-infrastructure/backup-restore/CHANGELOG/index.html create mode 100644 site/itpp-infrastructure/backup-restore/index.html create mode 100644 site/itpp-infrastructure/cost-control-rollout-2026-07-24/index.html create mode 100644 site/itpp-infrastructure/index.html create mode 100644 site/itpp-infrastructure/key-inventory/index.html create mode 100644 site/itpp-infrastructure/legal/irs-name-change-letter-2026-07-21.txt create mode 100644 site/itpp-infrastructure/model-chain/index.html create mode 100644 site/itpp-infrastructure/ops-portal/ARCHITECTURE/index.html create mode 100644 site/itpp-infrastructure/ops-portal/CHANGELOG/index.html create mode 100644 site/itpp-infrastructure/ops-portal/index.html create mode 100644 site/itpp-infrastructure/project-log/index.html create mode 100644 site/itpp-infrastructure/projects-master-readme/index.html create mode 100644 site/itpp-infrastructure/super-search-cf-bypass/index.html create mode 100644 site/itpp-standards/CHANGELOG/index.html create mode 100644 site/itpp-standards/index.html create mode 100644 site/launchcheck/CHANGELOG/index.html create mode 100644 site/launchcheck/competitive-analysis/index.html create mode 100644 site/launchcheck/index.html create mode 100644 site/launchcheck/launchcheck-business-proposal/index.html create mode 100644 site/osint-tool/CHANGELOG/index.html create mode 100644 site/osint-tool/index.html create mode 100644 site/scripts/CHANGELOG/index.html create mode 100644 site/scripts/index.html create mode 100644 site/search/search_index.json create mode 100644 site/shark-game/CHANGELOG/index.html create mode 100644 site/shark-game/index.html create mode 100644 site/sitemap.xml create mode 100644 site/sitemap.xml.gz create mode 100644 site/transitpin/CHANGELOG/index.html create mode 100644 site/transitpin/index.html create mode 100644 site/verdicttank/CHANGELOG/index.html create mode 100644 site/verdicttank/index.html diff --git a/.gitea/workflows/docs-publish.yml b/.gitea/workflows/docs-publish.yml new file mode 100644 index 0000000..e7b2b60 --- /dev/null +++ b/.gitea/workflows/docs-publish.yml @@ -0,0 +1,20 @@ +name: Publish Docs Site +on: + push: + branches: [main] + schedule: + - cron: '0 5 * * *' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build MkDocs site + run: | + pip install mkdocs mkdocs-material + bash build-docs.sh + mkdocs build + - name: Deploy to app3 + run: | + rsync -avz site/ root@152.53.241.111:/var/www/docs.itpropartner.com/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ff27640 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +# ITPP Docs — CHANGELOG + +## 2026-08-09 — Initial + +- Created itpp-docs repository with MkDocs Material build pipeline. +- Aggregated docs from 12 ITPP projects into unified documentation site. +- Added nightly Gitea Actions build + deploy workflow. +- Deployed to docs.itpropartner.com on app3. diff --git a/README.md b/README.md index 124c2e9..f7211b9 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,43 @@ -# itpp-docs +# ITPP Docs -Aggregated MkDocs documentation site for all IT Pro Partner projects \ No newline at end of file +> **Owner:** Germaine | **Status:** LIVE +> **Last Updated:** 2026-08-09 + +Aggregated MkDocs documentation site for all IT Pro Partner projects. Pulls READMEs, CHANGELOGs, and `docs/` directories from every project repo and builds a unified Material-for-MkDocs site served at [docs.itpropartner.com](https://docs.itpropartner.com/). + +## How It Works + +1. `build-docs.sh` clones/fetches every project repo listed in `REPOS` +2. Copies each repo's `docs/`, `README.md`, and `CHANGELOG.md` into `docs-source//` +3. `mkdocs build` renders the entire site to `site/` +4. Gitea Actions (nightly) or manual rsync deploys to app3 + +## Tech Stack + +- **Site generator:** MkDocs + Material for MkDocs theme +- **CI/CD:** Gitea Actions (`.gitea/workflows/docs-publish.yml`) +- **Hosting:** Nginx on app3 (152.53.241.111) + +## Quick Start + +```bash +git clone https://git.itpropartner.com/ippadmin/itpp-docs.git +cd itpp-docs +pip install mkdocs mkdocs-material +bash build-docs.sh +mkdocs serve +``` + +## Deploy + +```bash +# Manual deploy +bash build-docs.sh && mkdocs build && rsync -avz site/ root@152.53.241.111:/home/ippadmin/htdocs/docs.itpropartner.com/ + +# Or push to main — Gitea Actions handles rebuild + deploy nightly +``` + +## Related + +- [itpp-infrastructure](https://git.itpropartner.com/ippadmin/itpp-infrastructure) — server inventory, DNS +- [itpp-standards](https://git.itpropartner.com/ippadmin/itpp-standards) — docs standards and templates diff --git a/build-docs.sh b/build-docs.sh new file mode 100755 index 0000000..1ee6b0d --- /dev/null +++ b/build-docs.sh @@ -0,0 +1,113 @@ +#!/bin/bash +# build-docs.sh — Aggregate docs from all ITPP projects into docs-source/ +# Run: bash build-docs.sh +set -euo pipefail + +DOCS_SOURCE="docs-source" +REPO_BASE="https://git.itpropartner.com/ippadmin" +TOKEN="1761daa2c537fb72b365e54619208329d8e3ad33" + +# List of repos to pull docs from +REPOS=( + "itpp-infrastructure" + "itpp-standards" + "transitpin" + "homelab" + "scripts" + "fleettracker360" + "shark-game" + "verdicttank" + "apex-track" + "boxpilot" + "osint-tool" + "launchcheck" +) + +# Clean previous build +rm -rf "$DOCS_SOURCE" +mkdir -p "$DOCS_SOURCE" + +for repo in "${REPOS[@]}"; do + echo "=== Pulling docs from $repo ===" + REPO_DIR="/tmp/itpp-docs-build/$repo" + DOC_DIR="$DOCS_SOURCE/$repo" + + if [ -d "$REPO_DIR" ]; then + # Fetch latest if already cloned + git -C "$REPO_DIR" fetch origin 2>/dev/null || true + git -C "$REPO_DIR" reset --hard origin/main 2>/dev/null || true + else + mkdir -p "$(dirname "$REPO_DIR")" + git clone --depth 1 "https://ippadmin:${TOKEN}@git.itpropartner.com/ippadmin/${repo}.git" "$REPO_DIR" 2>&1 || { + echo "WARNING: Failed to clone $repo — skipping" + continue + } + fi + + mkdir -p "$DOC_DIR" + + # Copy docs/ directory if it exists + if [ -d "$REPO_DIR/docs" ]; then + cp -r "$REPO_DIR/docs/"* "$DOC_DIR/" 2>/dev/null || true + echo " Copied docs/ directory" + fi + + # Copy README.md as index.md if no docs/index.md exists + if [ ! -f "$DOC_DIR/index.md" ] && [ -f "$REPO_DIR/README.md" ]; then + # Convert README.md to index.md: strip first H1 line, prefix repo name + { + echo "# ${repo}" + echo + tail -n +2 "$REPO_DIR/README.md" + } > "$DOC_DIR/index.md" + echo " Created index.md from README.md" + fi + + # Copy CHANGELOG.md + if [ -f "$REPO_DIR/CHANGELOG.md" ]; then + cp "$REPO_DIR/CHANGELOG.md" "$DOC_DIR/" + echo " Copied CHANGELOG.md" + fi + + # Copy CONTRIBUTING.md if it exists + if [ -f "$REPO_DIR/CONTRIBUTING.md" ]; then + cp "$REPO_DIR/CONTRIBUTING.md" "$DOC_DIR/" + echo " Copied CONTRIBUTING.md" + fi + + echo " Done with $repo" +done + +# Create top-level index.md +cat > "$DOCS_SOURCE/index.md" << 'INDEXEOF' +# IT Pro Partner Documentation + +Welcome to the IT Pro Partner centralized documentation site. + +## Projects + +| Project | Description | Repo | +|---|---|---| +| [ITPP Infrastructure](itpp-infrastructure/) | Server inventory, DNS, architecture | [Repo](https://git.itpropartner.com/ippadmin/itpp-infrastructure) | +| [ITPP Standards](itpp-standards/) | Documentation standards & templates | [Repo](https://git.itpropartner.com/ippadmin/itpp-standards) | +| [TransitPin](transitpin/) | White-label transportation portal | [Repo](https://git.itpropartner.com/ippadmin/transitpin) | +| [HomeLab](homelab/) | Home lab infrastructure automation | [Repo](https://git.itpropartner.com/ippadmin/homelab) | +| [Scripts](scripts/) | Operations and automation scripts | [Repo](https://git.itpropartner.com/ippadmin/scripts) | +| [FleetTracker360](fleettracker360/) | GPS fleet tracking platform | [Repo](https://git.itpropartner.com/ippadmin/fleettracker360) | +| [Shark Game](shark-game/) | Shark Attack Fantasy League | [Repo](https://git.itpropartner.com/ippadmin/shark-game) | +| [VerdictTank](verdicttank/) | Product review and validation platform | [Repo](https://git.itpropartner.com/ippadmin/verdicttank) | +| [Apex Track](apex-track/) | Track event management | [Repo](https://git.itpropartner.com/ippadmin/apex-track) | +| [BoxPilot](boxpilot/) | Logistics operations platform | [Repo](https://git.itpropartner.com/ippadmin/boxpilot) | +| [OSINT Tool](osint-tool/) | OSINT people search & skip tracing | [Repo](https://git.itpropartner.com/ippadmin/osint-tool) | +| [LaunchCheck](launchcheck/) | Startup validation SaaS | [Repo](https://git.itpropartner.com/ippadmin/launchcheck) | + +## About + +This site is auto-generated by [mkdocs-material](https://squidfunk.github.io/mkdocs-material/) +from source repositories hosted on [Gitea](https://git.itpropartner.com/). +Rebuilt nightly via Gitea Actions. +INDEXEOF + +echo "=== Build complete ===" +echo "Source files in: $DOCS_SOURCE/" +ls -la "$DOCS_SOURCE/" | head -20 diff --git a/docs-source/apex-track/CHANGELOG.md b/docs-source/apex-track/CHANGELOG.md new file mode 100644 index 0000000..288e28b --- /dev/null +++ b/docs-source/apex-track/CHANGELOG.md @@ -0,0 +1,5 @@ +# Apex Track Experience — CHANGELOG + +## 2026-07-10 — Initial + +- Created project repository and directory structure. diff --git a/docs-source/apex-track/index.md b/docs-source/apex-track/index.md new file mode 100644 index 0000000..ddd6879 --- /dev/null +++ b/docs-source/apex-track/index.md @@ -0,0 +1,24 @@ +# apex-track + + +> **Owner:** Germaine | **Status:** PLANNED +> **Last Updated:** 2026-08-09 + +Apex Track is a track event and racing experience management platform. Designed for track day organizers, racing clubs, and motorsport venues to manage event scheduling, participant registration, timing and scoring, and live results publishing. + +## Tech Stack + +- TBD — architecture and stack decisions pending + +## Quick Start + +```bash +git clone https://git.itpropartner.com/ippadmin/apex-track.git +cd apex-track +# Project in early planning phase — implementation to follow +``` + +## Related + +- [itpp-infrastructure](https://git.itpropartner.com/ippadmin/itpp-infrastructure) — server inventory, DNS +- [itpp-standards](https://git.itpropartner.com/ippadmin/itpp-standards) — docs standards and templates diff --git a/docs-source/boxpilot/CHANGELOG.md b/docs-source/boxpilot/CHANGELOG.md new file mode 100644 index 0000000..bfbd314 --- /dev/null +++ b/docs-source/boxpilot/CHANGELOG.md @@ -0,0 +1,5 @@ +# BoxPilot Logistics — CHANGELOG + +## 2026-07-10 — Initial + +- Created project repository and directory structure. diff --git a/docs-source/boxpilot/index.md b/docs-source/boxpilot/index.md new file mode 100644 index 0000000..4aeb217 --- /dev/null +++ b/docs-source/boxpilot/index.md @@ -0,0 +1,24 @@ +# boxpilot + + +> **Owner:** Germaine | **Status:** PLANNED +> **Last Updated:** 2026-08-09 + +BoxPilot is a logistics operations platform for freight, shipping, and delivery management. Designed to streamline dispatch, route optimization, carrier management, and shipment tracking for logistics operations. + +## Tech Stack + +- TBD — architecture and stack decisions pending + +## Quick Start + +```bash +git clone https://git.itpropartner.com/ippadmin/boxpilot.git +cd boxpilot +# Project in early planning phase — implementation to follow +``` + +## Related + +- [itpp-infrastructure](https://git.itpropartner.com/ippadmin/itpp-infrastructure) — server inventory, DNS +- [itpp-standards](https://git.itpropartner.com/ippadmin/itpp-standards) — docs standards and templates diff --git a/docs-source/fleettracker360/CHANGELOG.md b/docs-source/fleettracker360/CHANGELOG.md new file mode 100644 index 0000000..1a7c3bd --- /dev/null +++ b/docs-source/fleettracker360/CHANGELOG.md @@ -0,0 +1,23 @@ +# FleetTracker360 — CHANGELOG + +## 2026-07-16 — Branding & GPS Accuracy + +### Branding +- Replaced Traccar logo SVG with FleetTracker360 (GPS dot + signal waves + gradient text) +- Created custom branded login page at `/login.html` — dark navy theme, animated grid background +- HTTPS proxy: Caddy on Core (gps.fleettracker360.com) → Traccar on app2 (152.53.39.202:8082) +- DNS: gps.fleettracker360.com moved from app2 direct to Core proxy (Cloudflare proxied) + +### GPS Accuracy +- Server-side filtering enabled in Traccar config: + - `filter.enable=true` (Kalman smoothing) + - `filter.accuracy=30` (reject cell tower triangulation >30m) + - `filter.distance=5` (ignore micro-jitter <5m) + - `geolocation.enable=true` (road snapping via Nominatim) +- Computed attributes added to database: + - `motion`: auto-detect transport mode (walking/cycling/driving/flying) + - `gpsQuality`: tag fix accuracy (excellent/good/fair/poor) + +### Still Pending +- Computed attribute device linking in UI +- Full community dashboard integration diff --git a/docs-source/fleettracker360/index.md b/docs-source/fleettracker360/index.md new file mode 100644 index 0000000..97aab46 --- /dev/null +++ b/docs-source/fleettracker360/index.md @@ -0,0 +1,40 @@ +# fleettracker360 + + +## Overview +Self-hosted GPS tracking using Traccar (open source) with live dashboard, ETA predictions, vehicle telemetry, and Sho'Nuff integration. + +## Architecture +``` +iPhone (Traccar Client) → Traccar Server (Docker) → HERE Maps API (traffic) +OBD2 dongles → → Sho'Nuff (ETAs, alerts) +Garrison's iPhone → → Live Dashboard (ops portal) +``` + +## Tech Stack +- **Traccar** — GPS tracking server (Docker) +- **Traccar Client** — iOS/Android app for phone tracking +- **HERE Maps API** — Traffic-aware routing (250k free transactions/mo) +- **Custom Dashboard** — Dark theme, live-updating HTML/JS +- **Sho'Nuff** — AI agent for ETA queries, alerts, geofencing +- **Domain:** fleettracker360.com (Cloudflare proxied) + +## Status +- [ ] Fresh Traccar Docker deployment on netcup +- [ ] iOS Traccar Client setup (Germaine + Garrison) +- [ ] OBD2 dongle pairing (specs TBD) +- [ ] HERE API key and routing integration +- [ ] Live dashboard design and deploy +- [ ] ETA automation (Sho'Nuff queries) +- [ ] Geofence alerts (home, office, school) +- [ ] Cancel old fleettracker Hetzner server + +## Directory Structure +``` +fleettracker/ +├── README.md <- This file +├── server/ <- Docker compose, Traccar config +├── dashboard/ <- Frontend HTML/CSS/JS +├── api/ <- Scripts for Sho'Nuff integration +└── ideas/ <- Feature ideas, mockups, research +``` diff --git a/docs-source/homelab/CHANGELOG.md b/docs-source/homelab/CHANGELOG.md new file mode 100644 index 0000000..6f624c9 --- /dev/null +++ b/docs-source/homelab/CHANGELOG.md @@ -0,0 +1,18 @@ +# Home Lab Changelog + +## 2026-07 + +### Added +- **Documentation repo initialized** — Git-tracked home lab infrastructure docs +- **Host inventory:** vm-host-01 (10.1.1.100), vm-host-02 (10.1.1.110), QNAP TS-1635 (10.1.1.40), docker-host-01 (10.1.1.14), MikroTik router (10.1.1.1) +- **SSH key:** homelab ed25519 key pair created and deployed to all hosts +- **QNAP NFS storage:** 3 exports configured — VM migration (2TB), backups (1TB), ISOs (1TB) +- **DNS chain documented:** AdGuard Home primary (docker-host-01, 10.1.1.14:53), Technitium secondary (dns1.itpropartner.com), AdGuard tertiary (vm-host-01) + +### Changed +- **vm-host-02 cleared** — all VMs migrated to vm-host-01 or destroyed; host ready for GPU installation +- **VM consolidation:** destroyed template VM, Graylog, Zabbix, and FOG VMs for later rebuild + +### Added +- **State snapshot** (`state-2026-07-21.md`) — VM inventory, service listing, and migration state captured +- Docker-host-01 service catalog: 13 containers including Mealie, n8n, Home Assistant, Jellyfin, media pipeline (Prowlarr/Radarr/Sonarr/Sabnzbd), AdGuard Home, NPM, Uptime Kuma, Dockhand, Browser-Use WebUI diff --git a/docs-source/homelab/index.md b/docs-source/homelab/index.md new file mode 100644 index 0000000..3effcd0 --- /dev/null +++ b/docs-source/homelab/index.md @@ -0,0 +1,75 @@ +# homelab + + +Proxmox, QNAP, and home network management. +**Last updated:** 2026-08-09 + +## Hosts + +| Host | IP | Role | Version | +|---|---|---|---| +| vm-host-01 | 10.1.1.100 | Proxmox — docker-host-01, adguard-home | **PVE 8.4.1** (kernel 6.8.12-10-pve) | +| vm-host-02 | 10.1.1.110 | Proxmox — cleared for GPU | **PVE 8.4.1** (kernel 6.8.12-10-pve) | +| QNAP TS-1635 | 10.1.1.40 | Shared storage, 4 pools | **Firmware 5.2.7** | +| docker-host-01 | 10.1.1.14 | Docker services (13 containers) | VM 105 on vm-host-01 | +| MikroTik router | 10.1.1.1 | Gateway, WireGuard/L2TP endpoint | — | + +## Connectivity + +| Tunnel | Type | Status | Notes | +|---|---|---|---| +| Core ↔ MikroTik | WireGuard (wg0) | ✅ Up | Core's wg0, port 51821 | +| Core ↔ MikroTik | L2TP | ✅ Up | SSH: `shonuff@192.168.88.1` | +| MikroTik → LAN | Route | ✅ Up | 10.1.0.0/24 reachable from Core | + +## SSH Access +- Key: `homelab` (ed25519) at `/root/.ssh/homelab` +- Deployed to: vm-host-01, vm-host-02, QNAP (admin user), docker-host-01 + +## Storage + +| Pool | Size | Used | Mount | +|---|---|---|---| +| CACHEDEV4_DATA | 2.0T | 6% (112.8G) | `/share/CACHEDEV4_DATA` | +| CACHEDEV5_DATA | 14.9T | 31% (4.6T) | `/share/CACHEDEV5_DATA` | +| CACHEDEV6_DATA | 15.9T | 38% (6.1T) | `/share/CACHEDEV6_DATA` | +| CACHEDEV7_DATA | 1.0T | 4% (40.8G) | `/share/CACHEDEV7_DATA`, ISO export | + +**NFS exports on Proxmox hosts (as of Aug 9):** +- `qnap-nfs-backups` — 10.1.1.40:/ISO → `/mnt/pve/qnap-nfs-backups` (1TB) +- `qnap-nfs-iso` — 10.1.1.40:/ISO → `/mnt/pve/qnap-nfs-iso` (1TB) +- ⚠️ `qnap-nfs` (VM migration storage) mount point `/mnt/pve/qnap-nfs` does not exist on vm-host-01 + +## VMs + +### vm-host-01 (10.1.1.100) + +| VMID | Name | Status | RAM | Disk | +|---|---|---|---|---| +| 100 | adguard-home | **stopped** | 2GB | 32GB | +| 105 | docker-host-01 | running | 16GB | 150GB | + +### vm-host-02 (10.1.1.110) + +**CLEARED** — no VMs. Ready for GPU installation. + +## DNS +- **Primary AdGuard Home:** docker-host-01 at `10.1.1.14` + - **Admin UI:** `http://10.1.1.14:8080/login.html` + - DNS service: port `53` +- **Secondary:** app2 Technitium (`dns1.itpropartner.com`) +- **Tertiary:** vm-host-01 AdGuard (`10.1.1.10`) — **VM stopped**, secondary DNS unavailable + +> Do not use `http://10.1.1.14:80` for AdGuard administration; that port serves NGINX Proxy Manager. + +## Services on docker-host-01 +- Mealie (:9925), n8n (:5678), Home Assistant (:8123) +- Jellyfin (:8096), Prowlarr/Radarr/Sonarr/Sabnzbd (media pipeline) +- AdGuard Home DNS (:53) + admin UI (:8080) +- NPM (:80/:443), Uptime Kuma (:3001) +- Dockhand (:3100), Browser-Use WebUI (:6080/:7788) +- 13 containers total, ~3.8GB RAM of 16GB allocated + +## Key Docs +- `state-2026-08-09.md` — current state snapshot +- `state-2026-07-21.md` — previous state (pre-August audit) \ No newline at end of file diff --git a/docs-source/index.md b/docs-source/index.md new file mode 100644 index 0000000..3c030c3 --- /dev/null +++ b/docs-source/index.md @@ -0,0 +1,26 @@ +# IT Pro Partner Documentation + +Welcome to the IT Pro Partner centralized documentation site. + +## Projects + +| Project | Description | Repo | +|---|---|---| +| [ITPP Infrastructure](itpp-infrastructure/) | Server inventory, DNS, architecture | [Repo](https://git.itpropartner.com/ippadmin/itpp-infrastructure) | +| [ITPP Standards](itpp-standards/) | Documentation standards & templates | [Repo](https://git.itpropartner.com/ippadmin/itpp-standards) | +| [TransitPin](transitpin/) | White-label transportation portal | [Repo](https://git.itpropartner.com/ippadmin/transitpin) | +| [HomeLab](homelab/) | Home lab infrastructure automation | [Repo](https://git.itpropartner.com/ippadmin/homelab) | +| [Scripts](scripts/) | Operations and automation scripts | [Repo](https://git.itpropartner.com/ippadmin/scripts) | +| [FleetTracker360](fleettracker360/) | GPS fleet tracking platform | [Repo](https://git.itpropartner.com/ippadmin/fleettracker360) | +| [Shark Game](shark-game/) | Shark Attack Fantasy League | [Repo](https://git.itpropartner.com/ippadmin/shark-game) | +| [VerdictTank](verdicttank/) | Product review and validation platform | [Repo](https://git.itpropartner.com/ippadmin/verdicttank) | +| [Apex Track](apex-track/) | Track event management | [Repo](https://git.itpropartner.com/ippadmin/apex-track) | +| [BoxPilot](boxpilot/) | Logistics operations platform | [Repo](https://git.itpropartner.com/ippadmin/boxpilot) | +| [OSINT Tool](osint-tool/) | OSINT people search & skip tracing | [Repo](https://git.itpropartner.com/ippadmin/osint-tool) | +| [LaunchCheck](launchcheck/) | Startup validation SaaS | [Repo](https://git.itpropartner.com/ippadmin/launchcheck) | + +## About + +This site is auto-generated by [mkdocs-material](https://squidfunk.github.io/mkdocs-material/) +from source repositories hosted on [Gitea](https://git.itpropartner.com/). +Rebuilt nightly via Gitea Actions. diff --git a/docs-source/itpp-infrastructure/CHANGELOG.md b/docs-source/itpp-infrastructure/CHANGELOG.md new file mode 100644 index 0000000..b51b5ee --- /dev/null +++ b/docs-source/itpp-infrastructure/CHANGELOG.md @@ -0,0 +1,6 @@ +# itpp-infrastructure — CHANGELOG + +## 2026-07-16 — Audit Remediation + +- Created CHANGELOG.md (missing per project documentation standard) +- Project directory: `/root/projects/itpp-infrastructure` diff --git a/docs-source/itpp-infrastructure/app2-caddyfile-audit-2026-07-21.md b/docs-source/itpp-infrastructure/app2-caddyfile-audit-2026-07-21.md new file mode 100644 index 0000000..f9a4139 --- /dev/null +++ b/docs-source/itpp-infrastructure/app2-caddyfile-audit-2026-07-21.md @@ -0,0 +1,27 @@ +# app2 Caddyfile Audit — July 21, 2026 + +## Root cause +Technitium DNS was deployed on app2. During the Caddyfile rewrite to add `dns1.itpropartner.com`, two existing services were dropped: + +1. **UNMS** — `reverse_proxy localhost:80` failed because UNMS nginx exposes port 443 (via host 8444), not port 80. Fixed by proxying via HTTPS with `tls_insecure_skip_verify`. +2. **Gitea** — entry was completely removed. Fixed by adding `reverse_proxy 127.0.0.1:3001`. + +## Prevention +- Always audit `docker ps` output BEFORE rewriting Caddyfile +- Verify every running container that exposes web ports has a Caddy entry +- Test each domain with `curl -sk` after Caddy reload + +## Final Caddyfile (validated) +``` +{ + default_bind 152.53.39.202 + auto_https disable_redirects +} +dns1.itpropartner.com:443 → 127.0.0.1:5380 +gps.fleettracker360.com:443 → localhost:8082 +fleettracker360.com:443 → localhost:8082 +unms.forefrontwireless.com:443 → https://localhost:8444 (tls_insecure_skip_verify) +unifi.itpropartner.com:443 → https://localhost:8443 (tls_insecure_skip_verify) +hudu.itpropartner.com:443 → localhost:3000 +git.itpropartner.com:443 → 127.0.0.1:3001 +``` diff --git a/docs-source/itpp-infrastructure/backup-restore/ARCHITECTURE.md b/docs-source/itpp-infrastructure/backup-restore/ARCHITECTURE.md new file mode 100644 index 0000000..a318fea --- /dev/null +++ b/docs-source/itpp-infrastructure/backup-restore/ARCHITECTURE.md @@ -0,0 +1,158 @@ +# Backup-Restore — Architecture + +## Topology + +``` + INTERNET + | + [Caddy on Core] + my.itpropartner.com + | + +---------------+---------------+ + | | | + /backups/* /api/restore /api/backup + /api/log /api/download /api/delete + | | | + +-------+-------+-------+-------+ + | + app3 (152.53.241.111) + netcup RS 4000 + | + [Flask :8090] + /opt/backup-restore/ + | + +-------------------+-------------------+ + | | | + snapshot.sh app.py (UI+API) snapshots/ + (cron 1AM,1PM) Jinja templates /opt/backup-restore/ + | | snapshots// + v v | + [tar files] [render HTML] +------+------+ + [mysqldump] [REST API] | | | + | | .tar.gz .sql note.txt + v v + /opt/backup-restore/ [Browser] + snapshots// + / +``` + +## Data Flow — Manual Backup + +``` +Browser (user clicks "Backup Now") + | + |-- POST /api/backup {"domain":"x.com","note":"pre-deploy"} + | | + | v + | Caddy → app3:8090 + | | + | v + | Flask api_backup() + | | + | |-- Parse nginx config → find htdocs path + | |-- tar -czf files.tar.gz (timeout 300s) + | |-- Parse wp-config.php → find DB_NAME + | |-- mysqldump → database.sql (timeout 300s) + | |-- Save note.txt, size.txt + | |-- Return {"ok":true, "snapshot":""} + | | + | v + | snapshots/x.com/2026-07-20_163208/ + | files.tar.gz (16MB) + | database.sql (74KB) + | note.txt ("pre-deploy") + | size.txt + | + v +Browser reloads → new snapshot in list +``` + +## Data Flow — Restore + +``` +Browser (user clicks Restore on a snapshot) + | + |-- POST /api/restore {"domain":"x.com","snapshot":"2026-07-20_130001"} + | | + | v + | Caddy → app3:8090 (flush_interval -1, 300s timeouts) + | | + | v + | Flask api_restore() + | | + | |-- Find snapshot path + | |-- tar -xzf files.tar.gz → htdocs (timeout 300s) + | |-- mysql < database.sql → WordPress DB (timeout 300s) + | |-- chown -R site-user:site-user + | |-- Log to restore.log: "TS|x.com|snap_id|OK" + | |-- Return {"ok":true, "msg":"x.com restored to "} + | | + | v + | Site is restored + | + v +Browser shows success toast → Restore History updates +``` + +## Components + +### 1. Flask App (`/opt/backup-restore/app/app.py`) +- Single-file Flask application, port 8090 +- Jinja2 templating for backup dashboard (render_template_string) +- 6 API endpoints (backup, restore, delete, download, log, index) +- All HTML/CSS/JS inline in a single Python triple-quoted string +- No auth — accessible via Caddy-only routing +- Systemd: `backup-restore.service` + +### 2. Snapshot Engine (`/opt/backup-restore/snapshot.sh`) +- Bash script, runs at 1 AM and 1 PM via cron +- Iterates all WordPress sites in `/etc/nginx/sites-enabled/` +- Creates: files.tar.gz (document root), database.sql (MySQL dump) +- Auto-cleanup: deletes snapshots older than 30 days +- Log: `/opt/backup-restore/logs/snapshots.log` + +### 3. Snapshot Storage (`/opt/backup-restore/snapshots/`) +- Structure: `///` +- 9 WordPress domains, 10 snapshots each (10 days retention shown) +- Average snapshot size: 16MB files + 74KB database +- Total: ~1.4GB for full snapshot set + +### 4. Restore Log (`/opt/backup-restore/logs/restore.log`) +- Pipe-delimited format: `timestamp|domain|snapshot_id|status` +- Written by api_restore() on every restore attempt +- Read by /api/log → displayed in Restore History table +- Last 50 entries retained + +### 5. Caddy Proxy (on Core) +- `handle /api/backup` → app3:8090 +- `handle /api/restore` → app3:8090 (flush_interval -1, 300s read/write timeouts) +- `handle /api/download/*` → app3:8090 +- `handle /api/log` → app3:8090 +- `handle_path /backups/*` → app3:8090 (300s timeouts for long restores) +- Domain: my.itpropartner.com + +## 9 Hosted WordPress Sites + +All served by CloudPanel on app3, backed up by this system: + +| Domain | htdocs Path | DB Pattern | +|---|---|---| +| apextrackexperience.com | /home/apx/htdocs/apextrackexperience.com | wp-config DB_NAME | +| boxpilotlogistics.com | /home/boxpilotlogistics/htdocs/boxpilotlogistics.com | wp-config DB_NAME | +| debtrecoveryexperts.com | /home/debtrecoveryexperts/... | wp-config DB_NAME | +| iamgmb.com | /home/iamgmb/... | wp-config DB_NAME | +| katiewattdesign.com | /home/katiewattdesign/htdocs/katiewattdesign.com | wp-config DB_NAME | +| katiewattsdesign.com | /home/katiewattsdesign/... | wp-config DB_NAME | +| mainwp.itpropartner.com | /home/mainwp/... | wp-config DB_NAME | +| vigilanttac.com | /home/vigilanttac/... | wp-config DB_NAME | +| voipsimplicity.com | /home/voipsimplicity/... | wp-config DB_NAME | + +## Key Design Decisions + +1. **Single-file Flask app:** No package structure needed — the app has 6 endpoints and one HTML template. Keeping it in one file makes deployment trivial (scp + systemctl restart). + +2. **Caddy on Core as single entry point:** app3 isn't exposed to the internet directly. All access goes through Core's Caddy with proper timeouts. The restore operation takes 30-45s and Caddy's default proxy timeout was killing connections mid-operation. + +3. **Tar + mysqldump over rsync:** Snapshots are point-in-time archives, not incremental backups. Each snapshot is self-contained (files.tar.gz + database.sql). Restore is a single operation with no dependency chain. + +4. **No auth on backup API:** The endpoints have no authentication. Access is controlled by Caddy routing — only requests through my.itpropartner.com reach the app. Internal network only. diff --git a/docs-source/itpp-infrastructure/backup-restore/CHANGELOG.md b/docs-source/itpp-infrastructure/backup-restore/CHANGELOG.md new file mode 100644 index 0000000..07ba660 --- /dev/null +++ b/docs-source/itpp-infrastructure/backup-restore/CHANGELOG.md @@ -0,0 +1,30 @@ +# Backup-Restore Changelog + +## 2026-07-20 — Restore logging and manual backup + +### Added +- **Backup Now button:** Green "+ Backup Now" button on backup page +- **Domain dropdown:** Select element with all 9 hosted domains +- **Note field:** Optional "why" note saved as note.txt in snapshot +- **Restore History section:** Auto-expanded table at bottom — Domain, Snapshot, Date/Time, Status +- **Restore logging:** Every restore writes to `/opt/backup-restore/logs/restore.log` +- **Status formatting:** Green OK / red FAILED with centered status column + +### Fixed +- **Restore timing out:** Caddy flush_interval added + 300s transport timeouts +- **Route ordering:** `/api/restore` decorator was stacked on backup function → reconnected to restore function +- **API routing:** `/api/restore`, `/api/backup`, `/api/log` not proxied → added to Caddy config +- **Mobile toggle:** Inline `display:none` on site tables removed → CSS class toggle now works +- **Mobile touch:** role="button", tabindex="0", Enter key support added to card headers +- **Auto-expand first domain:** Removed — all domains now start collapsed +- **Restore History auto-expanded:** tbl-log has class="show", arrow is ▼ + +### Changed +- "Backup Log History" → "Restore History" +- Config page scripts directory now shows content when clicked + +## 2026-07-17 — Initial deployment +- Flask app deployed on app3 as systemd service +- Snapshot script scheduled (1 AM, 1 PM) +- 9 WordPress sites configured for backup +- Caddy proxy from Core via my.itpropartner.com diff --git a/docs-source/itpp-infrastructure/backup-restore/README.md b/docs-source/itpp-infrastructure/backup-restore/README.md new file mode 100644 index 0000000..d71d530 --- /dev/null +++ b/docs-source/itpp-infrastructure/backup-restore/README.md @@ -0,0 +1,50 @@ +# Backup-Restore — my.itpropartner.com/backups/ + +## Architecture +- **Server:** app3 (152.53.241.111, netcup RS 4000) +- **Backend:** Flask Python app at `/opt/backup-restore/app/app.py` (port 8090) +- **Proxy:** Caddy on Core → reverse_proxy to 152.53.241.111:8090 with 300s timeouts +- **Snapshots:** `/opt/backup-restore/snapshots///` +- **Scheduled:** `0 1,13 * * * /opt/backup-restore/snapshot.sh` — 1 AM and 1 PM daily +- **Systemd:** `backup-restore.service` +- **Retention:** 30 days (auto-cleanup) + +## Sites Backed Up (9 domains) +apextrackexperience.com, boxpilotlogistics.com, debtrecoveryexperts.com, iamgmb.com, katiewattdesign.com, katiewattsdesign.com, mainwp.itpropartner.com, vigilanttac.com, voipsimplicity.com + +## Snapshot Contents +Each snapshot directory contains: +- `files.tar.gz` — WordPress document root tarball +- `database.sql` — MySQL dump +- `size.txt` — Total backup size in bytes +- `note.txt` — Optional manual backup note + +## API Endpoints +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | /backups/ | No | Backup dashboard page | +| POST | /api/backup | No | Trigger manual backup | +| POST | /api/restore | No | Restore a snapshot | +| POST | /api/delete | No | Delete a snapshot | +| GET | /api/download// | No | Download snapshot archive | +| GET | /api/log | No | Restore history | + +## Caddy Routes (on Core) +``` +handle /api/backup → app3:8090 +handle /api/restore → app3:8090 (flush_interval -1, 300s timeouts) +handle /api/download/* → app3:8090 +handle /api/log → app3:8090 +handle_path /backups/* → app3:8090 (300s timeouts) +``` + +## Recovery +``` +systemctl restart backup-restore +# Manual snapshot: +/opt/backup-restore/snapshot.sh +# Manual restore via curl: +curl -X POST https://my.itpropartner.com/api/restore \ + -H "Content-Type: application/json" \ + -d '{"domain":"katiewattdesign.com","snapshot":"2026-07-20_130001"}' +``` diff --git a/docs-source/itpp-infrastructure/cost-control-rollout-2026-07-24.md b/docs-source/itpp-infrastructure/cost-control-rollout-2026-07-24.md new file mode 100644 index 0000000..35db812 --- /dev/null +++ b/docs-source/itpp-infrastructure/cost-control-rollout-2026-07-24.md @@ -0,0 +1,37 @@ +# Cost Control Rollout — 2026-07-24 + +**Trigger:** $46 additional unexpected spend on top of $155.76/3-day burn. +**Root cause:** Unlimited LiteLLM key + GPT-5.6 Terra as default gateway model with no enforced budget, session-size, or model-allowlist guardrails. + +## Changes Deployed + +### 1. LiteLLM — New constrained team + key +- **Team `hermes-normal-ops`**: $3.33 rolling daily cap + $100 rolling 30-day cap, 30 RPM, 250K TPM, max 3 parallel requests. +- **Key `hermes-normal-ops-daily-capped`** (`...I3gQ`): Hard $3.33/day, model-restricted to approved list only. +- **Allowed models**: `claude-sonnet-5`, `deepseek-v4-pro`, `deepseek-v4-flash`, `glm-5.2`, `MiniMax-M3`, `qwen3.7-plus`. +- **Terra/GPT-5.6/Claude-Opus-4**: explicitly **excluded** from this key — LiteLLM returns HTTP 403. +- **Legacy key `sk-...itzA`**: blocked (blocked=t in DB). + +### 2. Hermes config — Default routing +- **Conductor**: `claude-sonnet-5` (admin-ai/LiteLLM proxy). +- **Fallbacks**: `deepseek-v4-pro` → `deepseek-v4-flash` (admin-ai only). +- **Delegation/workers**: `deepseek-v4-pro`, fallback `deepseek-v4-flash`. +- **No automatic escalation to premium** — failure stops, not silently upgrades. + +### 3. Session controls +- **Context length**: 128k tokens hard ceiling. +- **Compression**: enabled at 50% fill, targets 20% ratio. +- **Max turns**: 50 per session (prevents unbounded tool-call marathons). + +### 4. Rate limits (on LiteLLM key) +- 30 RPM, 250K TPM, max 3 concurrent requests. +- 429 throttle-backoff confirmed working in live logs. + +## Verification +- Sonnet makes calls through new key: HTTP 200. +- Terra through new key: HTTP 403 (blocked). +- Old key blocked in DB: `blocked = t`. +- Live gateway confirmed routing through `admin-ai` at `https://admin-ai.itpropartner.com/v1/`. + +## What's still behavioral (not enforced) +- Model compliance is enforced at the proxy key layer. Cost caps are enforced at the key+team layer. Session size is a Hermes config setting — stickiness depends on the runtime respecting it. diff --git a/docs-source/itpp-infrastructure/index.md b/docs-source/itpp-infrastructure/index.md new file mode 100644 index 0000000..d5715cf --- /dev/null +++ b/docs-source/itpp-infrastructure/index.md @@ -0,0 +1,278 @@ +# itpp-infrastructure + + +> **Last Updated:** July 17, 2026 +> **Maintainer:** Sho'Nuff + +--- + +## Server Inventory + +### Core Server +- **Hostname:** Core +- **IP:** 152.53.192.33 +- **Provider:** netcup RS 2000 G12 +- **Specs:** 8 vCPU EPYC 9645, 15 GB DDR5 ECC, 512 GB NVMe +- **OS:** Debian 13 +- **Role:** Hermes + Portals +- **Key Services:** + - Hermes Agent (Telegram + cron, 22 cron jobs) + - Caddy reverse proxy (12 domains, auto-TLS) + - Ops Portal (FastAPI, port 8090) + - Prometheus (native, port 9090) + Grafana (native, port 3002) + - Uptime Kuma (Docker, port 3001) — 9+ monitors + - Vaultwarden (Docker, port 8080) — vault.iamgmb.com + - Twenty CRM (Docker) — crm.debtrecoveryexperts.com + - DocuSeal (Docker, port 3000) — sign.core.itpropartner.com + - SearXNG (Docker, port 8888) + - Komodo (Docker, port 9120) + - Tailscale, StrongSwan, WireGuard (home CCR tunnel 10.77.0.0/24) + - Redis cache + +### App1 Server +- **Hostname:** app1 +- **IP:** 152.53.36.131 +- **Provider:** netcup RS 4000 G12 +- **Specs:** 12 vCPU EPYC 9645, 32 GB DDR5 ECC, 1 TB NVMe +- **OS:** Debian 13 +- **Role:** AI/Service Hub +- **Key Services:** + - Open WebUI (Docker, port 3000) — ai.itpropartner.com + - n8n + Postgres (Docker, port 5678) — n8n.itpropartner.com + - LiteLLM (Docker) + Postgres — admin-ai.itpropartner.com + - Mattermost Team Edition (Docker, port 8065) — noc.itpropartner.com + - Caddy (systemd, 80/443) + - 4 MCP servers: Browser (:8901), Filesystem (:8900), Email (:8902), Git (:8903) + - Super Search MCP (systemd, port 8899) + +### App2 Server +- **Hostname:** app2 +- **IP:** 152.53.39.202 +- **Provider:** netcup RS 4000 G12 +- **Specs:** 12 vCPU EPYC 9645, 32 GB DDR5 ECC, 1 TB NVMe +- **OS:** Debian 13 +- **Role:** Infrastructure Server +- **Key Services:** + - Traccar GPS (Docker, port 8082 + 5000-5150) — fleettracker360.com + - UniFi Controller (Docker, port 8443) — unifi.itpropartner.com + - UNMS/UISP (10 Docker containers) — unms.forefrontwireless.com + - Hudu (Docker) — hudu.itpropartner.com + - Caddy (4 domains) + +### App3 Server +- **Hostname:** app3 +- **IP:** 152.53.241.111 +- **Provider:** netcup RS 4000 G12 +- **Specs:** 12 vCPU EPYC 9645, 32 GB DDR5 ECC, 1 TB NVMe +- **OS:** Debian 13 +- **Role:** Web Hosting + Backup Restore +- **Key Services:** + - CloudPanel CE — panel.itpropartner.com + - Nginx (80/443) + Percona MySQL 8.4 + PHP 8.3 + - Backup Restore System (Flask, port 8090) — my.itpropartner.com/backups + - WordPress sites (7 migrated from wphost02, all live): + - debtreecoveryexperts.com, boxpilotlogistics.com, iamgmb.com + - katiewattsdesign.com, vigilanttac.com, apextrackexperience.com + - mainwp.itpropartner.com, voipsimplicity.com, my.voipsimplicity.com + - Daily snapshots: 1 AM + 1 PM, 60-day retention, /opt/backup-restore/snapshots + +### Core-BU (Warm Standby) +- **Hostname:** core-bu +- **IP:** 5.161.225.131 +- **Provider:** Hetzner CPX21 +- **Specs:** 3 vCPU, 4 GB RAM, 80 GB SSD +- **Role:** Warm standby — auto-failover if Core down +- **Watchdog:** 5-min check, 4-cycle confirmation, S3 sync every 10 min + +### Legacy / Decommissioned +- **old-ai:** 178.156.167.181 (Hetzner CPX41) — **decommissioned** (LiteLLM migrated to app1) +- **old app1:** 87.99.144.163 (Hetzner CPX11) — **deleted** +- **wphost02:** 5.161.62.38 (Hetzner CPX21) — **migrated to app3** +- **Ollama:** Removed from Core (systemd) and app1 (Docker) Jul 17 + +--- + +## Model Fallback Chain + +All providers use direct API keys. GPT-5.5 quality survives through admin-ai → OpenRouter, then degrades through DeepSeek → Gemini → Grok. + +| # | Model | Provider | Gateway | +|---|---|---|---| +| Primary | GPT-5.5 | admin-ai | Self-hosted LiteLLM (app1) | +| Fallback 1 | GPT-5.5 | OpenRouter | openrouter.ai | +| Fallback 2 | DeepSeek v4 Pro | DeepSeek | api.deepseek.com | +| Fallback 3 | Gemini 3.5 Flash | Google | generativelanguage.googleapis.com | +| Fallback 4 | Grok 4.5 | xAI | api.x.ai | + +**Credits (Jul 17):** DeepSeek $58, OpenRouter ~$30 remaining, OpenAI/xAI/Google on pay-as-you-go +**Health check:** Daily 8 AM cron (`model-usage-check`) + +--- + +## Domain / DNS Map + +### ⚠️ itpropartner.com — SiteGround Nameservers Only + +`itpropartner.com` uses **SiteGround nameservers** exclusively. A Cloudflare zone exists (`0dc20632…`) but is NOT authoritative — records created there silently fail. All `*.itpropartner.com` changes must be manual through SiteGround panel. + +| Domain | IP | Server | Service | +|---|---|---|---| +| core.itpropartner.com | 152.53.192.33 | Core | Landing page + Grafana link | +| ops.itpropartner.com | 152.53.192.33 | Core | Ops dashboard | +| sign.core.itpropartner.com | 152.53.192.33 | Core | DocuSeal | +| uptimekuma.itpropartner.com | 152.53.192.33 | Core | Uptime monitoring | +| gps.fleettracker360.com | 152.53.192.33 | Core | Traccar HTTPS proxy → app2 | +| my.itpropartner.com | 152.53.192.33 | Core | Customer portal hub | +| hudu.itpropartner.com | 152.53.39.202 | app2 | IT documentation | +| unifi.itpropartner.com | 152.53.39.202 | app2 | UniFi controller | +| panel.itpropartner.com | 152.53.241.111 | app3 | CloudPanel CE | +| ai.itpropartner.com | 152.53.36.131 | app1 | Open WebUI | +| n8n.itpropartner.com | 152.53.36.131 | app1 | n8n automation | +| admin-ai.itpropartner.com | 152.53.36.131 | app1 | LiteLLM | + +### Cloudflare-Managed Domains + +| Domain | IP | Server | Service | +|---|---|---|---| +| fleettracker360.com | Cloudflare | app2 | Fleet tracking website | +| gps.fleettracker360.com | Cloudflare → Core | Core → app2 | Traccar devices | +| voipsimplicity.com | Cloudflare | app3 | VoIP marketing site | +| my.voipsimplicity.com | Cloudflare | app3 | VoIP customer portal | +| portal.debtrecoveryexperts.com | 152.53.192.33 | Core | DRE portal | +| crm.debtrecoveryexperts.com | Cloudflare Access | — | DRE CRM | +| vault.iamgmb.com | 152.53.192.33 | Core | Vaultwarden | +| sign.iamgmb.com | 152.53.192.33 | Core | Document signing | +| shark.iamgmb.com | 152.53.192.33 | Core | Shark game | + +### DNS PENDING (create at SiteGround) + +| Subdomain | → IP | Service | +|---|---|---| +| vault.itpropartner.com | 152.53.36.131 | Vaultwarden (after migration) | +| status.itpropartner.com | 152.53.192.33 | Public status page | + +--- + +## Backup Pipeline + +| Backup | Schedule | Target | Purpose | +|---|---|---|---| +| hermes-live-sync | Every 15 min | s3://hermes-vps-backups/live/ | Live state sync | +| hermes-full-backup | Daily 1 AM | s3://hermes-vps-backups/hermes-full-backup/ | Full Hermes backup | +| home-router-backup | Daily 6 AM | s3://mikrotik-ccr-backups/ | CCR config | +| root-essentials-backup | Daily 3 AM | S3 | /root essentials | +| docker-volume-sync | Daily 3 AM | S3 | Docker volumes | +| system-config-sync | Daily 4 AM | S3 | System configs | +| unms-backup-sync | Daily 6 AM (Core) | s3://hermes-vps-backups/unms-backups/ | UNMS data (pulled from app2) | +| unifi-backup-sync | Daily 2 AM (Core) | s3://hermes-vps-backups/unifi-backups/ | UniFi configs (pulled from app2) | +| hudu-backup | Daily 7 AM | s3://hermes-vps-backups/hudu/backups/ | Hudu volume dump | +| gitea-backup | Daily 8 AM | s3://hermes-vps-backups/gitea/daily/ | Gitea repos | +| app1-backup | Daily 2 AM | s3://hermes-vps-backups/app1/ | LiteLLM, n8n, OpenWebUI, MCP, Mattermost | +| app2-backup | Daily 2:30 AM | s3://hermes-vps-backups/app2/ | Traccar, Gitea, Hudu, UNMS, UniFi | +| app3-backup | Daily 3 AM | s3://hermes-vps-backups/app3/ | CloudPanel, MySQL, WordPress | +| wphost02-backup | Daily 5 AM | s3://hermes-vps-backups/wphost02-backup/ | Webapps + MySQL | +| warm-standby-sync | Every 10 min | core-bu ← S3 | DR readiness | + +--- + +## Caddy Configuration + +### Core (152.53.192.33) + +``` +core.itpropartner.com → static files +sign.core.itpropartner.com → localhost:3000 (DocuSeal) +ops.itpropartner.com → 127.0.0.1:8090 + static +uptimekuma.itpropartner.com → 127.0.0.1:3001 (Uptime Kuma) +gps.fleettracker360.com → app2:8082 (Traccar) +my.itpropartner.com → static files +portal.debtrecoveryexperts.com → static files +vault.iamgmb.com → localhost:8080 (Vaultwarden) +sign.iamgmb.com → 127.0.0.1:8090 +shark.iamgmb.com → static + :8083 +``` + +### App1 (152.53.36.131) + +``` +ai.itpropartner.com → :3000 (Open WebUI) +n8n.itpropartner.com → :5678 (n8n) +admin-ai.itpropartner.com → :4000 (LiteLLM) +app1.itpropartner.com → static response +``` + +### App2 (152.53.39.202) + +``` +hudu.itpropartner.com → Hudu internal +gps.fleettracker360.com → :8082 (Traccar) +unms.forefrontwireless.com → UNMS Nginx +unifi.itpropartner.com → :8443 (UniFi) +``` + +--- + +## Service Access + +| Service | URL | Location | Auth | +|---|---|---|---| +| Open WebUI | https://ai.itpropartner.com | app1 | Chat UI | +| Open WebUI Admin | https://admin-ai.itpropartner.com/ui | app1 | user: admin, pw: LITELLM_MASTER_KEY | +| Ops Portal | https://ops.itpropartner.com | Core | Internal dashboard | +| Grafana | http://core.itpropartner.com:3002 | Core | admin/admin | +| Uptime Kuma | https://uptimekuma.itpropartner.com | Core | Service monitoring | +| Vaultwarden | https://vault.iamgmb.com | Core | Password vault | +| CloudPanel | https://panel.itpropartner.com | app3 | user: gmb / SQLite auth | +| Traccar | https://gps.fleettracker360.com | app2 | GPS fleet tracking | +| UniFi | https://unifi.itpropartner.com | app2 | Network controller | +| UNMS | https://unms.forefrontwireless.com | app2 | WISP management | +| Hudu | https://hudu.itpropartner.com | app2 | IT documentation | +| n8n | https://n8n.itpropartner.com | app1 | Automation | +| CRM (DRE) | https://crm.debtrecoveryexperts.com | Cloudflare Access | TwentyCRM | + +### MCP Access (from Open WebUI) + +| MCP Server | Location | Port | Tools | +|---|---|---|---| +| Super Search | app1 | :8899 | 10 tools — web_search, web_extract, person_search, email_search, phone_search, etc. | +| Browser | app1 | :8901 | browser_navigate, browser_snapshot, browser_click, browser_type, browser_console | +| Filesystem | app1 | :8900 | read_file, write_file, search_files, list_dir, file_info | +| Email | app1 | :8902 | search_emails, send_email, get_email | +| Git/Gitea | app1 | :8903 | clone, commit, push, pull | + +--- + +## SSH Access + +- **Key:** `itpp-infra` (deployed to all servers) +- **User:** `ippadmin` (sudo privileges) +- **Root SSH:** Enabled on app1, app2, app3 (key-only exception per provisioning standard) +- **Core SSH:** `ssh -i /root/.ssh/itpp-infra root@152.53.192.33` + +--- + +## Firewall + +UFW is enabled on all servers. Standard rules: +- **Core:** 22, 80, 443, 3000, 3001, 3002, 8080, 8082, 8090, 8443, 9090 +- **app1:** 22, 80, 443, 3000, 5678, 8899, 8900, 8901, 8902, 8903 +- **app2:** 22, 80, 443, 3000, 8080, 8082, 8089, 8443, 8843, 3478, 10001, 5000:5150 +- **app3:** 22, 80, 443, 8443 + +--- + +## SSL + +All SSL certificates issued via Let's Encrypt through Caddy. All certs auto-renew. No manual management needed. + +--- + +## Notes + +- **DNS trap:** `itpropartner.com` uses SiteGround nameservers. Cloudflare zone is NOT authoritative. Always verify with `dig NS domain.com` before creating records. +- **SiteGround:** No API access. All DNS changes are manual through SiteGround panel. +- **Provider diversity:** core-bu stays at Hetzner specifically so a netcup outage can't kill both Core and standby simultaneously. +- **app3 MySQL:** Root password in Vaultwarden + `/root/.my.cnf` on app3, accessible via 127.0.0.1:3306. +- **CloudPanel:** SQLite DB at `/home/clp/htdocs/app/data/db.sq3` — users live here, not in MySQL. +- **AWS CLI PATH:** All backup scripts must use `/opt/awscli-venv/bin/aws` or `source /opt/awscli-venv/bin/activate` — `aws` bare fails in cron context (PATH doesn't include venv bin). Documented in server-provisioning-standard v1.3.0. +- **Backup verification:** Always run at least one manual backup after provisioning a server and verify it landed in S3 — never trust cron entries alone. Silent failures (`aws: command not found`, wrong file paths, S3 permission issues) won't surface otherwise. diff --git a/docs-source/itpp-infrastructure/key-inventory.md b/docs-source/itpp-infrastructure/key-inventory.md new file mode 100644 index 0000000..bb34bd8 --- /dev/null +++ b/docs-source/itpp-infrastructure/key-inventory.md @@ -0,0 +1,252 @@ +# IT Pro Partner — Complete Key Inventory + +**Generated:** 2026-07-23 +**Sanitized:** 2026-07-23 (plaintext secrets replaced with storage references) +**Scope:** All SSH keys, API tokens, service credentials, device keys, and passwords across the infrastructure +**⚠️ SENSITIVE:** All credential values live in the listed storage locations. See Hudu for API keys (layout 49). + +--- + +## 1. SSH Keys + +| Key Name | File | Type | Fingerprint (SHA256) | Purpose | Deployed To | +|----------|------|------|-----------------------|---------|-------------| +| **itpp-infra** | `/root/.ssh/itpp-infra` | ED25519 | `Jxh0bbT9dUV3q1DYYB3hHyhy/1TDj7Q8U4xrVmB38uQ` | Universal server admin key | All servers (Core, app1, app2, app3, wphost02, app1-bu, home router) | +| **wisp_rsa** | `/root/.ssh/wisp_rsa` | ED25519 | `MxQw1oh90NibSgN2mDbKP+07/jE4FEUEBbFAzuk5DcI` | WISP MikroTik CCR router SSH | Home CCR router (10.77.0.2 via WireGuard) | +| **germaine-personal** | `/root/.ssh/germaine-personal` | ED25519 | `dDbLH+bdPFcGU0mm1DpGa43ec0nUZ88YnpCi4p63y3I` | Germaine's personal key (from his machines) | Germaine's devices → Core | +| **homelab** | `/root/.ssh/homelab` | ED25519 | `c1nts4wR9EU06/O/k895Pb2tGZublgnGWG6NoQrK/qs` | Homelab Proxmox/QNAP access | vm-host-01, vm-host-02, QNAP NAS | +| **siteground.key** | `/root/.ssh/siteground.key` | RSA (encrypted) | N/A (RSA, encrypted) | SiteGround SFTP backup (port 18765) | SiteGround shared hosting | +| **authorized_keys** | `/root/.ssh/authorized_keys` | — | — | Who can SSH into Core | Core (this server) | + +### SSH Key Details + +``` +itpp-infra.pub: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII4dxTH11aJkBqCY8lXl1kTfZ8yXWhTcthHnt1MtAuIE itpp-infra +wisp_rsa.pub: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDnI4UwwPL8gJvtP/Jr7qiw0Qj/bQBwi2+f03p730xvn wisp-backup +germaine-personal.pub: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID2H/2VMn8i7YSUUpcag6yXiI6nB3T99h7JIOs5/+73r germaine@itppartner +homelab.pub: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHT+727Cti4cZ2x6CiYDeDKZ9BhvCJCzTHlO9vMInHie homelab-itpp +``` + +--- + +## 2. Server Root Passwords + +**Storage:** Hudu (Vaultwarden asset) + `/root/.hermes/.env` (netcup CCP section) + +| Server | IP | Provider | Access | Notes | +|--------|-----|----------|--------|-------| +| **Core** | 152.53.192.33 | netcup RS 2000 | SSH key only | `itpp-infra` key, password auth disabled | +| **app1** | 152.53.36.131 | netcup RS 4000 | Via root or ippadmin+sudo | Root password in Vaultwarden | +| **app2** | 152.53.39.202 | netcup RS 4000 | Via root or ippadmin+sudo | Root password in Vaultwarden | +| **app3** | 152.53.241.111 | netcup RS 4000 | Via root or ippadmin+sudo | Root password in Vaultwarden | +| **app1-bu** | 5.161.114.8 | Hetzner CPX11 | itpp-infra SSH key | Warm standby, offline by default | + +### Admin Account (all servers) + +- **Username:** `ippadmin` +- **Password:** → Vaultwarden entry "ippadmin" +- **Sudo:** Yes (full sudo access) +- **SSH:** Key-based only (`itpp-infra`) + +--- + +## 3. Cloud & Infrastructure API Keys + +**Storage:** All in `~/.hermes/.env` + Hudu API assets (layout 49) + +| Service | Hudu Asset | Storage Location | Status | +|---------|-----------|------------------|--------| +| **Hetzner Cloud** | [177] | `/root/.hermes/scripts/.hetzner_token` + `/root/.hermes/.env` | ✅ Verified Jul 22 | +| **Cloudflare DNS** | [165] | `~/.hermes/.env` → `CLOUDFLARE_API_TOKEN` | ✅ Active (verified by health check) | +| **Wasabi S3** | [176] | `/root/.aws/credentials` | ✅ Active | +| **netcup API** | [166] | `~/.hermes/.env` → `NETCUP_API_KEY` | ✅ Active | +| **netcup CCP** | [167] | `~/.hermes/.env` → `NETCUP_CUSTOMER_NUMBER` + `NETCUP_CCP_PASSWORD` | ✅ Active | +| **Gitea (OLD/DEAD)** | — | ⚠️ **EXPIRED** — still in homelab + itpp-infrastructure remotes | ❌ INVALID (verified Jul 23) | +| **Gitea (ACTIVE)** | — | All other repos + `gitea-backup.sh` — ippadmin | ✅ Active (verified Jul 23) | + +--- + +## 4. AI Provider API Keys + +All stored in `/root/.hermes/.env` and Hudu API assets (layout 49). + +| Provider | Hudu Asset | Purpose | Status | +|----------|-----------|---------|--------| +| **admin-ai** (LiteLLM) | [126] Hermes Primary Key | Primary model gateway (all models) | ✅ Active | +| **Anthropic** | [150] | Claude models | ✅ Active | +| **OpenAI** | [149] | GPT models | ✅ Active | +| **DeepSeek** | [152] | DeepSeek models | ✅ Active | +| **Google Gemini** | [161] / [151] | Gemini models | ✅ Active | +| **xAI (Grok)** | [154] | Grok models | ✅ Active | +| **OpenRouter** | [153] | Multi-provider routing | ✅ Active | +| **Mistral** | [155] | Mistral models | ✅ Active | +| **Groq** | [157] | Fast inference | ✅ Active | +| **Fireworks AI** | [156] | Serverless inference | ✅ Active | +| **Perplexity** | [159] | Search-augmented LLM | ✅ Active | +| **Cohere** | [158] | Cohere models | ✅ Active | +| **AI21 Labs** | [160] | Jurassic models | ✅ Active | +| **MiniMax** | [187] | MiniMax M3 | ✅ Active | +| **Z.ai (GLM)** | [188] | GLM models | ✅ Active | +| **Alibaba Qwen** | [189] Alibaba Qwen (DashScope) | Qwen models | ✅ Active | +| **Deepgram** | [162] | STT (voice transcription) | ✅ Active | + +--- + +## 5. Communication APIs + +**Storage:** All in `~/.hermes/.env` + Hudu API assets (layout 49) + +| Service | Hudu Asset | Storage | Status | +|---------|-----------|---------|--------| +| **Telegram Bot** | [170] | `~/.hermes/.env` → `TELEGRAM_BOT_TOKEN` | ✅ Active | +| **Twilio (Live)** | [184] Twilio Live | `~/.hermes/.env` → `TWILIO_ACCOUNT_SID` + `TWILIO_AUTH_TOKEN` | ✅ Active | +| **Twilio (Test)** | [185] Twilio Test | `~/.hermes/.env` → `TWILIO_TEST_ACCOUNT_SID` + `TWILIO_TEST_AUTH_TOKEN` | ✅ Active | +| **Twilio API Key** | [186] Twilio API Key | `~/.hermes/.env` → `TWILIO_API_KEY_SID` + `TWILIO_API_KEY_SECRET` | ✅ Active | +| **ElevenLabs** | [148] | `~/.hermes/config.yaml` (auxiliary vision / TTS) | ✅ Active | +| **Email SMTP/IMAP** | — | `/root/.config/himalaya/shonuff.pass` | ✅ Active | +| **Email account** | — | `shonuff@germainebrown.com` — MXroute via mail.germainebrown.com:2525 (SMTP) / :993 (IMAP) | ✅ Active | + +--- + +## 6. VoIP / RingLogix + +**Storage:** `~/.hermes/.env` + Hudu API assets + +| Credential | Hudu Asset | Storage | +|-----------|-----------|---------| +| **RingLogix Client ID** | [174] | `~/.hermes/.env` → `RINGLOGIX_CLIENT_ID` | +| **RingLogix Client Secret** | [175] | `~/.hermes/.env` → `RINGLOGIX_CLIENT_SECRET` | +| **RingLogix Username** | — | `~/.hermes/.env` → `RINGLOGIX_USERNAME` | +| **RingLogix Password** | — | `~/.hermes/.env` → `RINGLOGIX_PASSWORD` | +| **RingLogix Domain** | — | `~/.hermes/.env` → `RINGLOGIX_DOMAIN` | + +--- + +## 7. MSP / RMM / Security APIs + +**Storage:** All in `~/.hermes/.env` + Hudu API assets + +| Service | Hudu Asset | Storage | Status | +|---------|-----------|---------|--------| +| **SyncroMSP** | [168] Token + [169] API Key | `~/.hermes/.env` → `SYNCROMSP_API_TOKEN` + `SYNCROMSP_API_KEY` | ✅ Active | +| **Bitdefender GZ** | [172] | `~/.hermes/.env` → `BITDEFENDER_API_KEY` | ✅ Active | +| **VirusTotal** | [171] | `~/.hermes/.env` → `VIRUSTOTAL_API_KEY` | ✅ Active | +| **UISP/UNMS** | [173] | `~/.hermes/.env` → `UISP_API_KEY` | ✅ Active | + +--- + +## 8. Search & Data APIs + +| Service | Hudu Asset | Storage | Status | +|---------|-----------|---------|--------| +| **Firecrawl** | [164] | `~/.hermes/.env` → `FIRECRAWL_API_KEY` | ✅ Active | +| **Exa AI Search** | [163] | `~/.hermes/.env` → `EXA_API_KEY` | ✅ Active | + +--- + +## 9. Database Credentials + +| Database | Host | User | Password Location | Purpose | +|----------|------|------|-------------------|---------| +| **MySQL (apex track)** | 127.0.0.1:33060 (SSH tunnel from wphost02) | `apextrackexperience_1781549652` | `wp-config.php` on wphost02 | Apex Track Experience WordPress | +| **MySQL (CloudPanel)** | app3:3306 | `root` | `/root/.my.cnf` on app3 (also in Vaultwarden) | CloudPanel WordPress hosting | +| **LiteLLM Postgres** | app1 (Docker) | (in docker-compose) | `/root/docker/litellm/docker-compose.yml` on app1 | LiteLLM operational DB | + +--- + +## 10. Docker Services + +| Service | URL | Credential Location | Storage | +|---------|-----|--------------------|---------| +| **Vaultwarden** | vault.itpropartner.com / vault.iamgmb.com | Admin Token → `/root/docker/vaultwarden/.env` on Core | Docker env file | +| **DRE Portal** | portal.debtrecoveryexperts.com | Basic Auth (htpasswd) | `/etc/caddy/dre-passwd` | +| **SearXNG** | (internal, no public endpoint) | (none) | — | +| **DocuSeal** | sign.core.itpropartner.com / sign.iamgmb.com | (none / app-managed) | — | +| **Uptime Kuma** | uptimekuma.itpropartner.com | (app-managed) | — | +| **Open WebUI** | admin-ai.itpropartner.com | `admin@itpropartner.com` (password: ask Sho'Nuff) | Not in .env | +| **Mealie** | recipe.iamgmb.com | `G@germainebrown.com` (password → Vaultwarden) | Vaultwarden | +| **Ops Portal** | ops.itpropartner.com | `ippadmin` (password → `~/.hermes/.env`) | `~/.hermes/.env` | + +--- + +## 11. VPN & Network Keys + +### WireGuard (Core) + +| Item | Value | +|------|-------| +| **Interface** | `wg0` | +| **Core Private Key** | → `/etc/wireguard/wg0.conf` on Core | +| **Core IP** | `10.77.0.1/24` | +| **Listen Port** | `51821` | +| **Home Peer Public Key** | `1fPwdGQ20CxlZCQZQV134olDcE91hfp78yNDeaKJZzg=` | +| **Home Peer Endpoint** | `76.195.7.60:13231` | +| **Routed Networks** | `10.1.0.0/16`, `10.2.0.0/16`, `172.16.1.0/24`, `172.18.18.0/24` | + +### Tailscale + +| Node | IP | Type | Status | +|------|-----|------|--------| +| core | 100.71.155.7 | Linux | ✅ Online | +| app1 | 100.90.186.109 | Linux | ✅ Online | +| app2 | 100.117.164.66 | Linux | ✅ Online | +| app3 | 100.72.15.12 | Linux | ✅ Online | +| app1-bu | 100.112.23.21 | Linux | ⚠️ Offline (7d) | +| iphone-15-pro-max | 100.106.231.86 | iOS | ✅ Online | +| ipp-g-lap | 100.120.64.120 | macOS | ✅ Online | +| m4-mac-mini | 100.116.232.65 | macOS | ✅ Online | + +--- + +## 12. UniFi / UDM Pro Device Keys + +| Site | Key Location | Type | Status | +|------|-------------|------|--------| +| **Grand Lake Club** | UniFi Network Controller → Settings → API | Local Network API Key | ✅ Stored, pending direct verification | +| **Liberty Tire** | UniFi Network Controller → Settings → API | Local Network API Key | ✅ Stored, pending direct verification | + +--- + +## 13. Unknown / Not Found + +The following credentials are known to exist but were not found in the standard locations: + +| Item | Notes | +|------|-------| +| **Open WebUI admin password** | Recovery manual says "in .env or ask Sho'Nuff" — NOT in current .env. Must ask Germaine. | +| **Hudu API key** | In skill docs (`hudu-management`) — used programmatically, not in .env. | +| **Traccar/FleetTracker360 admin** | Not in .env. May be Docker env or app-managed. | +| **Twenty CRM credentials** | Docker on Core, env at `/root/docker/twenty/.env` (not read). | +| **WordPress site DB passwords** | Various sites, typically in `wp-config.php` on wphost02 or app3. | +| **app1-bu root password** | Hetzner CPX11 — accessed via itpp-infra SSH key only. | +| **ComfyUI / Z4** | GPU server allocated for TripFlow — credentials not yet documented. | +| **Home MikroTik admin** | SSH via `admin@10.77.0.2` with `wisp_rsa` key. RouterOS password in router config (not extracted). | + +--- + +## 14. Key Rotation & Audit Notes + +- **Last full audit:** 2026-07-23 +- **Last sanitization:** 2026-07-23 — all plaintext secrets removed; use Hudu + file paths for values +- **Hetzner token:** Rotated Jul 22 (old tokens in Hudu were invalid) +- **Twilio:** Live + test credentials both present in Hudu [184]/[185]/[186] +- **OpenRouter:** Fallback routing key — keep active even if not primary +- **admin-ai:** Primary gateway — all model calls route through this +- **Backups:** All .env + config files included in daily Hermes backup to S3 + +### Recovery Priority + +If Core is lost, you need these to rebuild (in order): +1. `/root/.ssh/itpp-infra` — SSH to all servers +2. `/root/.hermes/.env` — All API keys and secrets +3. `/root/.aws/credentials` — S3 access for backups +4. `/root/.hermes/config.yaml` — Full Hermes config +5. `/root/.config/himalaya/shonuff.pass` — Email access + +### Hudu API Assets (layout 49) + +All API keys are documented as Hudu assets. List them via: +``` +GET https://hudu.itpropartner.com/api/v1/companies/1/assets?page=1&per_page=25 +``` +Filter by `asset_layout_id == 49` to see all API keys with their Hudu asset IDs and storage locations. diff --git a/docs-source/itpp-infrastructure/legal/irs-name-change-letter-2026-07-21.txt b/docs-source/itpp-infrastructure/legal/irs-name-change-letter-2026-07-21.txt new file mode 100644 index 0000000..9f38359 --- /dev/null +++ b/docs-source/itpp-infrastructure/legal/irs-name-change-letter-2026-07-21.txt @@ -0,0 +1,29 @@ +July 21, 2026 + +Department of the Treasury +Internal Revenue Service +Ogden, UT 84201-0027 + +RE: Business Name Change Notification +EIN: [INSERT EIN] +Previous Legal Name: CG Premier Transport LLC +New Legal Name: IT Pro Partner LLC + +To whom it may concern, + +This letter is to notify the Internal Revenue Service of a legal name change for the above-referenced entity. The name change was filed and approved by the Georgia Secretary of State. + +Enclosed: +- Copy of filed Georgia Articles of Amendment confirming the name change from CG Premier Transport LLC to IT Pro Partner LLC +- This notification letter + +Please update your records accordingly. The entity type (LLC) and EIN remain unchanged. All other information — business address, responsible party, and tax classification — remains the same as previously filed. + +If you require additional documentation, please contact me at the address or phone number below. + +Sincerely, + +_______________________________ +[Name of Authorized Member/Officer] +[Title] +[Phone Number] diff --git a/docs-source/itpp-infrastructure/model-chain.md b/docs-source/itpp-infrastructure/model-chain.md new file mode 100644 index 0000000..86a9e24 --- /dev/null +++ b/docs-source/itpp-infrastructure/model-chain.md @@ -0,0 +1,18 @@ +# AI Model Chain — IT Pro Partner + +**Updated:** July 24, 2026 + +## Active Fallback Chain + +| Tier | Model | Provider | Notes | +|---|---|---|---| +| **Primary** | `claude-sonnet-5` | `admin-ai` | Conductor via LiteLLM ($3.33/day cap) | +| **Fallback 1 (F1)** | `deepseek-v4-pro` | `deepseek` | Direct DeepSeek API | +| **Fallback 2 (F2)** | `gpt-5.6-terra` | `admin-ai` | Demoted from Primary via LiteLLM ($3.33/day cap) | +| **Fallback 3 (F3)** | `grok-4.5` (`grok-2-1212`) | `xai` | Direct xAI API | +| **Fallback 4 (F4)** | `gemini-3.6-flash` | `google` | Direct Google API | + +## Admin-AI Virtual Key +- **Key Hash**: `0237186aaff1ed90295c00d73103103900d4bd07252ec1806af8a4358e45d37a` +- **Allowed Models**: `claude-sonnet-5`, `gpt-5.6-terra`, `deepseek-v4-pro`, `deepseek-v4-flash`, `glm-5.2`, `MiniMax-M3`, `qwen3.7-plus` +- **Daily Budget Cap**: $3.33 / day diff --git a/docs-source/itpp-infrastructure/ops-portal/ARCHITECTURE.md b/docs-source/itpp-infrastructure/ops-portal/ARCHITECTURE.md new file mode 100644 index 0000000..c786d6c --- /dev/null +++ b/docs-source/itpp-infrastructure/ops-portal/ARCHITECTURE.md @@ -0,0 +1,112 @@ +# Ops Portal — Architecture + +## Topology + +``` + INTERNET + | + [Caddy :443] + | + Core (152.53.192.33) + | + +---------------+---------------+ + | | | + /api/* :8090 /data/* :files /static/* + | | | + [FastAPI app] ops-status.json [HTML/CSS/JS] + server.py /var/www/ops/ /opt/ops-portal/ + | /data/ static/ + | + +-------+-------+-------+-------+ + | | | | | + S3 API UISP Wazuh Bitdef systemd + (Wasabi) (FFW) (app1) (Cloud) (Core) +``` + +## Data Flow + +``` +[Collector] [Dashboard] + | | + |-- python3 ops-data- | + | collector.py | + | | + v | + S3 buckets ----+ | + UISP API ------+---> ops-status | + Wazuh API -----+ .json ------> GET /api/status + Bitdefender ---+ | + systemd -------+ | + cron jobs -----+ | + v + [Browser renders + health grid, + widgets, alerts] +``` + +## Components + +### 1. Collector (`/root/.hermes/scripts/ops-data-collector.py`) +- Runs every 5 min via cron +- Gathers: S3 backup status (6 buckets), UISP devices (90), Wazuh agents/alerts, Bitdefender endpoints, systemd services, cron jobs, server health, disk/memory/CPU +- Timeout: 90s (was 20s — too short for 94K-file S3 bucket) +- Output: `/var/www/ops/data/ops-status.json` + +### 2. Backend (`/opt/ops-portal/server.py`) +- FastAPI on port 8090 +- 7 API endpoints (health, status, servers, servers/health, audit-log, ft360/status) +- JWT auth from `/root/.hermes/.env` (ADMIN_USERNAME, ADMIN_PASSWORD, JWT_SECRET) +- Critical service restart protection (hermes, caddy, ops-portal blocked) +- Systemd: `ops-portal.service` + +### 3. Frontend (`/opt/ops-portal/static/`) +- 11 HTML pages with shared ops.css, app.js, utils.js +- Auth: login overlay → localStorage JWT → all API calls Bearer +- Auto-refresh: 60s interval + tab visibility API +- Mobile: hamburger toggle with .nav-links.open CSS +- Cache-busting: all assets versioned with timestamps + +### 4. Proxy (Caddy on Core) +- `/` and `/*.html` → static file server from `/opt/ops-portal/static/` +- `/api/*` → reverse_proxy to 127.0.0.1:8090 +- `/data/*` → file server from `/var/www/ops/data/` +- Domain: ops.itpropartner.com + +## Cross-Service Dependencies + +| Dependency | Server | Purpose | Fallback | +|---|---|---|---| +| Wasabi S3 | External | Backup bucket status | Shows "Issues" | +| UISP API | unms.forefrontwireless.com | Device/site count | Shows 0 devices | +| Wazuh | app1 (152.53.36.131) | Agent count, alerts | Shows "Offline" | +| Bitdefender | External API | Endpoint monitoring | Shows "Offline" | +| Traccar | app2 (152.53.39.202) | FleetTracker data | Dedicated endpoint | +| Core systemd | Local | Service health, disk, memory | N/A (local) | + +## Auth Flow + +``` +Browser Server + | | + |-- POST /api/auth/login ->| + | {username, password} | + | |-- Validate against ADMIN_USERNAME/ADMIN_PASSWORD + | |-- Generate JWT with JWT_SECRET + |<- {access_token} --------| + | | + |-- GET /api/status ------->| + | Authorization: Bearer | + | |-- Verify JWT + | |-- Read ops-status.json + |<- {full dashboard} ------| +``` + +## Key Design Decisions + +1. **Collector pattern over direct API calls:** Dashboard fetches one JSON blob rather than 6 separate APIs. Single point of failure but fast rendering and offline-capable (shows last-cached data). + +2. **Python/FastAPI over Node:** Already have Python toolchain on Core. FastAPI is lightweight, async-native, and the ops portal is read-heavy with minimal write paths. + +3. **Static HTML + vanilla JS over React/Vue:** 11-page dashboard with no SPA routing. Auth via localStorage JWT. Zero build step, zero dependencies beyond ops.css. + +4. **JWT over session cookies:** Cross-page auth without server-side session state. Token survives page navigations and ops-portal restarts (persistent JWT_SECRET in .env). diff --git a/docs-source/itpp-infrastructure/ops-portal/CHANGELOG.md b/docs-source/itpp-infrastructure/ops-portal/CHANGELOG.md new file mode 100644 index 0000000..8ab3412 --- /dev/null +++ b/docs-source/itpp-infrastructure/ops-portal/CHANGELOG.md @@ -0,0 +1,39 @@ +# Ops Portal Changelog + +## 2026-07-20 — Major audit and fix session + +### Fixed +- **`/api/health` returning 404:** Caddy `handle_path` was stripping the path → changed to `handle`, port corrected to 8090 +- **`/api/servers` returning null:** Server list now returns all 5 servers with live ping health +- **Server IPs stale:** app1-bu updated to 5.161.225.131, legacy entries removed +- **Page titles inconsistent:** All 11 pages standardized to "X — IT Pro Partner Ops" format +- **Missing nav icons:** All 11 nav items now have SVG icons +- **FleetTracker360 missing from nav:** Added to navigation with car icon +- **Backups page no data:** `s3_buckets` → `s3_backups` key fix +- **FleetTracker360 page no nav:** Added ops.css, app.js, utils.js includes +- **Network page dark sidebar:** Replaced with standard top nav bar +- **Cache-busting broken:** All JS/CSS references now versioned with timestamps +- **Mobile nav broken:** `.nav-links.open` CSS rule missing → hamburger menu now toggles properly on iOS/Android +- **Auth guard race condition:** IIFE scripts replaced with DOMContentLoaded event listeners — pages now load data when user is authenticated +- **Cost page broken:** Missing `loadData` function → defined and wired +- **Dependency diagram 404:** File copied to static dir, link corrected +- **Logs page mangled title:** Triple-nested `` tags from sed accident → cleaned +- **Config page scripts directory:** Now populates directory listing when clicked +- **Services page:** Server column added showing "Core (152.53.192.33)" +- **Dashboard auto-refresh on tab focus:** Visibility API handler added +- **Critical service protection:** hermes, caddy, ops-portal restarts blocked via API + +### Removed +- Duplicate server entries: "app1 (AI Stack)" and "Docker Box (legacy)" +- Server count: 7 → 5 clean entries + +### Changed +- Admin credentials: germaine/itpp2026! → ippadmin (password → Vaultwarden) +- JWT_SECRET made persistent in /root/.hermes/.env to survive restarts +- Collector timeout: 20s → 90s to handle 94K-file S3 bucket scanning + +## Jul 17, 2026 — Initial deployment +- Ops portal deployed on Core as FastAPI app +- Caddy reverse proxy configured +- 10 HTML pages created +- Ops collector built for S3, system health, server status diff --git a/docs-source/itpp-infrastructure/ops-portal/README.md b/docs-source/itpp-infrastructure/ops-portal/README.md new file mode 100644 index 0000000..f4cc046 --- /dev/null +++ b/docs-source/itpp-infrastructure/ops-portal/README.md @@ -0,0 +1,55 @@ +# Ops Portal — ops.itpropartner.com + +## Architecture +- **Server:** Core (152.53.192.33, netcup RS 2000) +- **Backend:** FastAPI at `/opt/ops-portal/server.py` (port 8090) +- **Proxy:** Caddy → reverse_proxy to 127.0.0.1:8090 +- **Static files:** `/opt/ops-portal/static/` — 11 HTML pages, ops.css, app.js, utils.js +- **Auth:** JWT via `POST /api/auth/login`, token in localStorage +- **Data:** `/var/www/ops/data/ops-status.json` (5-min collector refresh) +- **Collector:** `/root/.hermes/scripts/ops-data-collector.py` — Wazuh, Bitdefender, S3, UISP, system health +- **Systemd:** `ops-portal.service`, env from `/root/.hermes/.env` +- **Credentials:** ippadmin (password → Vaultwarden / `~/.hermes/.env`) + +## Pages (11 total) +| Page | Path | Description | +|------|------|-------------| +| Dashboard | / | System health, widgets, audit log | +| Services | /services.html | Systemd service control, audit log, server column | +| Servers | /servers.html | 5 servers with ping health | +| Network | /network.html | UISP data (44 sites, 90 devices), DNS zones | +| Backups | /backups.html | S3 bucket status (6 buckets) | +| FleetTracker | /fleettracker360.html | Traccar device tracking | +| Cron Jobs | /cron.html | Hermes cron jobs with expandable scripts | +| Config | /config.html | Active configs, /root/.hermes/scripts/ listing | +| Logs | /logs.html | Aggregated log viewer | +| Audit | /audit.html | Full audit trail | +| Costs | /cost.html | API cost tracking by model | + +## API Endpoints +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| POST | /api/auth/login | No | JWT authentication | +| GET | /api/health | No | Health check, DB status | +| GET | /api/status | JWT | Full dashboard data (17 sections) | +| GET | /api/servers | JWT | Server list with IPs | +| GET | /api/servers/health | JWT | Ping health (all 7 LIVE) | +| GET | /api/audit-log?limit=N | JWT | Audit trail entries | +| GET | /api/ft360/status | JWT | FleetTracker device data | + +## Critical Services (API restart blocked) +hermes, hermes-assistant, hermes-browser, caddy, ops-portal, mysql-tunnel + +## Dashboard Widgets +- System Health — Core metrics (jobs, disk, memory, S3, APIs) +- Wazuh Security — agent count, alerts +- Bitdefender GravityZone — 9 managed endpoints +- Alerts and Notifications — DR issues, backup failures, cron errors +- Quick Actions — Restart Ops Portal + +## Recovery +``` +systemctl restart ops-portal +systemctl reload caddy +python3 /root/.hermes/scripts/ops-data-collector.py +``` diff --git a/docs-source/itpp-infrastructure/project-log.md b/docs-source/itpp-infrastructure/project-log.md new file mode 100644 index 0000000..d5b1980 --- /dev/null +++ b/docs-source/itpp-infrastructure/project-log.md @@ -0,0 +1,68 @@ +# Project Log — All Completed Projects + +## 2026-07-20 + +### Ops Portal Audit and Overhaul +- Full audit of all 11 pages, 7 API endpoints, and 5 dashboard widgets +- Fixed 15 bugs: auth guards, cache-busting, mobile nav, page titles, missing icons, data keys +- Added 3 new widgets: Wazuh Security, Bitdefender GravityZone, Alerts and Notifications +- Standardized credentials: ippadmin (password → Vaultwarden / `~/.hermes/.env`) +- Added critical service protection (hermes/caddy/ops-portal restart blocked via API) +- Server list cleaned up (7→5), dependency diagram fixed, config page scripts listing + +### Backup-Restore Enhancements +- Added manual backup with domain dropdown and note field +- Added restore history logging with formatted 4-column table +- Fixed Caddy routing and timeouts (restore was returning 404 via proxy) +- Fixed mobile toggle on domain expansion cards +- 9 WordPress sites under daily backup (1 AM and 1 PM) + +### Docs Written +- `/root/projects/ops-portal/README.md` + `CHANGELOG.md` +- `/root/projects/backup-restore/README.md` + `CHANGELOG.md` + +--- + +## 2026-07-17 — Backup-Restore Initial Deployment +- Flask backup/restore app deployed on app3 (152.53.241.111) +- Daily snapshots scheduled at 1 AM and 1 PM +- Caddy reverse proxy from my.itpropartner.com +- 9 WordPress sites configured + +## 2026-07-21 — Home Lab Consolidation + +### Proxmox Migration +- vm-host-02 VMs migrated/destroyed: graylog, zabbix, fog, Ubuntu-Server +- vm-host-01 now hosts: docker-host-01, adguard-home +- vm-host-02 cleared for GPU installation (RTX 3090 pending verification) +- QNAP NFS shared storage created (2TB pool, mounted on both Proxmox hosts) + +### DNS Infrastructure +- Technitium DNS deployed on app2 (dns1.itpropartner.com) +- DoH upstreams: Quad9, Cloudflare, Google +- Home DNS chain: docker-host-01 AdGuard → dns1 Technitium → vm-host-01 AdGuard +- Firewall locked: port 53 restricted to 76.195.7.60 + +### Twilio +- Toll-free number verification submitted for IT Pro Partner +- Use case: customer notifications, appointment reminders, IVR + +### Mattermost +- Branding configured: IT Pro Partner NOC +- Channel structure designed (13 channels) +- Mobile push investigation: HPNS required for background notifications + +### Gift-a-Roast +- Domain giftaroast.com purchased, DNS live (Cloudflare → app1) +- ElevenLabs TTS + Deepgram STT keys verified +- Architecture: Twilio Voice → STT → AI → TTS → caller + +### Uptime Kuma +- Backed up (361MB, 25 monitors), updated to latest + +### IRS +- Name change letter drafted: CG Premier Transport LLC → IT Pro Partner LLC +- Georgia Secretary of State filing confirmed + +### Skills Updated +- 10 skills patched: docker-service-deployment, home-lab-*, server-architecture-plan, twilio-10dlc, vaultwarden-management, voip-portal, hudu, syncromsp, recurring-information-scout diff --git a/docs-source/itpp-infrastructure/projects-master-readme.md b/docs-source/itpp-infrastructure/projects-master-readme.md new file mode 100644 index 0000000..d885346 --- /dev/null +++ b/docs-source/itpp-infrastructure/projects-master-readme.md @@ -0,0 +1,10 @@ +# IT Pro Partner / GMB Projects + +Master index of all internal and client projects. + +- **[Debt Recovery Experts (DRE)](./dre/README.md)**: A specialized debt recovery platform focused on Texas mechanics liens and B2B collections. Handles client intake, compliance with Texas law, fee structures, and deliverables. (IN DEVELOPMENT) +- **[Shark Attack Fantasy League](./shark-game/README.md)**: A fantasy league game where players draft coastal regions and earn points based on real-world shark sightings, bites, and fatalities. (IN DEVELOPMENT) +- **[IT Pro Partner Infrastructure](./itpp-infra/README.md)**: Management of IT Pro Partner server infrastructure, backups, security baselines, and disaster recovery plans. (LIVE) +- **[OSINT People Search](./osint-tool/README.md)**: An Open Source Intelligence tool for performing background checks, compiling data broker reports, and removing personal information. (IN DEVELOPMENT) +- **[Apex Track Experience](./apex-track/README.md)**: Website and operations platform for track day experiences, vehicle registrations, and event logistics. (PLANNED) +- **[BoxPilot Logistics](./boxpilot/README.md)**: Logistics and shipping management platform. (PLANNED) diff --git a/docs-source/itpp-infrastructure/super-search-cf-bypass.md b/docs-source/itpp-infrastructure/super-search-cf-bypass.md new file mode 100644 index 0000000..5a4f806 --- /dev/null +++ b/docs-source/itpp-infrastructure/super-search-cf-bypass.md @@ -0,0 +1,64 @@ +# Super Search — Cloudflare Bypass + +**Added:** July 21, 2026 +**Version:** 2.1.0 + +## Extraction Chain + +| Tier | Provider | What it handles | Fallback Trigger | +|---|---|---|---| +| 1 | Trafilatura | Normal sites | Error OR CF challenge detected | +| 2 | Browserless Chrome | CF-protected sites | Chrome render + Trafilatura | +| 3 | Firecrawl | Everything else | API-based extraction | + +## CF Detection +Nine detection markers for caught challenge pages (from Hound's code + additional): +- cf-turnstile, challenges.cloudflare.com/turnstile +- cf_chl_opt, __cf_chl +- cf-browser-verification, challenge-platform, cf-mitigated +- "Checking your browser", "Just a moment" + +## Infrastructure +- Browserless Chrome on app1 (152.53.36.131), port 3005 +- Caddy proxy: app1:3006 → localhost:3005 +- Firewall: only Core (152.53.192.33) can reach port 3006 +- Super Search: `/root/docker/super-search/server.py` + +## Verify +```bash +# Test CF bypass +cd /root/docker/super-search && source venv/bin/activate +python3 -c " +from server import _extract_one +import asyncio +r = asyncio.run(_extract_one('https://nowsecure.nl')) +print(r['provider']) # Should be 'trafilatura' or 'browserless' +" +``` + +--- + +## v2.1 Features (Added July 21, 2026) + +### 13 Tools +| # | Tool | Description | +|---|---|---| +| 1-10 | Original 10 | Search, extract, lookup, suggest, images | +| 11 | `web_search_fast` | Parallel racing: all providers fire simultaneously | +| 12 | `screenshot` | Browserless Chrome → base64 PNG | +| 13 | `circuit_status` | Provider health states (closed/open/half-open) | + +### Circuit Breaker +- 8 provider circuits: 3 failures → open for 60s +- Prevents hammering dead providers +- Auto-recovers when provider comes back + +### Parallel Racing +- `web_search_fast`: SearXNG ∥ Exa ∥ DuckDuckGo ∥ Wikipedia +- First successful result wins — others cancelled +- Typically 2-3x faster than sequential fallback + +### Screenshot +- Browserless Chrome on port 3006 (Caddy HTTP proxy) +- Full-page or viewport capture +- Base64-encoded PNG in JSON response diff --git a/docs-source/itpp-standards/CHANGELOG.md b/docs-source/itpp-standards/CHANGELOG.md new file mode 100644 index 0000000..8625887 --- /dev/null +++ b/docs-source/itpp-standards/CHANGELOG.md @@ -0,0 +1,8 @@ +# ITPP Standards — CHANGELOG + +## 2026-08-09 — Initial + +- Created repository with documentation templates and CI workflows. +- Added README template, CHANGELOG template, DESIGN.md template, MkDocs config template. +- Added Gitea Actions workflows: docs-check (lint + link check + spell check), docs-publish (aggregated site rebuild). +- Added canonical .gitignore and .markdownlint.json. diff --git a/docs-source/itpp-standards/index.md b/docs-source/itpp-standards/index.md new file mode 100644 index 0000000..b7f9c6a --- /dev/null +++ b/docs-source/itpp-standards/index.md @@ -0,0 +1,40 @@ +# itpp-standards + + +> **Owner:** Germaine | **Status:** LIVE +> **Last Updated:** 2026-08-09 + +Canonical documentation standards, templates, and CI workflows for all IT Pro Partner repositories. Use this repo as a Gitea template when creating new projects, or copy individual templates as needed. + +## What's Inside + +- `templates/` — README, CHANGELOG, DESIGN.md, and MkDocs config templates for new projects +- `templates/gitea-ci/` — Reusable Gitea Actions workflows (docs lint, link check, spell check) +- `.markdownlint.json` — Standard Markdown linting rules for all ITPP repos +- `.gitignore` — Canonical `.gitignore` for Python, Node.js, secrets, and build artifacts + +## Quick Start + +```bash +# Option 1: Use as Gitea template +# In Gitea UI: New Repository → From Template: itpp-standards + +# Option 2: Copy templates into existing repo +git clone https://git.itpropartner.com/ippadmin/<your-repo>.git +cp /path/to/itpp-standards/templates/repo-readme.md <your-repo>/README.md +cp /path/to/itpp-standards/templates/repo-changelog.md <your-repo>/CHANGELOG.md +cp /path/to/itpp-standards/.gitignore <your-repo>/ +cp /path/to/itpp-standards/templates/gitea-ci/docs-check.yml <your-repo>/.gitea/workflows/ +``` + +## Standards + +- **README.md** — Mandatory. Every repo must have one. Minimum 400 bytes. Follow the template. +- **CHANGELOG.md** — Mandatory. Reverse-chronological, user-facing, one line per change. +- **DESIGN.md** — When the project has a public API or multiple consumers. +- **.gitea/workflows/docs-check.yml** — Recommended. Lints Markdown and checks links on push. + +## Related + +- [itpp-infrastructure](https://git.itpropartner.com/ippadmin/itpp-infrastructure) — server inventory, DNS +- [itpp-docs](https://git.itpropartner.com/ippadmin/itpp-docs) — aggregated docs site diff --git a/docs-source/launchcheck/CHANGELOG.md b/docs-source/launchcheck/CHANGELOG.md new file mode 100644 index 0000000..f4e1cbe --- /dev/null +++ b/docs-source/launchcheck/CHANGELOG.md @@ -0,0 +1,7 @@ +# LaunchCheck — CHANGELOG + +## 2026-07-25 — Project Inception + +- Spun startup validation into a separate product from IntelSight. +- Created competitive analysis and business proposal documentation. +- Defined two-audience strategy: startup founders (LaunchCheck) vs enterprise (IntelSight). diff --git a/docs-source/launchcheck/competitive-analysis.md b/docs-source/launchcheck/competitive-analysis.md new file mode 100644 index 0000000..af450ad --- /dev/null +++ b/docs-source/launchcheck/competitive-analysis.md @@ -0,0 +1,670 @@ +# Competitive Analysis: IntelSight & LaunchCheck + +> **Last Updated:** July 2026 +> **Products Analyzed:** IntelSight (enterprise competitive intelligence) + LaunchCheck (startup validation) +> **Shared Platform:** Super Search v2 + Premium APIs + LLM Synthesis + +--- + +## Table of Contents + +1. [Executive Summary](#executive-summary) +2. [Market Context](#market-context) +3. [IntelSight: Competitive Landscape](#intelsight-competitive-landscape) + - [Direct Competitors](#intelsight-direct-competitors) + - [Comparison Table: IntelSight vs Top 5](#intelsight-comparison-table) + - [IntelSight Competitive Positioning](#intelsight-competitive-positioning) +4. [LaunchCheck: Competitive Landscape](#launchcheck-competitive-landscape) + - [Direct Competitors](#launchcheck-direct-competitors) + - [Adjacent Competitors](#launchcheck-adjacent-competitors) + - [Comparison Table: LaunchCheck vs Top 6](#launchcheck-comparison-table) + - [LaunchCheck Competitive Positioning](#launchcheck-competitive-positioning) +5. [Our Unfair Advantages](#our-unfair-advantages) +6. [Why Customers Choose Us](#why-customers-choose-us) +7. [Threat Assessment & Risk Mitigation](#threat-assessment--risk-mitigation) +8. [Strategic Recommendations](#strategic-recommendations) + +--- + +## Executive Summary + +**IntelSight** and **LaunchCheck** operate in two distinct but overlapping markets: enterprise competitive intelligence ($1.46B by 2030, ~20% CAGR per Mordor Intelligence) and startup validation (a fragmented, emerging market with no clear leader). Both products share the same infrastructure backbone — Super Search v2, premium APIs (Crunchbase, Hunter.io), and LLM synthesis via deepseek-v4-pro — giving them structural cost advantages that no single competitor can match. + +**Key finding:** IntelSight competes against incumbents charging 5–20× more (Crayon $20K–$40K/yr, Klue $20K–$40K/yr) while offering uniquely deep OSINT enrichment and LLM-generated strategic content (SWOT reports, battle cards, war room dashboards) they don't. LaunchCheck has **no direct competitor** in the $49/mo "investor-ready validation report" niche — the market is dominated by either enterprise platforms (PitchBook $12K+/yr, CB Insights $60K+/yr) or shallow AI-opinion tools ($5–$129 one-off) that lack source-linked evidence. + +**The platform moat:** Both products benefit from shared infrastructure, enabling a founder-to-enterprise funnel no competitor can replicate. A founder who validates with LaunchCheck today becomes a natural IntelSight customer when their company reaches $2M+ revenue. + +--- + +## Market Context + +### The Competitive Intelligence Market + +The CI tools market is projected to reach **$1.46 billion by 2030**, growing at nearly 20% annually (Mordor Intelligence). Gartner's 2025 Market Guide reclassified the category from "Tools" to "Platforms," signaling maturation. Key dynamics: + +- **68% of B2B deals** now involve at least one direct competitor (Crayon 2025 State of CI) +- **76% YoY growth** in AI adoption within CI teams (Competitive Intelligence Alliance) +- **40% of tech/service providers** predicted to use commercial CI tools by 2026 (Gartner) +- Average CI platform pricing has **risen 15–25% YoY** as incumbents add AI features +- Market is consolidating: Semrush acquired Kompyte ($10M), Meltwater acquired Owler, ZoomInfo acquired Comparably + +### The Startup Validation Market + +This is an **emerging, fragmented market** with no clear category leader. The rise of "vibe coding" (shipping MVPs over a weekend) has made validation more urgent — 43% of startups fail from poor product-market fit (CB Insights). Current options fall into three buckets: + +| Type | Examples | Price Range | Core Limitation | +|---|---|---|---| +| **AI-Opinion Tools** | ValidatorAI, VenturusAI, ChatGPT | Free – $49/mo | LLM training data only; no live sources; can't verify claims | +| **Demand/Pain Evidence** | PainMap, WorthBuild, Trend Seeker | $5 – $199 one-off | Only answers "is there pain?" — doesn't grade business viability | +| **Enterprise Platforms** | PitchBook, CB Insights, Crunchbase | $588 – $70K+/yr | Built for investors, not founders; no validation framework; no deliverables | + +**LaunchCheck's position:** The only tool combining live-sourced evidence with a structured validation framework and investor-ready deliverables at a founder-accessible price ($49/mo). + +--- + +## IntelSight: Competitive Landscape + +### IntelSight Direct Competitors + +#### 1. Crayon — The Incumbent Leader + +**Founded:** 2014 | **Funding:** ~$40M | **HQ:** Boston, MA + +| Dimension | Crayon | IntelSight | +|---|---|---| +| **Pricing** | ~$15K–$100K+/yr (quote-based) | $2,388–$17,988/yr (transparent) | +| **Tiers** | Essentials ($15K–$25K), Professional ($25K–$60K), Enterprise ($60K+) | Pro ($199/mo), Growth ($499/mo), Enterprise ($1,499+/mo) | +| **Core Capability** | Competitor monitoring + battle cards + win/loss | SEO monitoring + review sentiment + Crunchbase + OSINT + battle cards + SWOT + war room | +| **Battle Cards** | Dynamic, automated | LLM-generated, source-linked | +| **OSINT/Enrichment** | Web monitoring, social media | 7-provider search + Hunter.io + Crunchbase + CourtListener + OpenCorporates | +| **AI Features** | AI classification, Win Story Insights | deepseek-v4-pro synthesis across all data sources | +| **CRM Integration** | Salesforce, HubSpot | Planned (API-first architecture) | +| **Target** | Enterprise CI programs | Mid-market ops directors, marketing VPs, strategy teams | + +**Crayon Strengths:** +- Market leader with G2 leader status and 3,000+ customers +- Mature win/loss analysis module +- Deep Salesforce/HubSpot integrations +- Strong brand and analyst recognition (Forrester, Gartner) + +**Crayon Weaknesses:** +- **Prohibitively expensive** for mid-market ($15K minimum ACV) +- No transparent pricing — all custom quotes, long sales cycles +- No OSINT enrichment beyond web/social monitoring +- No Crunchbase or Hunter.io integration +- Battle cards require significant manual curation; AI features lag behind agentic competitors +- Multi-year contracts with 90-day auto-renewal clauses +- No multi-tenant architecture (single organization only) + +**Why IntelSight Wins Against Crayon:** +- **5–10× price advantage** with transparent, predictable pricing +- **OSINT depth** no CI platform matches (7 search providers + CourtListener + OpenCorporates) +- **LLM-generated strategic content** (SWOT reports, war room dashboards) that Crayon requires manual creation for +- **Multi-tenant architecture** for agencies and consultancies — Crayon is single-org only +- **No long-term lock-in** — monthly billing with no multi-year contracts + +--- + +#### 2. Klue — The Battle Card Specialist + +**Founded:** 2015 | **Funding:** $81M (Series B, Tiger Global + Salesforce Ventures) | **HQ:** Vancouver, BC + +| Dimension | Klue | IntelSight | +|---|---|---| +| **Pricing** | ~$20K–$40K+/yr (quote-based; no public pricing) | $2,388–$17,988/yr (transparent) | +| **Core Capability** | CI + Win-Loss analysis, Compete Agent (agentic AI) | Full-spectrum CI + OSINT + strategic deliverables | +| **Battle Cards** | Industry-leading (9.5/10 G2); 10 auto-generated content categories | LLM-generated from multi-source intelligence | +| **Win/Loss** | Full module (AI interviewer, expert interviews, transcript analysis) | Not yet available (roadmap) | +| **OSINT/Enrichment** | Web monitoring, Gong/Teams call analysis, internal docs | 7-provider search + Hunter.io + Crunchbase + CourtListener + OpenCorporates | +| **CRM Integration** | Salesforce, Slack, Gong, Microsoft 365, Teams | Planned (API-first) | +| **Target** | Mid-market to enterprise B2B with dedicated CI/PMM teams | Mid-market ops, strategy, marketing — no dedicated CI headcount required | + +**Klue Strengths:** +- **Compete Agent** (launched July 2025) — the strongest agentic AI in CI, with deal-specific coaching +- **Win-Loss module** is best-in-class; acquired DoubleCheck Research, Goldpan.ai, Ignition +- **250,000+ users** across 1,000+ competitive programs +- Blue-chip customers: Adobe, Shopify, Zendesk, Atlassian, Salesforce, Cisco, Dell, HubSpot +- G2 leader in 4 categories; Forrester Strong Performer +- Strong community (Compete Network) + +**Klue Weaknesses:** +- **No public pricing** — all terms require demo and custom quote +- **Requires dedicated CI/PMM professional** — not self-serve; heavy platform management burden +- **No OSINT depth** — web monitoring only; no Crunchbase, Hunter.io, CourtListener +- **Navigation difficulties** when densely populated with data +- **Annual contracts only** with 90-day auto-renewal +- **No free trial** or free tier +- **No multi-tenant** architecture +- Win/Loss module is a separate product, not included in base CI + +**Why IntelSight Wins Against Klue:** +- **8–16× cheaper** with fully transparent pricing +- **No dedicated CI headcount required** — IntelSight is designed for ops directors and strategy teams who can't justify a full-time CI role +- **OSINT breadth** Klue cannot touch (public records, corporate registries, court documents) +- **Hunter.io email discovery** — Klue has no equivalent +- **Multi-tenant** for agencies holding multiple client competitive landscapes +- **Strategic deliverables** (SWOT, war room) that go beyond Klue's sales-focused battle cards + +--- + +#### 3. SEMrush + Kompyte — The Digital Marketing Giant + +**Founded:** 2008 (SEMrush), 2014 (Kompyte, acquired 2022 for ~$10M) | **Public:** NYSE: SEMR + +| Dimension | SEMrush + Kompyte | IntelSight | +|---|---|---| +| **Pricing** | SEMrush: $117–$456/mo; Kompyte: from $300/yr | $199–$1,499+/mo | +| **Core Capability** | SEO, PPC, content marketing + basic CI via Kompyte | Purpose-built CI + OSINT + strategic intelligence | +| **Competitive Intel** | SEO competitive analysis (keywords, backlinks, traffic); Kompyte adds battle cards and web monitoring | Full CI spectrum: SEO + reviews + Crunchbase + OSINT + LLM synthesis | +| **Battle Cards** | Yes (via Kompyte), CRM-integrated | LLM-generated, source-linked | +| **OSINT/Enrichment** | None beyond web monitoring | Full OSINT stack: 7 providers + Crunchbase + Hunter.io | +| **Target** | Digital marketers, SEO agencies | CI and strategy professionals | + +**SEMrush + Kompyte Strengths:** +- **Best-in-class SEO data** (43 trillion backlinks, 25 billion keywords) +- Public company with massive scale (108,000+ paying customers) +- Kompyte adds affordable CI ($300/yr starting) with CRM-integrated battle cards +- Strong G2 ratings (4.5/5, 2,200+ reviews for SEMrush) +- AI features advancing rapidly (Copilot, content generation) + +**SEMrush + Kompyte Weaknesses:** +- **CI is an add-on, not the core product** — CI features are fragmented across .Trends, Kompyte, and Market Explorer +- **No Crunchbase integration** for funding/market data +- **No OSINT enrichment** beyond digital presence +- Kompyte is a small side business for Semrush ($10M acquisition vs $376M annual revenue) +- Kompyte missing strategy/product/market intelligence use cases (per Contify analysis) +- **No review sentiment analysis** +- **No SWOT reports** or war room dashboards + +**Why IntelSight Wins Against SEMrush + Kompyte:** +- **Purpose-built CI** vs. SEO tool with CI bolted on +- **OSINT depth** — SEMrush knows digital marketing, IntelSight knows company intelligence (funding, reviews, legal, corporate structure) +- **Unified platform** — single dashboard vs. fragmented SEMrush + Kompyte experience +- **Crunchbase integration** — critical funding/market context SEMrush lacks +- **SWOT + War Room** — strategic deliverables SEMrush doesn't produce + +--- + +#### 4. SimilarWeb — The Traffic Intelligence Specialist + +**Founded:** 2007 | **Public:** NYSE: SMWB | **HQ:** Tel Aviv, Israel + +| Dimension | SimilarWeb | IntelSight | +|---|---|---| +| **Pricing** | Starter $149/mo, Professional $399/mo, Enterprise ~$16K+/yr | Pro $199/mo, Growth $499/mo, Enterprise $1,499+/mo | +| **Core Capability** | Website traffic estimation + digital competitive analysis | Full-spectrum CI + OSINT + strategic intelligence | +| **CI Depth** | Traffic sources, audience demographics, engagement metrics | SEO + reviews + funding + OSINT + email discovery + LLM synthesis | +| **Battle Cards** | No | Yes (LLM-generated) | +| **OSINT/Enrichment** | No | Full stack | +| **Target** | Marketing teams, investors, media buyers | CI and strategy professionals | + +**SimilarWeb Strengths:** +- **Unmatched traffic estimation** — no competitor matches their traffic data depth +- Multiple product lines (Web, Sales, App, Shopper Intelligence) +- Public company with institutional credibility +- Strong for investor due diligence + +**SimilarWeb Weaknesses:** +- **One-dimensional** — traffic data only; no funding, reviews, OSINT, email discovery +- **No battle cards** or sales enablement features +- **No SWOT** or strategic deliverables +- Enterprise pricing opaque and expensive ($16K+/yr) +- Free tier extremely limited (5 results, 1 month data) + +**Why IntelSight Wins Against SimilarWeb:** +- **Multi-dimensional intelligence** — traffic is one of many signals IntelSight synthesizes +- **Actionable deliverables** (battle cards, SWOT, war room) vs. raw traffic data +- **Crunchbase + Hunter.io** integration for company/funding intelligence +- **LLM synthesis** turns raw data into strategic insight + +--- + +#### 5. Owler (Meltwater) — The News Aggregator + +**Founded:** 2011 | **Acquired by Meltwater (2021)** | **HQ:** San Mateo, CA + +| Dimension | Owler | IntelSight | +|---|---|---| +| **Pricing** | Community (Free), Pro ($39/mo annual), Enterprise (custom) | $199–$1,499+/mo | +| **Core Capability** | Company news aggregation + basic competitive insights | Full-spectrum CI + OSINT + strategic intelligence | +| **CI Depth** | News alerts, company profiles, competitor graph | 7-provider search + SEO + reviews + Crunchbase + OSINT + email + LLM | +| **Battle Cards** | No | Yes | +| **OSINT/Enrichment** | Basic company data | Full OSINT stack | + +**Owler Strengths:** +- **Free tier** with real value — good for basic company monitoring +- Simple, accessible UX +- Meltwater's media intelligence ecosystem +- AI-powered outreach suggestions (New Owler AI) + +**Owler Weaknesses:** +- **Very shallow CI** — mostly news aggregation; no SEO, reviews, funding, OSINT depth +- No battle cards, SWOT reports, or war room dashboards +- Limited free tier features; Pro at $39/mo still very basic +- UI criticized as difficult to use +- Limitations on number of tracked accounts + +**Why IntelSight Wins Against Owler:** +- Owler is fundamentally a different product category — news alerts vs. comprehensive CI +- IntelSight does everything Owler does plus 10× more depth +- Owler's Pro tier at $39/mo validates that the market wants affordable CI but can't get depth at that price — IntelSight occupies the sweet spot between Owler's shallowness and Crayon/Klue's enterprise pricing + +--- + +### IntelSight Comparison Table + +| Feature | **IntelSight** | Crayon | Klue | SEMrush+Kompyte | SimilarWeb | Owler | +|---|---|---|---|---|---|---| +| **Starting Price (annual)** | **$2,388/yr** | ~$15,000/yr | ~$20,000/yr | $1,404/yr (SEMrush) + $300/yr (Kompyte) | $1,500/yr (Starter) | $468/yr (Pro) | +| **Mid-Tier Price** | **$5,988/yr** | ~$30,000/yr | ~$30,000/yr | $2,978/yr (Pro+) | $4,788/yr (Professional) | — | +| **Enterprise** | **$17,988+/yr** | $60K–$100K+/yr | $40K+/yr | $5,468/yr (Advanced) | $16K+/yr | Custom | +| **Transparent Pricing** | ✅ Yes | ❌ Quote only | ❌ Quote only | ✅ Yes | ⚠️ Partial | ⚠️ Partial | +| **Multi-Tenant** | ✅ Yes | ❌ No | ❌ No | ⚠️ Agency add-on | ❌ No | ❌ No | +| **SEO Monitoring** | ✅ | ❌ | ❌ | ✅ (Best-in-class) | ⚠️ Traffic only | ❌ | +| **Review Sentiment** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | +| **Crunchbase Integration** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | +| **Hunter.io Email Discovery** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | +| **OSINT Enrichment** | ✅ Full (7 providers + CourtListener + OpenCorporates) | ⚠️ Web only | ⚠️ Web + call transcripts | ❌ | ❌ | ⚠️ Basic | +| **Battle Cards** | ✅ LLM-generated | ✅ Dynamic | ✅ Industry-best | ✅ CRM-integrated | ❌ | ❌ | +| **SWOT Reports** | ✅ LLM-generated | ❌ | ❌ | ❌ | ❌ | ❌ | +| **War Room Dashboards** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | +| **Win/Loss Analysis** | ❌ (Roadmap) | ✅ | ✅ Best-in-class | ⚠️ Partner (IcebergIQ) | ❌ | ❌ | +| **CRM Integration** | ⚠️ Planned | ✅ Salesforce, HubSpot | ✅ Salesforce, Slack, Teams, Gong | ✅ Salesforce, HubSpot | ❌ | ✅ Salesforce, HubSpot | +| **No Long-Term Contract** | ✅ Monthly | ❌ Annual minimum | ❌ Annual minimum | ✅ Monthly | ❌ Annual | ✅ Monthly | +| **Free Trial** | ⚠️ Planned | ❌ Demo only | ❌ Demo only | ✅ 7-day | ⚠️ Limited free | ✅ Free tier | +| **G2 Rating** | New | 4.5/5 | 4.8/5 | 4.5/5 (SEMrush) | 4.5/5 | 4.2/5 | + +### IntelSight Competitive Positioning + +**IntelSight occupies a unique position:** enterprise-grade competitive intelligence depth at mid-market pricing. The market is bifurcated between: + +1. **Expensive, deep platforms** (Crayon, Klue: $20K–$100K+/yr) — require dedicated CI teams +2. **Cheap, shallow tools** (Owler: $39/mo, free tiers) — news aggregation only + +IntelSight sits in the **uncontested middle**: $199–$1,499/mo for depth that rivals the enterprise platforms plus unique capabilities (OSINT, Crunchbase, Hunter.io) none of them offer. The multi-tenant architecture opens an additional market (agencies, consultancies) that incumbents structurally cannot serve. + +--- + +## LaunchCheck: Competitive Landscape + +### LaunchCheck Direct Competitors + +LaunchCheck's competitive landscape is unique: there is **no direct competitor** offering a complete, source-linked startup validation report with investor-ready PDFs at $49/mo. The market fragments into enterprise platforms (priced 100–1,000× higher) and shallow AI tools (priced similarly but lacking evidence). Here's the breakdown: + +#### 1. Preuve AI — Closest Feature Competitor + +**Founded:** 2025 | **Pricing:** Free / $29/mo | **Method:** Full viability (10 agents, 50+ live sources) + +| Dimension | Preuve AI | LaunchCheck | +|---|---|---| +| **Pricing** | Free (Reality Check) / $29/mo (Full Report) | **$49/mo** | +| **Method** | 10 AI agents across 50+ live sources | Super Search v2 (7 providers) + Crunchbase + Hunter.io + LLM synthesis | +| **Source-Linked** | ✅ Yes — every claim linked to source | ✅ Yes — all intelligence source-linked | +| **Competitor Mapping** | ✅ With pricing | ✅ With pricing + feature benchmarking | +| **Market Sizing** | ✅ | ✅ | +| **Demand/Pain Signals** | ✅ (Reddit, HN, LinkedIn, etc.) | ✅ (7-provider search) | +| **OSINT Depth** | ⚠️ Web sources only | ✅ Web + CourtListener + OpenCorporates + Crunchbase | +| **Investor-Ready PDF** | Report format, not investor-specific | ✅ Purpose-built investor-ready summary PDF | +| **Feature Benchmarking** | ⚠️ Basic | ✅ Structured comparison tables | +| **Pricing Comparisons** | ✅ | ✅ | +| **Blind Spot Detection** | ✅ (3 pivot directions) | ✅ | +| **Founder Contact Discovery** | ❌ | ✅ Via Hunter.io | + +**Preuve AI Strengths:** +- Excellent source-linking — every claim verifiable +- 50+ live data sources per scan +- Free tier available +- Strong methodology (pain + demand + market size + competitors + blind spots) + +**Preuve AI Weaknesses:** +- One-person operation (founder-built, disclosed on their blog) +- No Crunchbase or OSINT depth (CourtListener, OpenCorporates) +- No email/contact discovery for competitor founders +- Report format, not investor-ready deck +- No multi-product strategy (validation only, no enterprise upsell path) + +**Why LaunchCheck Wins Against Preuve AI:** +- **OSINT depth** — CourtListener, OpenCorporates, and Crunchbase integration provide evidence Preuve can't access +- **Hunter.io integration** — discover competitor founders' contact info +- **Investor-ready PDFs** — formatted for pitch decks, not just research reports +- **Platform ecosystem** — LaunchCheck users graduate to IntelSight (founder-to-enterprise funnel) +- **Crunchbase data** — funding history, investor networks, market signals + +--- + +#### 2. DimeADozen — The AI Business Analyst + +**Founded:** 2023 | **Pricing:** Free / $129+ (one-off reports) | **Method:** AI + web search + +| Dimension | DimeADozen | LaunchCheck | +|---|---|---| +| **Pricing** | Free (basic) / $129+ per report | **$49/mo** (unlimited reports) | +| **Method** | AI analysis + sourced web data | Multi-source search + Crunchbase + Hunter.io + LLM | +| **Source-Linked** | ✅ Yes (web search) | ✅ Yes | +| **Report Format** | Long-form document | Structured validation + investor-ready PDF | +| **OSINT** | ❌ | ✅ | +| **Subscription Model** | ❌ One-off reports | ✅ Monthly unlimited | + +**DimeADozen Weaknesses:** +- Per-report pricing ($129+) — expensive if validating multiple ideas +- No OSINT or structured company data +- Document format, not investor deliverables +- No Crunchbase or Hunter.io + +**Why LaunchCheck Wins:** +- **$49/mo unlimited** vs. $129+/report — 3× cheaper if validating even one idea per month +- **Subscription model** matches founder behavior (iterate and re-validate) +- **Investor-ready deliverables** built for the fundraising journey +- **OSINT + Crunchbase** depth + +--- + +#### 3. ValidatorAI & VenturusAI — AI-Opinion Tools + +**ValidatorAI:** Free / $49 | **VenturusAI:** Free / paid tiers + +These tools represent the "AI opinion" category — they run your idea through an LLM and return analysis based on training data only. No live sources, no verification. + +**Their Weaknesses (shared):** +- **No live data** — output is LLM training data, not real market evidence +- **Can't verify claims** — may hallucinate competitors or market sizes +- **No Crunchbase, OSINT, or structured data** +- **"Confident but wrong"** problem — sounds authoritative but unverifiable + +**Why LaunchCheck Wins:** +- **Every claim source-linked** — founders can verify, investors trust +- **Real-time data** from 7 search providers + premium APIs +- **Evidence over opinion** — the fundamental differentiator + +--- + +### LaunchCheck Adjacent Competitors + +These aren't direct competitors (they don't offer validation reports), but they compete for the same founder wallet share and attention: + +#### 4. Crunchbase (Free/Pro) — The Company Database + +**Pricing:** Free / Pro $49/mo annual ($588/yr) / Business $199/mo annual ($2,388/yr) + +**What it does:** Database of 4M+ companies with funding rounds, investors, news. + +**Why it's NOT a LaunchCheck competitor:** +- **Research tool, not validation tool** — you see data, you don't get analysis +- **No competitive landscape snapshots** — you build them manually +- **No pricing comparisons or feature benchmarking** +- **No OSINT dossiers** +- **No investor-ready PDFs** — raw data exports, not formatted deliverables +- **No validation framework** — no grading, no go/no-go verdict + +**However:** Crunchbase's free tier provides basic company lookups that some founders use for DIY research. LaunchCheck integrates Crunchbase data into a structured validation — making the data actionable rather than raw. + +#### 5. PitchBook — The Institutional Database + +**Pricing:** $12,000–$70,000+/yr (quote-based) + +**What it does:** Deep private market data for VCs, PE firms, investment banks. + +**Why it's NOT a LaunchCheck competitor:** +- **100–1,400× more expensive** — irrelevant to solo founders +- **No validation framework** +- **Built for deal sourcing**, not startup validation +- **No OSINT or competitive analysis features** + +#### 6. CB Insights — The Research Platform + +**Pricing:** ~$60,000+/yr (quote-based, per Vendr data) + +**What it does:** Technology market intelligence, startup scoring, industry analysis. + +**Why it's NOT a LaunchCheck competitor:** +- **1,200× more expensive** — enterprise-only +- **No founder-facing validation product** +- **Built for corporate strategy and VC**, not pre-revenue founders + +--- + +### LaunchCheck Comparison Table + +| Feature | **LaunchCheck** | Preuve AI | DimeADozen | ValidatorAI | Crunchbase Pro | PitchBook | +|---|---|---|---|---|---|---| +| **Pricing** | **$49/mo** | Free / $29/mo | Free / $129+/report | Free / $49 | $49/mo ($588/yr) | $12K–$70K+/yr | +| **Subscription** | ✅ Unlimited reports | ✅ Monthly | ❌ Per-report | ✅ Monthly | ✅ Annual | ✅ Annual | +| **Live Data Sources** | ✅ 7+ providers | ✅ 50+ web sources | ✅ Web search | ❌ AI training data only | ✅ Database | ✅ Database | +| **Source-Linked Claims** | ✅ | ✅ | ✅ | ❌ | N/A (raw data) | N/A (raw data) | +| **Competitive Landscape** | ✅ With pricing + feature benchmarking | ✅ With pricing | ✅ | ⚠️ May hallucinate | ❌ Manual only | ✅ Manual only | +| **OSINT Enrichment** | ✅ CourtListener + OpenCorporates | ❌ | ❌ | ❌ | ❌ | ❌ | +| **Crunchbase Integration** | ✅ | ❌ | ❌ | ❌ | ✅ (native) | ❌ | +| **Hunter.io Email Discovery** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | +| **Investor-Ready PDF** | ✅ Purpose-built | ⚠️ Report format | ⚠️ Long document | ❌ | ❌ | ❌ | +| **Feature Benchmarking** | ✅ | ⚠️ Basic | ❌ | ❌ | ❌ | ❌ | +| **Validation Score** | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | +| **Blind Spot Detection** | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | +| **Free Tier** | ⚠️ Planned | ✅ | ✅ | ✅ | ✅ Limited | ❌ Trial only | +| **Path to Enterprise** | ✅ IntelSight upsell | ❌ | ❌ | ❌ | N/A | N/A | + +### LaunchCheck Competitive Positioning + +**LaunchCheck is the only tool that combines:** + +1. **Live, source-linked evidence** (not AI hallucination) +2. **Structured validation framework** (not raw data) +3. **OSINT depth** (CourtListener, OpenCorporates — unique competitive signal) +4. **Investor-ready deliverables** (formatted PDFs, not research reports) +5. **Founder-accessible pricing** ($49/mo — less than most SaaS tools) + +The market is bifurcated between tools that are cheap but unverifiable (ValidatorAI, VenturusAI, ChatGPT) and tools that are data-rich but built for investors, not founders (PitchBook, CB Insights, Crunchbase). LaunchCheck bridges this gap: evidence-grade validation at founder prices. + +--- + +## Our Unfair Advantages + +### 1. LLM Synthesis Across Multi-Source Intelligence + +**What it is:** deepseek-v4-pro synthesizes intelligence from 7 search providers + Crunchbase + Hunter.io + CourtListener + OpenCorporates into structured deliverables (SWOT reports, battle cards, war room dashboards, validation reports). + +**Why competitors can't match it:** +- **Crayon/Klue:** AI is limited to classification and summarization of web monitoring data. They don't have multi-provider search architecture or OSINT integration. +- **SEMrush:** AI is SEO-focused (content generation, keyword analysis). No general-purpose intelligence synthesis. +- **Preuve AI/DimeADozen:** Use LLM for analysis but lack our data breadth (no Crunchbase, no OSINT, no Hunter.io). +- **Owler:** Basic AI for outreach personalization, not intelligence synthesis. + +**The moat:** LLM synthesis is only as good as the data fed into it. Our 7-provider search architecture + premium APIs creates a data moat that's expensive and time-consuming to replicate. + +### 2. OSINT Depth No CI Platform Matches + +**Our OSINT stack:** +- **SearXNG** — Metasearch across dozens of engines +- **Exa** — Semantic web search with domain filtering +- **OpenCorporates** — Global company registry data (200M+ companies) +- **CourtListener** — US court records and legal filings +- **DuckDuckGo** — Privacy-preserving web search +- **Wikipedia** — Structured encyclopedic data +- **Firecrawl** — Deep web page extraction + +**Why it matters:** +- CourtListener reveals lawsuits, IP disputes, regulatory actions — competitive signals no CI platform captures +- OpenCorporates reveals corporate structure, subsidiaries, shell companies — illuminates competitor org charts +- This is the kind of intelligence that traditionally required expensive boutique research firms ($500–$2,000/report) + +### 3. Lower Cost Structure — The Infrastructure Advantage + +**Our monthly infrastructure costs:** + +| Component | Monthly Cost | +|---|---| +| Super Search v2 (7 providers) | Self-hosted (netcup VPS) | +| Crunchbase API | $49/mo | +| Hunter.io API | $34/mo | +| deepseek-v4-pro (LLM) | Usage-based API | +| Netcup VPS hosting | ~$30–$50/mo | +| **Total infrastructure** | **~$150–$200/mo** | + +**Competitor cost structures (estimated):** +- **Crayon/Klue:** $81M+ funding, 200–500+ employees, massive sales orgs — must charge $20K+/yr to sustain +- **SEMrush:** Public company, 1,300+ employees, massive marketing spend +- **PitchBook/CB Insights:** Large analyst teams, expensive data licensing, enterprise sales + +**Our advantage:** 1–2 person operation with automated intelligence gathering can serve hundreds of customers at 10–20× lower price points while maintaining healthy margins. + +### 4. The Founder-to-Enterprise Funnel + +**No competitor has this:** + +``` +LaunchCheck ($49/mo) → Solo founder validates idea + ↓ +Founder builds company, reaches $2M+ revenue + ↓ +IntelSight (Pro $199/mo) → Now needs competitive intelligence + ↓ +Company grows, needs more → IntelSight Growth ($499/mo) + ↓ +Scales to enterprise → IntelSight Enterprise ($1,499+/mo) +``` + +**The math:** +- A LaunchCheck user today could become a $18K+/yr IntelSight Enterprise customer in 3–5 years +- Zero customer acquisition cost for the upsell — they're already in the platform +- Competitors: Crayon/Klue acquire enterprise customers through expensive outbound sales ($1,000–$3,000 CAC). PitchBook/CB Insights have no founder product at all. + +**This is a structural advantage.** No competitor has a product for pre-revenue founders AND enterprise strategy teams. The shared infrastructure makes serving both segments economically viable. + +### 5. Multi-Tenant Architecture + +**IntelSight is multi-tenant** — agencies, consultancies, and holding companies can manage competitive landscapes for multiple clients from a single account. + +**Why competitors can't:** +- **Crayon, Klue, SimilarWeb, Owler** — all single-organization +- **SEMrush** — has agency features but not for CI specifically +- **PitchBook, CB Insights** — single-org licensing with strict seat enforcement + +**Market opportunity:** The 25,000+ US management consultancies and 14,000+ digital agencies need competitive intelligence for their clients. Multi-tenant IntelSight opens a market incumbents structurally cannot address. + +### 6. Transparent, Predictable Pricing + +Every enterprise CI competitor (Crayon, Klue, PitchBook, CB Insights) uses opaque, quote-based pricing. This creates: + +- **Friction:** 2–6 week sales cycles for basic pricing information +- **Distrust:** Buyers suspect they're being charged what they can pay, not what the product costs +- **Budget barriers:** Mid-market companies can't get pricing without a sales call — many don't bother + +**Our transparent pricing** ($199, $499, $1,499/mo) removes friction, builds trust, and captures the mid-market that incumbents underserve. + +--- + +## Why Customers Choose Us + +### For IntelSight vs. Enterprise CI Platforms + +| Decision Factor | Why IntelSight | +|---|---| +| **Budget** | "I need CI depth but can't justify $20K+/yr for Crayon or Klue. IntelSight at $199–$499/mo fits my ops budget without board approval." | +| **No CI Team** | "I run strategy/ops for a 50-person company. I don't have a dedicated CI person. IntelSight gives me intelligence without a platform manager." | +| **OSINT Depth** | "I need to know about competitor lawsuits, corporate structure, and public records — not just their website changes. IntelSight finds things Crayon misses." | +| **Multi-Client** | "I'm a consultant with 8 clients. I need one platform to manage all their competitive landscapes. Crayon would charge me 8×." | +| **Deliverables** | "I need SWOT reports and war room dashboards for quarterly board meetings. IntelSight generates them; Crayon gives me raw alerts." | +| **No Lock-In** | "I want monthly billing and the ability to cancel. Crayon and Klue require annual contracts." | + +### For LaunchCheck vs. Validation Tools + +| Decision Factor | Why LaunchCheck | +|---|---| +| **Evidence Over Opinion** | "I need to show my co-founder real data, not AI guesses. LaunchCheck links every claim to its source." | +| **Investor-Ready** | "I'm pitching in two weeks and need a professional competitive landscape to include in my deck. LaunchCheck generates the PDF I need." | +| **Affordable Iteration** | "I'm testing 3 ideas this month. At $49/mo unlimited, I can validate all of them. DimeADozen would cost me $387." | +| **OSINT Intel** | "I want to know if my competitors have legal troubles or hidden corporate structures. No other validation tool shows me that." | +| **Path Forward** | "I'm validating now, but when my company grows I'll need real CI. LaunchCheck's IntelSight integration means I won't outgrow the platform." | +| **Contact Discovery** | "I need to reach competitor founders for customer discovery interviews. Hunter.io integration finds their emails." | + +--- + +## Threat Assessment & Risk Mitigation + +### Threat 1: Incumbents Add OSINT/API Integrations + +**Risk:** Crayon or Klue partner with Crunchbase, add CourtListener, or build multi-provider search. + +**Likelihood:** Low–Medium. These companies are focused on sales enablement and CRM integration depth, not intelligence breadth. Their product roadmaps center on win/loss, deal coaching, and conversation intelligence — not OSINT. + +**Mitigation:** +- Move fast to establish OSINT depth as a brand differentiator +- Build proprietary intelligence models (competitor scoring algorithms) that improve with data volume +- Lock in Crunchbase/Hunter.io integrations as platform features before incumbents notice + +### Threat 2: SEMrush Expands CI Features + +**Risk:** SEMrush deepens Kompyte integration and adds company intelligence features. + +**Likelihood:** Medium. Kompyte's roadmap shows they're moving toward broader CI, and SEMrush has the resources to build. However, SEMrush's DNA is digital marketing, not corporate intelligence. + +**Mitigation:** +- Double down on what SEMrush can't do: OSINT, Crunchbase, court records, email discovery +- Position IntelSight as "intelligence for strategy teams" vs. "CI for marketers" +- Maintain pricing advantage (SEMrush Advanced + Kompyte is already $5,468+/yr) + +### Threat 3: PitchBook or Crunchbase Launch a Founder Product + +**Risk:** Either builds a lightweight validation tool at $50–$100/mo. + +**Likelihood:** Low for PitchBook (institutional DNA, $618M ARR from 10,600 accounts — they optimize for whales). Medium for Crunchbase (already has Pro at $49/mo, could add validation features). + +**Mitigation:** +- Crunchbase Pro is a data tool, not a validation tool — we integrate them, we don't compete +- Build proprietary validation methodology (scoring algorithms, report formats) that raw data access can't replicate +- Establish brand as "the validation platform" before Crunchbase enters + +### Threat 4: Free AI Tools Improve + +**Risk:** ChatGPT/Claude with web search become good enough for DIY validation. + +**Likelihood:** Medium-High. AI models are rapidly improving and adding search capabilities. + +**Mitigation:** +- **Source-linking is our moat** — even with search, raw LLM outputs aren't verifiable without structured sourcing +- **Investor-ready formatting** — raw AI chat can't produce pitch-ready PDFs +- **Structured methodology** — our validation framework provides consistency that ad-hoc AI queries can't +- **OSINT depth** — CourtListener and OpenCorporates aren't indexed by general web search + +--- + +## Strategic Recommendations + +### Near-Term (0–6 Months) + +1. **Ship CRM integrations** (Salesforce, HubSpot) — this is the #1 objection from Crayon/Klue evaluators +2. **Launch free tier for LaunchCheck** — capture top-of-funnel founders before Preuve AI/DimeADozen +3. **Publish comparison pages** on intelsight.io and launchcheck.io referencing this analysis +4. **Build win/loss analysis** for IntelSight Enterprise — close the feature gap with Klue +5. **Content marketing:** "Why OSINT matters in competitive intelligence" — establish category authority + +### Medium-Term (6–12 Months) + +6. **Launch the founder-to-enterprise funnel** — automated upsell path from LaunchCheck to IntelSight +7. **Agency/consultancy program** for IntelSight multi-tenant — target 14,000+ digital agencies +8. **API access** for IntelSight Enterprise — enable custom integrations and data export +9. **Competitor scoring algorithm** — proprietary ML model trained on multi-source intelligence data +10. **LaunchCheck investor matching** — connect validated founders with relevant VCs (Crunchbase-powered) + +### Long-Term (12–24 Months) + +11. **International expansion** — multi-language OSINT and local data sources (EU business registries, UK Companies House, APAC equivalents) +12. **Data-as-a-Service** — sell enriched competitive intelligence datasets to enterprises +13. **M&A target positioning** — build enough OSINT depth and customer base to become an attractive acquisition for a CRM or sales intelligence platform + +--- + +## Appendix: Methodology + +This analysis is based on: + +- **Primary research:** Direct extraction of pricing pages, feature documentation, and product marketing from all named competitors (July 2026) +- **Secondary sources:** G2 reviews, Gartner Peer Insights, TrustRadius, Vendr pricing data, Capterra, and industry analyst reports +- **Market data:** Mordor Intelligence CI Tools Market Report, Crayon State of Competitive Intelligence 2025, Gartner Market Guide 2025 +- **Pricing data:** Verified via official pricing pages where available; estimated via Vendr, TrustRadius, and third-party analysis for quote-based vendors + +Pricing is current as of July 2026 and subject to change. All competitive assessments are based on publicly available information and do not reflect proprietary knowledge of competitor roadmaps or internal strategies. + +--- + +> **Document maintained by:** IntelSight / LaunchCheck Strategy Team +> **Next review:** January 2027 diff --git a/docs-source/launchcheck/index.md b/docs-source/launchcheck/index.md new file mode 100644 index 0000000..2d12d82 --- /dev/null +++ b/docs-source/launchcheck/index.md @@ -0,0 +1,34 @@ +# launchcheck + + +> **Owner:** Germaine | **Status:** PLANNED +> **Last Updated:** 2026-08-09 +> **Tagline:** Validate your idea before you build it. + +LaunchCheck is a startup validation SaaS built for pre-revenue founders who need to understand their competitive landscape before writing code. It delivers competitive landscape reports, pricing comparisons, feature benchmarking, OSINT dossiers on key competitors, and investor-ready summary PDFs — powered by the Super Search v2 engine that drives IntelSight, IT Pro Partner's enterprise-grade intelligence platform. + +## What's Inside + +- `docs/` — Product documentation and research + - `competitive-analysis.md` — Full competitive landscape for IntelSight (enterprise CI) and LaunchCheck (startup validation) + - `launchcheck-business-proposal.md` — Business proposal with market analysis, revenue model, GTM strategy, and financial projections + +## Tech Stack + +- **Platform:** IntelSight shared infrastructure — Super Search v2 + Premium APIs + LLM Synthesis +- **Backend:** TBD — FastAPI or equivalent +- **Frontend:** TBD +- **Target Pricing:** $49/month + +## Quick Start + +```bash +git clone https://git.itpropartner.com/ippadmin/launchcheck.git +cd launchcheck +# Review docs/ for product spec and competitive analysis +``` + +## Related + +- [itpp-infrastructure](https://git.itpropartner.com/ippadmin/itpp-infrastructure) — server inventory, DNS +- [itpp-standards](https://git.itpropartner.com/ippadmin/itpp-standards) — docs standards and templates diff --git a/docs-source/launchcheck/launchcheck-business-proposal.md b/docs-source/launchcheck/launchcheck-business-proposal.md new file mode 100644 index 0000000..35f0878 --- /dev/null +++ b/docs-source/launchcheck/launchcheck-business-proposal.md @@ -0,0 +1,561 @@ +# LaunchCheck — Business Proposal + +**Prepared for:** Germaine Brown & Advisory Team +**Product:** LaunchCheck (launchcheck.io) — "Validate your idea before you build it" +**Company:** IT Pro Partner (established MSP) +**Date:** July 25, 2026 +**Classification:** Confidential — Advisory Team Only + +--- + +## Table of Contents + +1. [Executive Summary](#1-executive-summary) +2. [Elevator Pitch](#2-elevator-pitch) +3. [Problem Statement](#3-problem-statement) +4. [Market Analysis](#4-market-analysis) +5. [Product Overview](#5-product-overview) +6. [Revenue Model](#6-revenue-model) +7. [Competitive Advantages](#7-competitive-advantages) +8. [Go-to-Market Strategy](#8-go-to-market-strategy) +9. [Risk Analysis](#9-risk-analysis) +10. [Financial Projections](#10-financial-projections) +11. [The Upgrade Path](#11-the-upgrade-path) +12. [The Ask](#12-the-ask) + +--- + +## 1. Executive Summary + +LaunchCheck is a startup validation SaaS built for pre-revenue founders who need to understand their competitive landscape **before they write a single line of code**. Priced at $49/month, it delivers competitive landscape reports, pricing comparisons, feature benchmarking, OSINT dossiers on key competitors, and investor-ready summary PDFs — all powered by the same Super Search v2 engine that drives IntelSight, IT Pro Partner's enterprise-grade intelligence platform. + +**The core insight:** 42% of startups fail because there is no market need (CB Insights). The overwhelming majority of those failures are preventable with structured competitive intelligence early in the ideation phase. Yet existing validation tools are either too shallow (single-page AI summaries), too expensive (enterprise platforms starting at $199+/month), or too manual (founders Googling competitors one at a time). LaunchCheck sits in the whitespace: professional-grade competitive intelligence at a price a pre-revenue founder can afford. + +**Why now:** The US saw a record 5.5 million new business applications in 2023, Y Combinator processed 96,000+ applications for roughly 620 spots in 2025 (0.7% acceptance rate), and the global SaaS market is $322 billion growing at 19.3% CAGR. More people are starting companies than ever before — and more of them are failing for reasons that structured competitive intelligence could prevent. + +**The unit economics are compelling.** At $49/month, LaunchCheck is profitable on Day 1 customer 1. The Super Search v2 infrastructure already exists and is amortized across IntelSight. The remaining build is a React frontend, a FastAPI wrapper, Stripe integration, and a Postgres schema — roughly 80% of the engine is already operational on app1. At 1,000 customers, LaunchCheck generates $588,000 ARR with near-zero incremental infrastructure cost. + +**The strategic value extends beyond direct revenue.** LaunchCheck is the top of a deliberate funnel: founders who validate with LaunchCheck and then raise funding or achieve product-market fit naturally graduate to IntelSight Pro ($199/mo), Growth ($499/mo), or Enterprise ($1,499+/mo). The customer acquisition cost for IntelSight's higher tiers drops dramatically when the lead arrives pre-qualified through LaunchCheck. + +--- + +## 2. Elevator Pitch + +> **For founders sitting on an idea they can't stop thinking about:** LaunchCheck tells you who your competitors are, what they charge, what features they have that you don't, and whether there's room for you — in a single report you can take to investors. $49. One month. Know before you build. + +> You wouldn't open a restaurant without knowing what's on the next block. Don't build a startup without knowing your competitive landscape. LaunchCheck gives you the intelligence that used to cost thousands — for $49/month. Cancel anytime, but most founders keep it until they close their first round. + +--- + +## 3. Problem Statement + +### 3.1 The Founder Pain Point + +Every founder faces the same moment of terror: *"What if someone's already built this?"* Right now, the answer to that question comes through one of three paths, all broken: + +| Method | What Founders Do | Why It Fails | +|--------|-----------------|--------------| +| **Google manually** | Search "[idea] competitor" and skim 20 tabs | Incomplete, unstructured, takes days, misses pricing/feature gaps | +| **Free AI validators** | Paste idea into ValidatorAI or ChatGPT | Shallow, hallucinates market data, no verifiable sources, zero investor credibility | +| **Enterprise tools** | Try Crunchbase, CB Insights, PitchBook | $99–$25,000/year, built for funded companies and VCs, absurd overkill for pre-revenue | + +The result: founders either build blind (and join the 42% who fail from "no market need") or waste 2–3 weeks doing manual competitive research they could have outsourced for $49. + +### 3.2 The Numbers That Make This Urgent + +- **5.5 million** new business applications were filed in the US in 2023 — an all-time record. +- **22.1%** of new US businesses close within their first year (Bureau of Labor Statistics, 2024 data). +- **48.6%** are gone within five years. **65.3%** within ten years. +- **42%** of VC-backed startup failures cite "no market need" as the primary cause (CB Insights, analysis of 431 failures). +- **63%** of tech startups fail within five years — worse than the overall average. +- **96,000+** founders applied to Y Combinator in 2025. **620** got in (0.65% acceptance rate). +- A solo founder spending 6 months building an unvalidated product burns **$30,000–$60,000** in opportunity cost. LaunchCheck costs $49. + +### 3.3 The Four Founder Personas + +| Persona | Situation | What They Need | Willingness to Pay | +|---------|-----------|---------------|-------------------| +| **Solo Founder** | Has an idea, pre-revenue, pre-funding | Confidence to commit weekends for 6 months | $49 feels cheap compared to wasted time | +| **Indie Hacker** | Launching a side project, wants recurring revenue | Competitive landscape to avoid saturated niches | $49/month is a line-item expense they'll justify | +| **Pre-Seed Team** | Preparing a pitch deck, needs the "competition" slide | Investor-ready competitive analysis in PDF | $49 is a rounding error on a $500K raise | +| **YC/Techstars Applicant** | Needs to show market awareness | Structured competitive data with named comparables | $49 to improve 0.7% odds is a no-brainer | + +--- + +## 4. Market Analysis + +### 4.1 Total Addressable Market (TAM) + +**Definition:** Every person globally who starts a business and needs competitive intelligence. + +- **US new business applications:** 5.3–5.5 million annually (Census Bureau, 2023–2024). +- **Global new business formation:** Estimated 100+ million new enterprises annually across all countries (World Bank entrepreneurship data). Narrowing to English-speaking and tech-adjacent markets: conservatively **15–20 million new founders per year**. +- **YC/accelerator applicants:** 96,000+ to YC alone in 2025. Techstars processes tens of thousands more. 500 Global adds thousands. Combined accelerator ecosystem: **200,000–300,000 serious applicants annually**. +- **Indie Hackers community:** 100,000+ on r/indiehackers alone; broader indie maker movement estimated at **500,000–1,000,000 active builders** across platforms (Indie Hackers, Makerlog, Product Hunt makers, Twitter/X #buildinpublic). + +**TAM Estimate:** 3–5 million serious, tech-savvy founders annually who could benefit from structured competitive validation. At $49/month (annualized $588/year), that's a **$1.7–$2.9 billion annual market** if every founder paid for one month of validation. + +### 4.2 Serviceable Addressable Market (SAM) + +**Definition:** Founders who (a) are building tech or tech-enabled products, (b) operate in English, (c) have $49 of disposable cash, and (d) are actively seeking validation before building. + +- US-based tech founders: ~500,000–800,000 annually (subset of 5.5M business applications that are tech/software). +- Accelerator applicants (YC, Techstars, 500 Global, AngelPad, etc.): ~300,000 annually. +- Indie hackers building SaaS/MRR products: ~200,000–400,000 active at any time. +- Global English-speaking tech founders: ~500,000–1,000,000 additional. + +**SAM Estimate:** **1.0–2.0 million founders** annually. At $588/year average revenue (accounting for churn), this represents a **$588M–$1.18B SAM**. + +### 4.3 Serviceable Obtainable Market (SOM) + +**Definition:** The portion of SAM LaunchCheck can realistically capture in years 1–3 through community-driven growth and Product Hunt. + +**Benchmarks from comparable products:** + +| Comparable | Early Traction | Notes | +|-----------|---------------|-------| +| IdeaProof | 68,342+ founders since launch | Credit-based model, free tier drives growth | +| ValidatorAI | Hundreds of thousands of validation sessions | Free, single-page AI summary | +| ValidateMySaaS | Steady niche growth | $19–$29/mo, competitor-focused | +| Trend Seeker | Growing community, $9.99/mo | Demand-based, different angle | + +**LaunchCheck Year 1 SOM:** If LaunchCheck captures 0.05%–0.1% of the SAM in Year 1: +- Conservative: **500–1,000 paying customers** by Month 12 +- Optimistic: **1,500–2,500 paying customers** with Product Hunt tailwinds + +At $49/month ($588/year annualized), Year 1 ARR exit: +- Conservative: **$294K–$588K ARR** +- Base case: **$588K–$1.18M ARR** +- Optimistic: **$882K–$1.47M ARR** + +### 4.4 Market Timing + +Five structural tailwinds make now the right moment: + +1. **Record business formation:** 5.5M new applications in 2023 is 50%+ above pre-pandemic levels. The "great entrepreneurship boom" is not slowing. +2. **AI validation is a recognized category:** Founders now expect AI-powered tools for competitive research. The category has been validated by IdeaProof, ValidatorAI, and WorthBuild. +3. **YC is harder than ever:** 0.65% acceptance rate means founders need every edge. A competitive landscape report improves the "What do you know about your market?" section of the application. +4. **"Build in public" culture:** Indie hackers publicly share their journey. LaunchCheck reports become shareable content — "Here's what LaunchCheck found about my idea space." +5. **Enterprise SaaS fatigue:** Founders are rejecting $199+/month tools. There's a clear whitespace for professional-grade intelligence at indie-friendly pricing. + +--- + +## 5. Product Overview + +### 5.1 Core Features + +LaunchCheck is built on the **Super Search v2** MCP server already operational on app1 — the same infrastructure powering IntelSight ($199–$1,499/mo). The core differentiator: this is not an AI hallucinating about your market. It's 7 search providers feeding an LLM synthesis engine producing structured, verifiable competitive intelligence. + +| Feature | Description | Frequency | +|---------|-------------|-----------| +| **Competitive Search** | Multi-provider search across 7 engines (SearXNG, Exa, DuckDuckGo, Firecrawl, Wikipedia, OpenCorporates, CourtListener) | 25 searches/month | +| **Competitive Landscape Report** | "Here's everyone in your space" — identified competitors with URLs, descriptions, and market positioning | On-demand | +| **Pricing Comparison** | "What do they charge?" — pricing tiers, free trials, freemium vs. paid comparison across all identified competitors | Included in landscape report | +| **Feature Benchmarking** | "What do they have that you don't?" — feature matrix across competitors, gaps identified, differentiation opportunities flagged | Included in landscape report | +| **OSINT Dossier** | Deep-dive on one key competitor: founder background, funding history, Glassdoor/employee signals, web presence, technology stack indicators | 1 dossier/month | +| **Investor-Ready Summary PDF** | Professionally formatted PDF with executive summary, competitive matrix, market positioning, and differentiation thesis. Ready for pitch deck attachment. | On-demand | + +### 5.2 The "Validate Your Startup" Lead Gen Engine + +The landing page at launchcheck.io will feature a **free, instant competitive snapshot** — enter a one-line description of your idea and get a mini-report as a lead generation tool: + +1. User enters: *"AI-powered contract review for small law firms"* +2. LaunchCheck returns: Top 3–5 identified competitors, average pricing range, one key insight +3. Call to action: *"Get the full report — 15+ competitors mapped, pricing comparison, feature gaps, investor-ready PDF. $49/month."* + +This lead gen engine converts 3–8% to paid (industry benchmark for freemium SaaS). At 10,000 free snapshots/month, that's 300–800 new trials. + +### 5.3 Technology Stack + +| Component | Technology | Status | +|-----------|-----------|--------| +| Search Engine | Super Search v2 MCP server (7 providers) | ✅ **Operational** on app1 | +| LLM Synthesis | Admin-AI (DeepSeek, Claude, GPT-4.1, Gemini) | ✅ **Operational** via existing provider | +| PDF Generation | WeasyPrint or Puppeteer | 🟡 Needs implementation | +| Frontend | React + TailwindCSS | 🔴 Needs build | +| API Layer | FastAPI wrapper around Super Search v2 | 🔴 Needs build | +| Payments | Stripe (single tier: $49/mo or $49/mo annual) | 🔴 Needs integration | +| Database | PostgreSQL (users, reports, search history) | 🔴 Needs schema + deployment | +| Auth | Clerk or Auth0 (social login: Google, GitHub) | 🔴 Needs integration | +| Hosting | app1 (existing infrastructure) | ✅ **Available** | + +**Build estimate:** ~80% of the engine exists. Remaining work is frontend + API wrapper + payments. This is a 4–6 week build for one senior React/FastAPI developer — not a 6-month greenfield project. + +--- + +## 6. Revenue Model + +### 6.1 Pricing + +| Plan | Price | What You Get | +|------|-------|-------------| +| **LaunchCheck Monthly** | $56/month | 25 competitive searches, landscape reports, pricing comparison, feature benchmarking, 1 OSINT dossier, investor PDF | +| **LaunchCheck Annual** | $49/month (billed $588/year) | Same as monthly, save $84/year | + +**Single tier only.** No confusing "Starter / Pro / Enterprise" upsells. Founders hate pricing page anxiety. One price, everything included. Clean. + +### 6.2 Why $49? + +- **Psychological threshold:** Below $50 is "impulse buy" territory for professionals. Above $50 triggers budget-decision anxiety. +- **Competitor positioning:** IdeaProof is €19.99–€99.99 in credits, ValidateMySaaS is $19–$29/month for lighter reports, DimeADozen is $129+ for a single report. LaunchCheck at $49/month for unlimited landscape + 1 dossier occupies the "professional but affordable" zone. +- **LTV calculation:** Average customer lifetime of 6 months = $294 LTV. At near-zero marginal cost, this is >90% gross margin. +- **Anchor to IntelSight:** $49 makes IntelSight's $199 Pro tier feel like a natural step up — 4x the price for 4x the searches and multi-user features. + +### 6.3 Revenue Scenarios + +| Customers | Monthly Revenue | Annual Revenue (ARR) | Monthly (if 50% monthly plan) | +|-----------|----------------|---------------------|-------------------------------| +| 25 | $1,225 | $14,700 | $1,312 | +| 50 | $2,450 | $29,400 | $2,625 | +| 100 | $4,900 | $58,800 | $5,250 | +| 250 | $12,250 | $147,000 | $13,125 | +| 500 | $24,500 | $294,000 | $26,250 | +| 1,000 | $49,000 | $588,000 | $52,500 | +| 2,500 | $122,500 | $1,470,000 | $131,250 | +| 5,000 | $245,000 | $2,940,000 | $262,500 | + +*Assumes 70% annual / 30% monthly mix. Monthly plan customers pay $56/month.* + +### 6.4 Cost Structure (Monthly at 1,000 Customers) + +| Cost Category | Monthly Estimate | Notes | +|--------------|-----------------|-------| +| LLM API calls (Admin-AI) | $800–$1,500 | 25 searches × 1,000 customers = 25,000 queries/month. Heavily cached overlapping searches. | +| Super Search v2 infra | $0 (marginal) | Already running for IntelSight. Shared infrastructure. | +| Hosting (app1) | $0 (marginal) | Existing server. Incremental load is minimal. | +| Stripe fees | ~$1,470 | 2.9% + $0.30 per transaction | +| Domain + email | $25 | launchcheck.io + transactional email | +| **Total** | **~$2,300–$3,000** | | + +**Gross margin at 1,000 customers:** ~94%. This is a high-margin software business from Day 1. + +--- + +## 7. Competitive Advantages + +### 7.1 The Competitive Landscape + +| Competitor | Price | Strengths | Weaknesses vs. LaunchCheck | +|-----------|-------|-----------|---------------------------| +| **ValidatorAI** | Free | Fast, simple, unlimited | Single-page AI summary; no real search data; hallucinations of market size | +| **IdeaProof** | €19.99–€99.99 (credits) | Multi-model AI, brand assets, business plans | Per-report pricing adds up; still AI-only, no real-time search | +| **WorthBuild** | $5/report | Customer discovery leads, cheap | Limited competitive depth; consumer-grade reports | +| **ValidateMySaaS** | $19–$29/month | SEO + competitor focus | Lighter reports; no OSINT; no investor PDF | +| **DimeADozen** | $129–$179/report | Deep investment-grade reports | Expensive per use; overkill for ideation phase | +| **Crunchbase** | $99+/month | Funding data, investor discovery | Built for funded companies, not pre-revenue founders | +| **ChatGPT / Claude** | Free/$20 | Quick, conversational | Hallucinates competitor names and market data; no verifiable sources | + +### 7.2 LaunchCheck's Moat + +1. **Real search, not AI hallucination.** Seven search providers feeding structured synthesis means LaunchCheck returns actual competitors with real URLs — not made-up company names. This is the single biggest differentiator: founders can click through and verify every finding. IdeaProof, ValidatorAI, and ChatGPT cannot do this. + +2. **The IT Pro Partner credibility backstop.** LaunchCheck is not a random startup built by a solo founder in a weekend. It's a product of IT Pro Partner, an established managed services provider with real revenue, existing infrastructure, and a track record. This matters when founders share reports with investors — the logo and company backing convey legitimacy. + +3. **Same engine as IntelSight ($199–$1,499/mo).** Enterprise-grade search infrastructure at indie pricing. LaunchCheck customers are running the same search pipeline that powers competitive intelligence for funded companies. + +4. **OSINT dossiers.** No other validation tool offers deep competitor dossiers (founder background, funding history, employee signals, technology stack). This is normally a $500+ consulting engagement. LaunchCheck includes one per month. + +5. **The upgrade path creates a compounding advantage.** Every LaunchCheck customer who upgrades to IntelSight makes the IntelSight product better (more data, more search patterns, better synthesis). The funnel is self-reinforcing. + +6. **Single tier, simple pricing.** No credit systems, no "you've run out of reports," no confusing feature matrices. One price, everything included. Founders are exhausted by pricing page dark patterns. + +--- + +## 8. Go-to-Market Strategy + +### 8.1 Launch Sequence + +**Phase 1: Soft Launch (Weeks 1–2)** +- Deploy to a small waitlist of 50–100 founders (recruited from IT Pro Partner network, Germaine's contacts) +- Gather testimonials, fix bugs, refine the PDF output +- Identify "magic moments" — what do users do right before they convert? + +**Phase 2: Product Hunt Launch (Week 3)** +- **Title:** "LaunchCheck: Know your competition before you write a line of code" +- **Tagline:** Validate your startup idea with real competitive intelligence. +- **Maker comment:** Personal story about watching founders build things nobody wanted. LaunchCheck is the tool you wish you had before spending 6 months on the wrong idea. +- **Target:** Top 5 of the day (requires 300+ upvotes). Product Hunt's top products average 1,079 upvotes. Top 5 typically requires 400–600 upvotes with an engaged community. +- **Expected outcomes:** + - 500–2,000 signups during launch week (26.5% of PH launches attract 500–1,000 users) + - 5–10% conversion to paid = 25–200 new customers in Week 1 + - Press coverage: Product Hunt feature → tech blogs pick up → founder Twitter amplification + +**Phase 3: Community Flywheel (Weeks 4–12)** +- **YC community:** Free competitive landscape snapshots posted in YC forums, Bookface, and founder Slack groups. *"Applied to YC? Here's a competitive landscape for your space. DM me your one-liner and I'll run it through LaunchCheck."* +- **Indie Hackers:** Guest posts on Indie Hackers: *"I analyzed 100 startup ideas on LaunchCheck. Here's what founders consistently get wrong about their competition."* Native content performs 10x better than ads on IH. +- **r/startups, r/SaaS, r/Entrepreneur:** Answer "how do I validate my idea?" threads with specific, helpful responses. Include a LaunchCheck reference only when genuinely relevant. +- **Techstars applicant networks:** Identical playbook to YC. 300,000+ accelerator applicants annually is a captive audience with a hard deadline and high motivation. + +**Phase 4: Content + SEO (Months 3–12)** +- **"Validate your startup" landing page** (launchcheck.io): Free competitive snapshot for lead generation. SEO-optimized for "validate startup idea," "competitor analysis for startups," "startup competitive landscape." +- **Blog content:** "How to research competitors before building," "The competition slide investors actually want," "What Y Combinator looks for in market analysis." +- **Founder spotlights:** Interview LaunchCheck users who validated, pivoted, or killed an idea based on competitive intelligence. Real stories outperform marketing copy. + +### 8.2 Zero Ad Spend + +LaunchCheck will not spend on paid acquisition in Year 1. The customer acquisition channels are: + +| Channel | Cost | Expected Volume (Year 1) | Conversion Rate | +|---------|------|--------------------------|----------------| +| Product Hunt launch | $0 (organic) | 500–2,000 signups | 5–10% to paid | +| Free lead gen landing page | $0 (SEO) | 5,000–20,000 snapshots/mo by M12 | 3–8% to trial | +| Community posts (YC, IH, Reddit) | $0 (time) | 200–500 visitors/post | 2–5% to trial | +| Word of mouth / viral sharing | $0 | Compounding | 1–3% referral rate | +| Founder Twitter/X (#buildinpublic) | $0 (content) | Steady inbound | 2–4% to trial | + +### 8.3 Viral Loops + +1. **"Share your competitive landscape"** — Every LaunchCheck report has a "Share publicly" button that generates a branded preview card. Founders sharing their competitive research is marketing for LaunchCheck. +2. **Investor PDFs carry branding** — Every PDF sent to an investor includes "Powered by LaunchCheck" with a QR code. Investors evaluating multiple deals see the branding repeatedly. +3. **"Validated by LaunchCheck" badge** — Founders can embed a badge on their landing page: "Validated by LaunchCheck — competitive landscape analyzed." This is aspirational for pre-launch startups. + +--- + +## 9. Risk Analysis + +### 9.1 Risk Matrix + +| Risk | Likelihood | Impact | Mitigation | +|------|-----------|--------|------------| +| **Founders churn after validating one idea** | High | Medium | This is by design. One month of validation is the expected use case. Annual pricing reduces churn. Email "Validate your next idea" re-engagement campaigns. | +| **Price sensitivity — founders compare to free AI validators** | Medium | High | Differentiate on verifiable sources vs. AI hallucination. "Free validators tell you your idea is great. LaunchCheck shows you the competitive reality." | +| **Competitor (IdeaProof, WorthBuild) adds real search** | Medium | Medium | First-mover advantage with Super Search v2. IntelSight's enterprise revenue funds continuous infrastructure investment. Competitors would need to build a 7-provider search pipeline — significant engineering investment. | +| **AI search becomes commoditized** | Medium-High | High | Shift to value-add: OSINT dossiers, investor PDFs, upgrade path to IntelSight. The search engine is the moat; if search becomes free, the moat becomes depth + synthesis + trust. | +| **Low upgrade rate to IntelSight** | Medium | Medium | Even at 5% upgrade rate on 1,000 LaunchCheck customers, that's 50 new IntelSight leads at $199+/month ($119K+ ARR). The standalone economics are strong enough that the upgrade path is upside, not necessity. | +| **Founders fail, stop paying** | High | Medium | Expected. Churn will be 8–15% monthly. Compensate with high top-of-funnel growth. The "free snapshot" lead gen engine needs to consistently feed new trials. | +| **GDPR / data privacy** | Low | Medium | Competitive intelligence is public information. Reports cite sources. Standard privacy policy + data processing agreement. | +| **IT Pro Partner brand risk** | Low | High | LaunchCheck is a separate brand (launchcheck.io). If it fails, it doesn't damage the MSP brand. If it succeeds, IT Pro Partner gets credit as the parent company. | + +### 9.2 Churn Analysis + +LaunchCheck will have higher churn than typical B2B SaaS (which averages 3–7% monthly). Reasons: + +- Founders validate one idea and leave (expected, not a failure) +- Founders run out of money (pre-revenue, pre-funding) +- Founders succeed and upgrade to IntelSight (desired outcome) +- Founders fail and stop all spending (sad but inevitable) + +**Projected churn rates:** + +| Month | Monthly Churn Rate | Cumulative Retention | +|-------|-------------------|---------------------| +| 1 | 25% | 75% | +| 2 | 20% | 60% | +| 3 | 15% | 51% | +| 4 | 12% | 45% | +| 5 | 12% | 40% | +| 6 | 10% | 36% | + +At these rates, average customer lifetime is ~4.2 months, LTV = ~$206. This is acceptable because: +- Gross margin is 94% (near-zero marginal cost) +- CAC is near-zero (community-driven, no ad spend) +- LTV:CAC ratio is effectively infinite (or at minimum >10:1) + +The upgrade path to IntelSight dramatically increases effective LTV. A LaunchCheck customer who upgrades to IntelSight Pro at $199/month for 12+ months has an LTV of $2,500+. + +### 9.3 What Happens If Nobody Upgrades? + +If LaunchCheck-to-IntelSight upgrades are negligible, the business still works: + +- At 500 customers: $24,500/month ($294K ARR) at ~$1,500/month operating cost +- At 1,000 customers: $49,000/month ($588K ARR) at ~$3,000/month operating cost + +These are profitable outcomes that require no IntelSight dependency. The upgrade path is upside, not the core thesis. + +--- + +## 10. Financial Projections + +### 10.1 12-Month Revenue Forecast (Base Case) + +| Month | New Customers | Churned | Net | Total Active | MRR | Cumulative Revenue | +|-------|--------------|---------|-----|-------------|-----|-------------------| +| 1 | 50 | 0 | +50 | 50 | $2,450 | $2,450 | +| 2 | 80 | 5 | +75 | 125 | $6,125 | $8,575 | +| 3 | 150 | 15 | +135 | 260 | $12,740 | $21,315 | +| 4 | 200 | 30 | +170 | 430 | $21,070 | $42,385 | +| 5 | 150 | 50 | +100 | 530 | $25,970 | $68,355 | +| 6 | 120 | 65 | +55 | 585 | $28,665 | $97,020 | +| 7 | 100 | 70 | +30 | 615 | $30,135 | $127,155 | +| 8 | 90 | 75 | +15 | 630 | $30,870 | $158,025 | +| 9 | 80 | 75 | +5 | 635 | $31,115 | $189,140 | +| 10 | 70 | 75 | -5 | 630 | $30,870 | $220,010 | +| 11 | 60 | 70 | -10 | 620 | $30,380 | $250,390 | +| 12 | 50 | 65 | -15 | 605 | $29,645 | $280,035 | + +**Key metrics at Month 12:** +- Active customers: 605 +- MRR: $29,645 +- ARR run rate: $355,740 +- Cumulative Year 1 revenue: $280,035 +- Total customers acquired: 1,200 +- Average monthly churn: ~11% +- Customer acquisition cost: ~$0 (organic) + +### 10.2 Three Scenarios + +| Scenario | Month 12 Customers | Month 12 MRR | Year 1 Revenue | Assumptions | +|----------|-------------------|-------------|----------------|-------------| +| **Conservative** | 300 | $14,700 | $120,000 | Slow community growth, low PH launch (200 upvotes), 30% monthly churn | +| **Base Case** | 605 | $29,645 | $280,000 | Healthy PH launch (400+ upvotes), steady community growth, 11% avg monthly churn | +| **Optimistic** | 1,200 | $58,800 | $520,000 | PH #1–3 of the day, viral founder sharing, YC community embrace, 8% avg monthly churn | + +### 10.3 Breakeven Analysis + +LaunchCheck is profitable from Day 1, Customer 1. The only upfront costs are the build: + +| Build Component | Estimated Cost | +|----------------|----------------| +| React frontend | $8,000–$12,000 (one senior dev, 3–4 weeks) | +| FastAPI wrapper | $4,000–$6,000 (one dev, 1–2 weeks) | +| Stripe integration | $2,000–$3,000 | +| PostgreSQL + schema | $1,000–$2,000 | +| PDF generation | $2,000–$3,000 | +| Auth (Clerk/Auth0) | $1,000–$2,000 | +| Design + branding | $3,000–$5,000 | +| **Total Build** | **$21,000–$33,000** | + +**Breakeven on build cost:** At 50 customers, Monthly 1 (~$2,450 MRR), the build cost is recovered in **9–14 months**. At 200 customers (Month 4 base case), the build cost is recovered in **2–3 months**. + +### 10.4 Year 2 Projection + +At Month 12 with 600+ customers and organic growth compounding: + +- Monthly new customers: 80–120 (compounding from SEO, referrals, founder word-of-mouth) +- Monthly churn stabilizing at 8–10% +- **Month 24 customers:** 1,000–1,500 +- **Month 24 MRR:** $49,000–$73,500 +- **Year 2 cumulative revenue:** $470,000–$750,000 + +--- + +## 11. The Upgrade Path: LaunchCheck → IntelSight + +### 11.1 The Funnel + +``` +LaunchCheck ($49/mo) + │ + ├── Founder validates, builds, raises funding ────► IntelSight Pro ($199/mo) + │ • Multi-user dashboard + │ • 100 searches/month + │ • Competitor monitoring alerts + │ + ├── Founder grows, hires team ───────────────────► IntelSight Growth ($499/mo) + │ • Team seats + │ • Custom report scheduling + │ • API access + │ + └── Founder's company scales, needs enterprise ──► IntelSight Enterprise ($1,499+/mo) + • Dedicated analyst + • White-label reports + • SSO / compliance +``` + +### 11.2 What Triggers an Upgrade? + +| Trigger | Signal | Upgrade Path | +|---------|--------|-------------| +| **Funding closed** | Founder mentions in-app or in support email | IntelSight Pro — "Now that you're funded, here's what you need" | +| **Exceeded search limits 3+ months in a row** | 25 searches/mo consistently maxed out | IntelSight Growth — "You need more firepower" | +| **Team member added** | Second user tries to access account | IntelSight Growth — "Add your team" | +| **6+ months active** | Sustained usage beyond validation phase | IntelSight Pro — "You're clearly past validation. Upgrade for monitoring." | + +### 11.3 Upgrade Economics + +| Metric | Conservative | Base Case | Optimistic | +|--------|-------------|-----------|------------| +| LaunchCheck → IntelSight conversion | 3% | 5% | 8% | +| At 600 LaunchCheck customers | 18 IntelSight upgrades | 30 IntelSight upgrades | 48 IntelSight upgrades | +| Incremental ARR from upgrades | $42,984 | $71,640 | $114,624 | +| Combined ARR (LaunchCheck + upgrades) | $398,724 | $427,380 | $469,344 | + +The upgrade path effectively doubles ARR per LaunchCheck cohort when factored over 24 months. + +--- + +## 12. The Ask + +### 12.1 What's Needed + +LaunchCheck is not a 6-month greenfield build. ~80% of the engine exists. The ask is minimal: + +| Resource | What | Cost | +|----------|------|------| +| **Frontend developer** | React + TailwindCSS build for launchcheck.io | $8,000–$12,000 (contract, 3–4 weeks) | +| **Backend developer** | FastAPI wrapper + Stripe + Postgres + PDF | $7,000–$10,000 (contract, 2–3 weeks) | +| **Design/branding** | Logo, color system, landing page, report templates | $3,000–$5,000 | +| **Domain + infrastructure** | launchcheck.io, deployment on app1 | $200/year | +| **Legal** | Terms of service, privacy policy, data processing agreement | $2,000–$3,000 (if not using template) | +| **Total Ask** | | **$20,000–$30,000** | + +### 12.2 What's Already In Place (No Additional Cost) + +- Super Search v2 MCP server (7 providers) — operational on app1 +- LLM access via Admin-AI (DeepSeek, Claude, GPT-4.1, Gemini) +- Hosting infrastructure on app1 +- IT Pro Partner operational backbone (support, billing, compliance awareness) +- Germaine's founder network for initial distribution +- IntelSight as the upgrade destination + +### 12.3 Timeline + +| Phase | Duration | Deliverable | +|-------|---------|------------| +| Build | Weeks 1–6 | Functional product on launchcheck.io | +| Internal QA | Week 7 | Bug fixes, PDF polish, report quality review | +| Soft launch | Week 8 | 50–100 founder waitlist, gather testimonials | +| Product Hunt launch | Week 9 | Public launch, community push | +| Growth | Weeks 10–52 | Community, SEO, content, iteration | + +### 12.4 Decision Required + +The advisory team is asked to: + +1. **Approve** the $20,000–$30,000 development budget +2. **Validate** the single-tier $49/month pricing strategy +3. **Confirm** the Product Hunt + community GTM approach (zero ad spend) +4. **Authorize** the use of app1 infrastructure for LaunchCheck deployment +5. **Decide** whether LaunchCheck operates as an IT Pro Partner product line or a separate entity + +--- + +## Appendix A: Source Citations + +| Claim | Source | +|-------|--------| +| 5.5M new business applications (US, 2023) | US Census Bureau, Business Formation Statistics | +| 531,423 new business applications (June 2026) | US Census Bureau, BFS Current Report | +| 22.1% of businesses fail in first year | Bureau of Labor Statistics, via LendingTree analysis | +| 48.6% fail within 5 years; 65.3% within 10 years | Bureau of Labor Statistics, Business Employment Dynamics | +| 42% of startups fail due to "no market need" | CB Insights, "Top 20 Reasons Startups Fail" (analysis of 431 failures) | +| 63% of tech startups fail within 5 years | DemandSage research analysis | +| Global SaaS market: $322B in 2024, 19.3% CAGR | SkyQuest, Precedence Research, Fortune Business Insights | +| YC: 27,000+ applications for Winter 2024 batch, 96,000+ for 2025 | GrowthList, LinkedIn (Ayush Sharma), Y Combinator public data | +| 0.7% YC acceptance rate (2025) | LinkedIn (Ayush Sharma): 620 accepted from 96,000+ | +| Techstars <1% acceptance rate | Techstars 2.0 announcement, multiple sources | +| 68,342+ IdeaProof users | ideaproof.io public homepage | +| Average Product Hunt top product: 1,079 upvotes | Medium: "What 76,822 Product Hunt Launches Reveal" | +| 26.5% of PH launches attract 500–1,000 users | MySignature.io survey | +| $15K Product Hunt launch → $6–20K in customer value | LinkedIn (Thomas Mazimann) | +| r/indiehackers: 100K+ members | waveup.com blog | +| 10% of SaaS startups fail in first year | Failory / Exploding Topics | + +--- + +## Appendix B: Competitive Pricing Comparison + +| Tool | Low-End Pricing | High-End Pricing | Model | LaunchCheck Advantage | +|------|----------------|-----------------|-------|----------------------| +| ValidatorAI | Free | Free (public) | Perpetual free | No real search; hallucinations | +| IdeaProof | Free (90 credits) | €99.99 (1,500 credits) | Credit-based | Per-report costs add up; AI-only | +| WorthBuild | Free (1/mo) | $20 (5 reports) | Pay-per-report | Consumer-grade depth; no OSINT | +| ValidateMySaaS | $19/mo | $29/mo | Monthly sub | Lighter reports; no investor PDF | +| Trend Seeker | Free (daily limit) | $9.99/mo | Monthly sub | Demand signals, not competitive intel | +| DimeADozen | Free (Solo) | $179 (3-pack) | Pay-per-report | Expensive; overkill for ideation | +| Crunchbase | $99/mo (Pro) | Custom enterprise | Annual contract | Built for funded companies | +| **LaunchCheck** | **$49/mo annual** | **$56/mo monthly** | **Simple monthly** | **Verifiable search + OSINT + investor PDF** | + +--- + +*This proposal was prepared with publicly available market data and reflects the best available information as of July 2026. Revenue projections are forward-looking estimates and actual results may vary. All competitive pricing was verified against public pricing pages as of the date of this document.* diff --git a/docs-source/osint-tool/CHANGELOG.md b/docs-source/osint-tool/CHANGELOG.md new file mode 100644 index 0000000..34bdf59 --- /dev/null +++ b/docs-source/osint-tool/CHANGELOG.md @@ -0,0 +1,13 @@ +# OSINT People Search — CHANGELOG + +## 2026-07-10 — Documentation + +- Migrated web scraping API comparison and tool evaluations into the repo. + +## 2026-07-02 — Research + +- Completed comparison of web scraping APIs for OSINT data collection. + +## 2026-06-20 — API Planning + +- Drafted initial frontend UI and API integration plans. diff --git a/docs-source/osint-tool/index.md b/docs-source/osint-tool/index.md new file mode 100644 index 0000000..4d2f3ef --- /dev/null +++ b/docs-source/osint-tool/index.md @@ -0,0 +1,33 @@ +# osint-tool + + +> **Owner:** Germaine | **Status:** PLANNED +> **Last Updated:** 2026-08-09 + +An open-source intelligence (OSINT) toolchain for people search, skip tracing, and data aggregation. Integrates multiple web scraping and search APIs to build comprehensive person profiles from public data sources — supporting debt recovery, asset location, and investigative workflows. + +## What's Inside + +- `research/` — API evaluations and tool comparisons for web scraping, search, and browser automation + - `web-scraping-api-comparison.md` — Detailed comparison of 10 APIs (Firecrawl, ScrapingAnt, SerpAPI, Browserbase, etc.) for scraping government sites, court records, and people-search platforms + +## Tech Stack + +- **Scraping Pipeline:** Firecrawl, ScrapingAnt, Browserbase (evaluated, pending selection) +- **Search:** Brave Search API, Serper, Tavily (evaluated) +- **Backend:** TBD — likely Python/FastAPI +- **Frontend:** TBD + +## Quick Start + +```bash +git clone https://git.itpropartner.com/ippadmin/osint-tool.git +cd osint-tool +# Review research/ for API evaluation and selection +``` + +## Related + +- [itpp-infrastructure](https://git.itpropartner.com/ippadmin/itpp-infrastructure) — server inventory, DNS +- [itpp-standards](https://git.itpropartner.com/ippadmin/itpp-standards) — docs standards and templates +- [FleetTracker360](https://git.itpropartner.com/ippadmin/fleettracker360) — GPS fleet tracking diff --git a/docs-source/scripts/CHANGELOG.md b/docs-source/scripts/CHANGELOG.md new file mode 100644 index 0000000..ef7b43d --- /dev/null +++ b/docs-source/scripts/CHANGELOG.md @@ -0,0 +1,19 @@ +# scripts — CHANGELOG + +## 2026-07-21 — Purpose Descriptions + +- Added one-line purpose descriptions for all 78 scripts in README.md +- Backup scope documented in README header + +## 2026-07-16 — Audit Remediation + +- Recategorized scripts and removed Garrison Code Watch from cron +- `_fetch_body.py` moved to Email (internal utility) +- `mcp-mysql.py` moved to Hermes Internal (MCP server) +- `unsubscribe-scanner.py` and `daily-feed-summary.py` moved to Personal +- `garrison-code-watch.py` cron removed, script archived + +## 2026-07-15 — Initial Inventory + +- Initial commit: 79 scripts across 16 categories catalogued in README.md +- 31 cron-attached scripts, 48 manual/indirect diff --git a/docs-source/scripts/index.md b/docs-source/scripts/index.md new file mode 100644 index 0000000..f0a6a2b --- /dev/null +++ b/docs-source/scripts/index.md @@ -0,0 +1,157 @@ +# scripts + + +**Generated:** 2026-07-21 19:53 +**Total scripts:** 78 (44 shell, 34 Python) +**Cron-attached:** 30 of 78 +**Location:** `/root/.hermes/scripts/` +**Backup:** Live sync (every 15m) + Full daily backup → Wasabi S3 + +## Backup & DR + +| Script | Purpose | Type | Size | Schedule | Cron Job | +|---|---|---|---|---|---| +| `unms-backup-sync.sh` | unms-backup-sync.sh -- Daily UISP/UNMS backup sync to Wasabi S3. | shell | 2.6KB | {'kind': 'cron', 'expr': '0 6 * * *', 'display': '0 6 * * *'} | unms-backup-sync ✅ | +| `gitea-backup.sh` | gitea-backup.sh — Daily Gitea backup to Wasabi S3. | shell | 2.5KB | {'kind': 'cron', 'expr': '0 8 * * *', 'display': '0 8 * * *'} | gitea-backup ✅ | +| `snapshot-hetzner.py` | Create a snapshot of ALL running servers in the Hetzner account. | python | 1.8KB | {'kind': 'cron', 'expr': '0 5 * * 1', 'display': '0 5 * * 1'} | hetzner-weekly-snapshots ✅ | +| `hudu-backup.sh` | hudu-backup.sh — Daily Hudu PostgreSQL backup to Wasabi S3. | shell | 1.6KB | {'kind': 'cron', 'expr': '0 7 * * *', 'display': '0 7 * * *'} | hudu-backup ✅ | +| `hermes-live-sync.sh` | hermes-live-sync.sh — Full Hermes sync to S3 every 15 min | shell | 1.1KB | {'kind': 'interval', 'minutes': 15, 'display': 'every 15m'} | hermes-live-sync ✅ | +| `run-wisp-backup.sh` | run-wisp-backup.sh — Wrapper for cron: sets up env and runs CCR backup via VPN | shell | 776B | {'kind': 'cron', 'expr': '0 6 * * *', 'display': '0 6 * * *'} | home-router-daily-backup ✅ | +| `sync_ringlogix.py` | Sync RingLogix subscriber data to local cache | python | 27.0KB | Manual | — | +| `backup_portal.py` | Backup portal data for web hosting services | python | 17.1KB | Manual | — | +| `wisp-backup.py` | Call home-router-vpn.sh up/down/status. | python | 13.1KB | Manual | — | +| `hermes-backup.sh` | hermes-backup.sh — Full Hermes backup to Wasabi S3 | shell | 7.9KB | Manual | — | +| `core-services-backup.sh` | Supplemental Core service backups (services not covered by existing Core backups) | shell | 3.8KB | Manual | — | +| `hermes-standby-restore.sh` | hermes-standby-restore.sh — Warm standby restore from S3 | shell | 2.7KB | Manual | — | +| `app1-backup.sh` | Backup app1 AI stack services to S3 | shell | 2.6KB | Manual | — | +| `hermes-system-config-sync.sh` | hermes-system-config-sync.sh — Sync system-level configs to Wasabi | shell | 2.4KB | Manual | — | +| `backup-uisp.sh` | Backup UISP/UNMS network management system | shell | 2.3KB | Manual | — | +| `app3-backup.sh` | Backup app3 web hosting services to S3 | shell | 2.1KB | Manual | — | +| `root-essentials-backup.sh` | root-essentials-backup.sh — Daily backup of Hermes config/core to S3 | shell | 1.8KB | Manual | — | +| `hermes-docker-sync.sh` | hermes-docker-sync.sh — Sync Docker volumes to Wasabi | shell | 1.7KB | Manual | — | +| `unifi-backup-sync.sh` | unifi-backup-sync.sh Daily UniFi backup sync from app2 Docker volume to Wasabi S3. | shell | 1.7KB | Manual | — | +| `home-router-backup.sh` | Backup home MikroTik router config to S3 | shell | 1.7KB | Manual | — | +| `app2-backup.sh` | Backup app2 services to S3 | shell | 1.7KB | Manual | — | +| `backup-audit-check.sh` | backup-audit-check.sh — Daily check: was yesterday's full backup successful? | shell | 1.3KB | Manual | — | +| `send-backup-email.py` | Send home router backup archive via SMTP. | python | 1.1KB | Manual | — | +| `send-backup-readme.py` | Send home router backup README via SMTP. | python | 1.1KB | Manual | — | +| `run-portal-backup.sh` | Portal backup cron wrapper — runs backup_portal.py, silent on success | shell | 782B | Manual | — | +| `system-config-sync.sh` | Sync system config files to S3 | shell | 669B | Manual | — | +| `docker-volume-sync.sh` | Sync Docker volumes to S3 | shell | 597B | Manual | — | + +## Monitoring + +| Script | Purpose | Type | Size | Schedule | Cron Job | +|---|---|---|---|---|---| +| `vps-threshold-check.sh` | VPS Resource Threshold Alert Checker | shell | 6.1KB | {'kind': 'interval', 'minutes': 15, 'display': 'every 15m'} | vps-threshold-check ✅ | +| `bounce-check.py` | Check email bounce rates and flag delivery issues | python | 3.3KB | {'kind': 'interval', 'minutes': 60, 'display': 'every 60m'} | bounce-check ✅ | +| `service-health-check.sh` | service-health-check.sh — Check all critical services and report failures | shell | 2.3KB | {'kind': 'interval', 'minutes': 5, 'display': 'every 5m'} | service-health-check ✅ | +| `imap_triage_watchdog.sh` | Watchdog for the email triage cron job (5929c5f1deff). | shell | 2.1KB | {'kind': 'interval', 'minutes': 10, 'display': 'every 10m'} | IMAP-triage-watchdog ✅ | +| `spend-monitor-collect.sh` | spend-monitor-collect.sh — Collect LLM spend data for daily monitor | shell | 1.9KB | {'kind': 'cron', 'expr': '0 7 * * *', 'display': '0 7 * * *'} | daily-spend-monitor ✅ | +| `apex-mail-watchdog.sh` | apex-mail-watchdog.sh — Check Apex WPForms email delivery every 5 min | shell | 1.9KB | {'kind': 'cron', 'expr': '*/5 * * * *', 'display': '*/5 * * * *'} | apex-mail-watchdog ✅ | +| `home-router-watchdog.sh` | home-router-watchdog.sh — Check home router via WireGuard tunnel | shell | 553B | {'kind': 'interval', 'minutes': 5, 'display': 'every 5m'} | Home-Router-Watchdog ✅ | +| `home-router-watchdog.sh` | home-router-watchdog.sh — Check home router via WireGuard tunnel | shell | 553B | {'kind': 'interval', 'minutes': 5, 'display': 'every 5m'} | Home-Router-Watchdog ✅ | +| `boys-mail-monitor.py` | Monitor Tony's email inbox for important messages | python | 18.5KB | Manual | — | +| `hermes-standby-watchdog.sh` | hermes-standby-watchdog.sh — Periodic health check for warm standby | shell | 4.2KB | Manual | — | +| `apex-mail-watchdog.py` | Try sending a test email to the admin address. | python | 2.9KB | Manual | — | +| `reboot-with-check.sh` | reboot-with-check.sh — Reboot a server and wait for it + key services to come back. | shell | 2.7KB | Manual | — | + +## Email + +| Script | Purpose | Type | Size | Schedule | Cron Job | +|---|---|---|---|---|---| +| `shonuff-email-responder.py` | Collect new emails from the Master | python | 9.7KB | {'kind': 'interval', 'minutes': 5, 'display': 'every 5m'} | shonuff-email-reply ✅ | +| `dre-mail-poller.py` | ── Configuration ────────────────────────────────────────────────────────────── | python | 9.6KB | {'kind': 'interval', 'minutes': 1, 'display': 'every 1m'} | dre-mail-poller ✅ | +| `boxpilot-triage.py` | Free/personal email domains — if sender is from these AND claims another company, it's sus | python | 5.8KB | {'kind': 'interval', 'minutes': 10, 'display': 'every 10m'} | boxpilot-triage ✅ | +| `shonuff-inbox-collect.py` | Extract plain text from email, favoring text/plain. | python | 3.3KB | {'kind': 'interval', 'minutes': 15, 'display': 'every 15m'} | shonuff-inbox-agent ✅ | +| `imap_triage_autorun.py` | User-blocked marketing/loan domains are spam/promotional. | python | 2.9KB | {'kind': 'interval', 'minutes': 60, 'display': 'every 60m'} | IMAP-email-triage ✅ | +| `imap_triage.py` | Core IMAP email triage and classification engine | python | 14.6KB | Manual | — | +| `send-dr-audit-email.py` | Send DR Backup Audit email to Germaine. | python | 5.5KB | Manual | — | +| `send-shonuff.py` | Send email from Sho'Nuff Brown with signature + BCC to Germaine + IMAP Sent copy. | python | 2.3KB | Manual | — | +| `watch-shonuff-inbox.py` | Monitor Sho'Nuff inbox for new messages | python | 2.2KB | Manual | — | +| `send-recovery.py` | Email the recovery bundle to g@germainebrown.com. | python | 1.5KB | Manual | — | +| `_fetch_body.py` | Fetch body of email sequence number 2 from shonuff inbox. | python | 1.1KB | Manual | — | +| `send-reset-notifications.sh` | Called BEFORE suggesting the user type /reset | shell | 914B | Manual | — | +| `send-back-online.sh` | Called by the NEW session after /reset to notify Germaine and Anita | shell | 832B | Manual | — | + +## Ops Portal + +| Script | Purpose | Type | Size | Schedule | Cron Job | +|---|---|---|---|---|---| +| `ops-data-collector.py` | ─── Configuration ───────────────────── | python | 47.6KB | {'kind': 'cron', 'expr': '*/5 * * * *', 'display': '*/5 * * * *'} | ops-data-collector ✅ | +| `status-page-refresh.sh` | Status page heartbeat JSON generator | shell | 762B | {'kind': 'interval', 'minutes': 1, 'display': 'every 1m'} | status-page-refresh ✅ | + +## Network + +| Script | Purpose | Type | Size | Schedule | Cron Job | +|---|---|---|---|---|---| +| `home-router-vpn.sh` | wisp-vpn.sh — Connect/disconnect L2TP/IPsec VPN to MicroTik gateway | shell | 8.1KB | Manual | — | +| `home-router-keepalive.sh` | wisp-vpn-keepalive.sh — Check VPN status and reconnect if down | shell | 877B | Manual | — | + +## FleetTracker + +| Script | Purpose | Type | Size | Schedule | Cron Job | +|---|---|---|---|---|---| +| `ft360-export.py` | Cron runs scripts with system Python. The FT360 MCP uses the shared | python | 5.2KB | {'kind': 'interval', 'minutes': 1, 'display': 'every 1m'} | ft360-dashboard-export ✅ | +| `ft360-route-export.py` | Export today's FT360 device positions as GeoJSON for the dashboard map overlay. | python | 3.8KB | Manual | — | +| `ft360-daily-stats.py` | Compute daily stats from the Traccar H2 DB for the FT360 dashboard export. | python | 3.2KB | Manual | — | + +## AI & Models + +| Script | Purpose | Type | Size | Schedule | Cron Job | +|---|---|---|---|---|---| +| `track-firecrawl.py` | Track Firecrawl API credit usage for portal dashboard. | python | 2.7KB | {'kind': 'cron', 'expr': '0 9 * * *', 'display': '0 9 * * *'} | firecrawl-usage-check ✅ | +| `model-usage-tracker.sh` | Model health check -- test all 5 fallback chain providers | shell | 2.2KB | {'kind': 'cron', 'expr': '0 8 * * *', 'display': '0 8 * * *'} | model-usage-check ✅ | + +## Security + +| Script | Purpose | Type | Size | Schedule | Cron Job | +|---|---|---|---|---|---| +| `lynis-scan.sh` | Nightly security audit via Lynis | shell | 723B | {'kind': 'cron', 'expr': '0 3 * * *', 'display': '0 3 * * *'} | lynis-scan ✅ | +| `audit-server.sh` | Comprehensive server audit script | shell | 3.1KB | Manual | — | + +## DRE + +| Script | Purpose | Type | Size | Schedule | Cron Job | +|---|---|---|---|---|---| +| `dre-approval-reminder.sh` | dre-approval-reminder.sh — Check for pending claim approvals | shell | 854B | {'kind': 'cron', 'expr': '0 * * * *', 'display': '0 * * * *'} | dre-pending-approval-reminder ✅ | + +## Shark Game + +| Script | Purpose | Type | Size | Schedule | Cron Job | +|---|---|---|---|---|---| +| `shark-scraper.sh` | Shark scraper wrapper — silent on success, only outputs on failure | shell | 307B | {'kind': 'interval', 'minutes': 60, 'display': 'every 60m'} | shark-scraper ✅ | +| `shark-draft-reminder.sh` | shark-draft-reminder.sh — Send 24h and 1h draft reminder emails to league members | shell | 13.1KB | Manual | — | + +## Hermes Internal + +| Script | Purpose | Type | Size | Schedule | Cron Job | +|---|---|---|---|---|---| +| `hermes-consolidate.sh` | hermes-consolidate.sh — Auto-consolidate Hermes memory every 10 min. | shell | 2.8KB | {'kind': 'interval', 'minutes': 10, 'display': 'every 10m'} | hermes-memory-consolidate ✅ | +| `hermes-consolidate.py` | Consolidate and archive Hermes backup files | python | 4.6KB | Manual | — | +| `mcp-mysql.py` | Simple MySQL MCP server for Hermes - with async stdin handling. | python | 3.0KB | Manual | — | + +## Infrastructure + +| Script | Purpose | Type | Size | Schedule | Cron Job | +|---|---|---|---|---|---| +| `snmp-collect.sh` | Collect SNMP metrics from MikroTik network devices | shell | 926B | Manual | — | +| `snmp-http-server.py` | Serve SNMP textfile metrics via HTTP for Prometheus | python | 620B | Manual | — | +| `list-hetzner-servers.py` | List Hetzner servers via API. | python | 500B | Manual | — | + +## Utilities + +| Script | Purpose | Type | Size | Schedule | Cron Job | +|---|---|---|---|---|---| +| `build-recovery.py` | Build the full Hermes recovery bundle markdown file. | python | 6.9KB | Manual | — | +| `generate-script-contents.py` | Find a script file by name in known script directories. | python | 2.4KB | Manual | — | +| `changelog.sh` | changelog.sh — Append an entry to the Hermes changelog | shell | 877B | Manual | — | + +## Personal + +| Script | Purpose | Type | Size | Schedule | Cron Job | +|---|---|---|---|---|---| +| `daily-feed-summary.py` | Collect RSS feeds and generate daily digest summary | python | 9.3KB | {'kind': 'cron', 'expr': '0 11 * * *', 'display': '0 11 * * *'} | daily-tech-digest ✅ | +| `unsubscribe-scanner.py` | ── Config ── | python | 4.8KB | {'kind': 'cron', 'expr': '0 7 * * *', 'display': '0 7 * * *'} | Unsubscribe Daily Digest ✅ | +| `icloud_bills_calendar.py` | Sync iCloud calendar with billing reminders | python | 12.0KB | Manual | — | + diff --git a/docs-source/shark-game/CHANGELOG.md b/docs-source/shark-game/CHANGELOG.md new file mode 100644 index 0000000..5d71364 --- /dev/null +++ b/docs-source/shark-game/CHANGELOG.md @@ -0,0 +1,23 @@ +# Changelog - Shark Attack Fantasy League + +### 2026-07-10 +- **Backend & Infrastructure:** Backend server deployed (Python/FastAPI on Core), push notifications, Discord-Hermes bridge. + +### 2026-07-10 +- **Game Logic:** Player registration, league creation, season start locked to Aug 1. + +### 2026-07-10 +- **Frontend:** Bottom navigation, PWA manifest, draft room, settings page. + +### 2026-07-09 +- **Backend:** Shark sighting scraper deployed, Caddy reverse proxy configured. + +### 2026-07-09 +- **Game Logic:** Scoring system designed (1pt sighting, 3pt bite, 10pt fatality). + +### 2026-07-08 +- **Design:** Game concept finalized, coastal regions defined, database schema. + +### 2026-07-08 +- **Frontend & Backend:** Initial frontend scaffold, API endpoints created. + diff --git a/docs-source/shark-game/index.md b/docs-source/shark-game/index.md new file mode 100644 index 0000000..4ab8a5f --- /dev/null +++ b/docs-source/shark-game/index.md @@ -0,0 +1,19 @@ +# shark-game + + +A fantasy league game where players draft coastal regions and earn points based on real-world shark sightings, bites, and fatalities. + +## Project Info + +- **Owner:** Germaine +- **Ideation Team:** Germaine, Ryan, Garrison (age 11) +- **Status:** IN DEVELOPMENT +- **Deployed:** https://shark.iamgmb.com (Core Server) +- **Changelog:** [CHANGELOG.md](./CHANGELOG.md) +- **Note:** Game may evolve into real-time multiplayer + +## Access + +- Game: https://shark.iamgmb.com +- Discord: #shark-game channel (Hermes connected for 2-way chat) +- Draft reminders: cron-scheduled via shark-draft-reminder.sh diff --git a/docs-source/transitpin/CHANGELOG.md b/docs-source/transitpin/CHANGELOG.md new file mode 100644 index 0000000..7b4d3a2 --- /dev/null +++ b/docs-source/transitpin/CHANGELOG.md @@ -0,0 +1,45 @@ +# TransitPin — CHANGELOG + +## 2026-08-09 — Initial: migrate to Git + +- Migrated project to Git with ITPP standards (.gitignore, README template, docs-check CI). + +## 2026-07-29 + +### Added +- **Follow Driver** — each driver card in Dispatch tab has a 📍 button. Clicking centers the map on that driver and pans to follow their position updates every 3s. Dragging the map or clicking again unfollows. Blue glow on active follow. (JS: `window.followedDriverId`, `dispatchMap.panTo` in position handler) +- **Wake Lock API** — driver page requests `navigator.wakeLock.request('screen')` when GPS tracking starts, preventing screen dim/lock. Released on stop. Re-acquired automatically on visibility change. "🔒 Screen awake" / "⚠️ Screen may dim" indicator below GPS grid. +- **TomTom traffic overlay** — 🚦 toggle button on Dispatch tab. Toggles real-time traffic flow tiles (green/yellow/red overlay) on the Leaflet map. + +### Fixed +- **Permission 600→644** — driver.html and manifest were root-only, blocked Caddy from serving them. +- **Caddy WebSocket route** — changed from `handle_path /ws/driver/*` to `handle_path /ws/driver*` to match bare `/ws/driver` path. +- **Position ID field mismatch** — server broadcasts `position` with `driverId` field, but admin JS was looking for `d.id`. Fixed `updateDriverPosition`, `updateDriverCard`, `updateDriverStatus`, and `driver-offline` handler to accept both `id` and `driverId`. +- **TomTom tile URL** — was missing `/absolute/` in the path, causing 400 errors on all traffic tiles. Fixed from `/tile/flow/{z}/{x}/{y}.png` to `/tile/flow/absolute/{z}/{x}/{y}.png`. +- **JS scope on toggleTraffic** — function was local inside `initDispatch()`, invisible to onclick. Changed to `window.toggleTraffic`. +- **Parent portal dashboard crash after login** — `escapeHtml()` was called in 10 places throughout `renderDashboard()` and `renderEmergencyContacts()`, but the function was defined as `escHTML()` (abbreviated). This threw `ReferenceError` immediately after login, leaving a blank dashboard. Fix: added `const escapeHtml = escHTML;` alias at line 1121. Root cause: function was named one way during message rendering code (`escHTML`) and inconsistently named in dashboard/emergency contacts code (`escapeHtml`). + +### Architecture +- WebSocket relay running as `village-express.service` (systemd, port 8210) +- Caddy reverse-proxies `/ws/driver*` → `127.0.0.1:8210` for TLS +- Driver PWA streams GPS every 3s via `watchPosition` +- Dispatchers receive real-time position broadcasts + +--- + +## Future Projects + +### Native Background Tracking (Capacitor Wrapper) +The PWA approach cannot keep GPS running when the phone screen locks. No browser-based solution supports this. + +**Next step:** Wrap the driver PWA with Capacitor to produce real iOS/Android apps with native background geolocation permissions (`CLLocationManager` on iOS, `FusedLocationProviderClient` on Android). The existing HTML/JS frontend stays — Capacitor wraps it with native APIs. + +**Requirements:** +- Capacitor CLI + native iOS/Android project scaffolding +- `cordova-plugin-background-geolocation` or Capacitor's built-in geolocation plugin with background mode +- App store deployment for iOS (Apple Developer account needed) +- Play Store or direct APK for Android + +**Estimated effort:** 1-2 days for Capacitor setup + background plugin integration. App store submission adds 1-2 weeks for review. + +**No rewrite needed** — the web code stays; Capacitor adds the native shell. diff --git a/docs-source/transitpin/index.md b/docs-source/transitpin/index.md new file mode 100644 index 0000000..0238130 --- /dev/null +++ b/docs-source/transitpin/index.md @@ -0,0 +1,42 @@ +# transitpin + + +> **Owner:** Germaine | **Status:** LIVE (dev) +> **Last Updated:** 2026-08-09 + +TransitPin is a real-time student transportation tracking SaaS built for the private contractor market — the companies school districts hire to move students. It replaces paper route sheets, manual parent texts, and clipboards with a live GPS platform: driver PWA, parent ETA portal, bell-schedule intelligence, and an admin dashboard for owners. + +Built from a direct client engagement with Village Express Transportation (Savannah, GA). + +## Access + +| Resource | URL | Location | Notes | +|---|---|---|---| +| Parent Portal | https://villageexpress.transitpin.com/ | Core (152.53.192.33) | Caddy reverse proxy | +| Driver PWA | https://villageexpress.transitpin.com/driver.html | Core | WebSocket at /ws/driver* | +| Admin Dashboard | https://villageexpress.transitpin.com/admin.html | Core | Dispatch map, driver tracking | +| WebSocket Relay | port 8210 (localhost) | Core | systemd: village-express.service | + +## Tech Stack + +- Frontend: Vanilla HTML/CSS/JS, Leaflet maps, TomTom traffic tiles +- Backend: WebSocket relay (Node.js), systemd service +- Reverse Proxy: Caddy (TLS auto-provision) +- GPS: Browser `watchPosition` API, Wake Lock API +- Data: driver-manifest.json (static routes), PWA manifest + +## Quick Start + +```bash +git clone https://git.itpropartner.com/ippadmin/transitpin.git +cd transitpin/ +# Start WebSocket relay +sudo systemctl start village-express +# Caddy serves static files + proxies /ws/* to WebSocket relay +``` + +## Related + +- [itpp-infrastructure](https://git.itpropartner.com/ippadmin/itpp-infrastructure) — server inventory, DNS +- [itpp-standards](https://git.itpropartner.com/ippadmin/itpp-standards) — docs standards and templates +- [transitpin-white-label](https://git.itpropartner.com/ippadmin/transitpin-white-label) — white-label portal builder diff --git a/docs-source/verdicttank/CHANGELOG.md b/docs-source/verdicttank/CHANGELOG.md new file mode 100644 index 0000000..6c9b139 --- /dev/null +++ b/docs-source/verdicttank/CHANGELOG.md @@ -0,0 +1,48 @@ +# VerdictTank — Changelog + +## 2026-08-07 + +### Rebrand (SharkTank → VerdictTank) +- Project renamed: /root/projects/sharktank → /root/projects/verdicttank +- Mockup moved: mockup.iamgmb.com/sharktank → mockup.iamgmb.com/verdicttank +- New proposal: proposals.iamgmb.com/verdicttank (incorporates all pipeline review feedback) +- Original SharkTank proposal archived with "superseded" banner +- Mockup landing page: removed all model names, added file upload (doc/docx/pdf), purged all em dashes +- Proposals index: VerdictTank (primary) + SharkTank (archived, grayed out) +- Critical review page: rebranded, em dashes purged + +### Pipeline Review Results +- First dogfood: ran proposal through full VerdictTank pipeline +- **Verdict: 3/3 Unanimous Conditional Go** +- Phase 1 (Research Agent): No direct competitor found — cross-vendor architecture is novel +- Phase 2 (Critic Agent): 4/10 average — 4 fatal flaws: trademark, pricing, financial model, no validation +- Phase 3a (Judge A): Ratified 85% of critic — pricing > name in priority, rejected co-founder as condition +- Phase 3b (Judge B): Caught overage incentive flaw, compound reliability risk (97.5% = 18hr/mo downtime) +- Phase 3c (Judge C): Meta-layer insight, training-data recursion (Year 3), liability asymmetry, SEO desert +- Critical review addendum: https://mockup.iamgmb.com/verdicttank/critical-review.html +- Priority-ranked 11-item action plan with reconciled timeline (Apr 2027 paid launch) +- Novel insights: product IS content, verdict confidence scoring, degraded-mode fallback needed + +### Updated Proposal (Post-Review) +- Full rebrand to VerdictTank throughout +- Tiered usage-based pricing (no "unlimited"): $19/$49/$99/$299 + $14.99 pay-per-review +- Financial model with churn (5-7%), CAC ($15-25), LTV ($210-290), LTV:CAC (~10:1) +- Real break-even estimate: 25-35 users (not 6) +- Timeline pushed to Mar-Apr 2027 paid launch +- New risks: training-data recursion, liability asymmetry, alignment drift, SEO desert +- Pipeline described by roles only (no model names) +- Pipeline self-review section with verdict banner +- Added: degraded-mode fallback, benchmark accuracy report, advisory board plan +- Product origin: dogfooding at IT Pro Partner + +### Pending +- Register verdicttank.com domain +- File VerdictTank trademark (USPTO Class 42) +- Landing page + waitlist at verdicttank.com +- Interview 20 target ICP users +- Publish benchmark accuracy report (20 proposals, known outcomes) +- Recruit 3+ named advisors +- FastAPI backend with job queue +- WeasyPrint PDF generation +- Email delivery via MXroute +- LiteLLM virtual key: verdicttank-prod diff --git a/docs-source/verdicttank/index.md b/docs-source/verdicttank/index.md new file mode 100644 index 0000000..3293bbc --- /dev/null +++ b/docs-source/verdicttank/index.md @@ -0,0 +1,70 @@ +# verdicttank + + +AI-powered business proposal review platform. Five specialized agents, three independent judges, majority-rules verdict. + +**Status:** PRE-LAUNCH (rebranded from SharkTank) +**Owner:** Germaine Brown +**Built by:** Sho'Nuff (Hermes Agent) +**Pipeline Verdict:** 3/3 Unanimous Conditional Go (Aug 7, 2026) + +## Access + +| Resource | URL | Access | +|---|---|---| +| Mockup (landing page) | https://mockup.iamgmb.com/verdicttank/ | Public | +| Critical Review | https://mockup.iamgmb.com/verdicttank/critical-review.html | Public | +| Proposal (updated) | https://proposals.iamgmb.com/verdicttank | Public | +| Proposal (original, archived) | https://proposals.iamgmb.com/sharktank | Public | +| Domain | verdicttank.com | Available, not registered | +| AI Costs | admin-ai key: `verdicttank-prod` | TBD | + +## Pipeline + +``` +Phase 1 → Research Agent — Live web verification with citations +Phase 2 → Critic Agent — 10-dimension brutal review, scored 1-10 +Phase 3 → Three judges in parallel (majority rules): + Judge A — Independent architecture + Judge B — Independent architecture + Judge C — Independent architecture + → PDF verdict delivered by email +``` + +**Cost per review:** $0.55 fully loaded +**Turnaround:** ~5-9 minutes +**Degraded mode:** Runs with 2 judges if 1 is down. Free re-run if only 1 available. + +## Pricing (Revised — Post-Review) + +| Tier | Price | Reviews/Month | +|---|---|---| +| Free | $0 | 1 | +| Starter | $19/mo | 5 ($3 overage) | +| Pro | $49/mo | 20 ($4 overage) | +| Scale | $99/mo | 50 ($5 overage) | +| Enterprise | $299/mo | 150 (custom) | + +Pay-per-review: $14.99. Annual: 20% discount. + +## Current State + +- Pipeline skill (`shark-tank-review`) fully built and tested +- Pipeline self-reviewed: 3/3 unanimous Conditional Go (Aug 7, 2026) +- Proposal page live (updated VerdictTank + archived SharkTank) +- Mockup landing page live at mockup.iamgmb.com/verdicttank +- Critical review addendum live +- Backend API (FastAPI) — to build +- PDF generation (WeasyPrint) — to build +- Email delivery (MXroute) — to build +- Domain: verdicttank.com — to register +- Trademark: USPTO Class 42 — to file + +## Key Risks (From Pipeline Review) + +- Pricing suicide (fixed: tiered usage-based) +- Sony trademark conflict (fixed: rebranded) +- Training-data recursion (Year 3+ — acknowledged, cap plan in place) +- Liability asymmetry (GO verdicts > NO-GO exposure) +- Alignment drift (models becoming less critical over time) +- SEO desert from rebrand (mitigation: 301 redirects, content marketing) diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..0c6ee46 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,47 @@ +site_name: "IT Pro Partner Docs" +site_url: "https://docs.itpropartner.com/" +repo_url: "https://git.itpropartner.com/ippadmin/itpp-docs" +edit_uri: edit/main/docs-source/ +docs_dir: docs-source + +theme: + name: material + palette: + scheme: slate + primary: indigo + accent: indigo + features: + - navigation.instant + - navigation.tracking + - navigation.tabs + - navigation.sections + - search.highlight + - search.share + +plugins: + - search + +markdown_extensions: + - admonition + - pymdownx.details + - pymdownx.superfences + - pymdownx.highlight + - tables + - toc: + permalink: true + +nav: + - Home: index.md + - Projects: + - ITPP Infrastructure: itpp-infrastructure/index.md + - ITPP Standards: itpp-standards/index.md + - TransitPin: transitpin/index.md + - HomeLab: homelab/index.md + - Scripts: scripts/index.md + - FleetTracker360: fleettracker360/index.md + - Shark Game: shark-game/index.md + - VerdictTank: verdicttank/index.md + - Apex Track: apex-track/index.md + - BoxPilot: boxpilot/index.md + - OSINT Tool: osint-tool/index.md + - LaunchCheck: launchcheck/index.md diff --git a/site/404.html b/site/404.html new file mode 100644 index 0000000..7a780f4 --- /dev/null +++ b/site/404.html @@ -0,0 +1,764 @@ + +<!doctype html> +<html lang="en" class="no-js"> + <head> + + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width,initial-scale=1"> + + + + + + + + + + <link rel="icon" href="/assets/images/favicon.png"> + <meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.7"> + + + + <title>IT Pro Partner Docs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ +
+ +

404 - Not found

+ +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/site/apex-track/CHANGELOG/index.html b/site/apex-track/CHANGELOG/index.html new file mode 100644 index 0000000..4aac4a0 --- /dev/null +++ b/site/apex-track/CHANGELOG/index.html @@ -0,0 +1,815 @@ + + + + + + + + + + + + + + + + + + + + + + + + Apex Track Experience — CHANGELOG - IT Pro Partner Docs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ +
+ + + + + + + + +

Apex Track Experience — CHANGELOG

+

2026-07-10 — Initial

+
    +
  • Created project repository and directory structure.
  • +
+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/site/apex-track/index.html b/site/apex-track/index.html new file mode 100644 index 0000000..38d1ce8 --- /dev/null +++ b/site/apex-track/index.html @@ -0,0 +1,943 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Apex Track - IT Pro Partner Docs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ +
+ + + + + + + + +

apex-track

+
+

Owner: Germaine | Status: PLANNED +Last Updated: 2026-08-09

+
+

Apex Track is a track event and racing experience management platform. Designed for track day organizers, racing clubs, and motorsport venues to manage event scheduling, participant registration, timing and scoring, and live results publishing.

+

Tech Stack

+
    +
  • TBD — architecture and stack decisions pending
  • +
+

Quick Start

+
git clone https://git.itpropartner.com/ippadmin/apex-track.git
+cd apex-track
+# Project in early planning phase — implementation to follow
+
+ + + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/site/assets/images/favicon.png b/site/assets/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..1cf13b9f9d978896599290a74f77d5dbe7d1655c GIT binary patch literal 1870 zcmV-U2eJ5xP)Gc)JR9QMau)O=X#!i9;T z37kk-upj^(fsR36MHs_+1RCI)NNu9}lD0S{B^g8PN?Ww(5|~L#Ng*g{WsqleV}|#l zz8@ri&cTzw_h33bHI+12+kK6WN$h#n5cD8OQt`5kw6p~9H3()bUQ8OS4Q4HTQ=1Ol z_JAocz`fLbT2^{`8n~UAo=#AUOf=SOq4pYkt;XbC&f#7lb$*7=$na!mWCQ`dBQsO0 zLFBSPj*N?#u5&pf2t4XjEGH|=pPQ8xh7tpx;US5Cx_Ju;!O`ya-yF`)b%TEt5>eP1ZX~}sjjA%FJF?h7cX8=b!DZl<6%Cv z*G0uvvU+vmnpLZ2paivG-(cd*y3$hCIcsZcYOGh{$&)A6*XX&kXZd3G8m)G$Zz-LV z^GF3VAW^Mdv!)4OM8EgqRiz~*Cji;uzl2uC9^=8I84vNp;ltJ|q-*uQwGp2ma6cY7 z;`%`!9UXO@fr&Ebapfs34OmS9^u6$)bJxrucutf>`dKPKT%%*d3XlFVKunp9 zasduxjrjs>f8V=D|J=XNZp;_Zy^WgQ$9WDjgY=z@stwiEBm9u5*|34&1Na8BMjjgf3+SHcr`5~>oz1Y?SW^=K z^bTyO6>Gar#P_W2gEMwq)ot3; zREHn~U&Dp0l6YT0&k-wLwYjb?5zGK`W6S2v+K>AM(95m2C20L|3m~rN8dprPr@t)5lsk9Hu*W z?pS990s;Ez=+Rj{x7p``4>+c0G5^pYnB1^!TL=(?HLHZ+HicG{~4F1d^5Awl_2!1jICM-!9eoLhbbT^;yHcefyTAaqRcY zmuctDopPT!%k+}x%lZRKnzykr2}}XfG_ne?nRQO~?%hkzo;@RN{P6o`&mMUWBYMTe z6i8ChtjX&gXl`nvrU>jah)2iNM%JdjqoaeaU%yVn!^70x-flljp6Q5tK}5}&X8&&G zX3fpb3E(!rH=zVI_9Gjl45w@{(ITqngWFe7@9{mX;tO25Z_8 zQHEpI+FkTU#4xu>RkN>b3Tnc3UpWzPXWm#o55GKF09j^Mh~)K7{QqbO_~(@CVq! zS<8954|P8mXN2MRs86xZ&Q4EfM@JB94b=(YGuk)s&^jiSF=t3*oNK3`rD{H`yQ?d; ztE=laAUoZx5?RC8*WKOj`%LXEkgDd>&^Q4M^z`%u0rg-It=hLCVsq!Z%^6eB-OvOT zFZ28TN&cRmgU}Elrnk43)!>Z1FCPL2K$7}gwzIc48NX}#!A1BpJP?#v5wkNprhV** z?Cpalt1oH&{r!o3eSKc&ap)iz2BTn_VV`4>9M^b3;(YY}4>#ML6{~(4mH+?%07*qo IM6N<$f(jP3KmY&$ literal 0 HcmV?d00001 diff --git a/site/assets/javascripts/bundle.d7400e89.min.js b/site/assets/javascripts/bundle.d7400e89.min.js new file mode 100644 index 0000000..6308ac3 --- /dev/null +++ b/site/assets/javascripts/bundle.d7400e89.min.js @@ -0,0 +1,16 @@ +"use strict";(()=>{var Zi=Object.create;var _r=Object.defineProperty;var ea=Object.getOwnPropertyDescriptor;var ta=Object.getOwnPropertyNames,Bt=Object.getOwnPropertySymbols,ra=Object.getPrototypeOf,Ar=Object.prototype.hasOwnProperty,bo=Object.prototype.propertyIsEnumerable;var ho=(e,t,r)=>t in e?_r(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,P=(e,t)=>{for(var r in t||(t={}))Ar.call(t,r)&&ho(e,r,t[r]);if(Bt)for(var r of Bt(t))bo.call(t,r)&&ho(e,r,t[r]);return e};var vo=(e,t)=>{var r={};for(var o in e)Ar.call(e,o)&&t.indexOf(o)<0&&(r[o]=e[o]);if(e!=null&&Bt)for(var o of Bt(e))t.indexOf(o)<0&&bo.call(e,o)&&(r[o]=e[o]);return r};var Cr=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var oa=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of ta(t))!Ar.call(e,n)&&n!==r&&_r(e,n,{get:()=>t[n],enumerable:!(o=ea(t,n))||o.enumerable});return e};var $t=(e,t,r)=>(r=e!=null?Zi(ra(e)):{},oa(t||!e||!e.__esModule?_r(r,"default",{value:e,enumerable:!0}):r,e));var go=(e,t,r)=>new Promise((o,n)=>{var i=c=>{try{a(r.next(c))}catch(p){n(p)}},s=c=>{try{a(r.throw(c))}catch(p){n(p)}},a=c=>c.done?o(c.value):Promise.resolve(c.value).then(i,s);a((r=r.apply(e,t)).next())});var xo=Cr((kr,yo)=>{(function(e,t){typeof kr=="object"&&typeof yo!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(kr,(function(){"use strict";function e(r){var o=!0,n=!1,i=null,s={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function a(k){return!!(k&&k!==document&&k.nodeName!=="HTML"&&k.nodeName!=="BODY"&&"classList"in k&&"contains"in k.classList)}function c(k){var ut=k.type,je=k.tagName;return!!(je==="INPUT"&&s[ut]&&!k.readOnly||je==="TEXTAREA"&&!k.readOnly||k.isContentEditable)}function p(k){k.classList.contains("focus-visible")||(k.classList.add("focus-visible"),k.setAttribute("data-focus-visible-added",""))}function l(k){k.hasAttribute("data-focus-visible-added")&&(k.classList.remove("focus-visible"),k.removeAttribute("data-focus-visible-added"))}function f(k){k.metaKey||k.altKey||k.ctrlKey||(a(r.activeElement)&&p(r.activeElement),o=!0)}function u(k){o=!1}function d(k){a(k.target)&&(o||c(k.target))&&p(k.target)}function v(k){a(k.target)&&(k.target.classList.contains("focus-visible")||k.target.hasAttribute("data-focus-visible-added"))&&(n=!0,window.clearTimeout(i),i=window.setTimeout(function(){n=!1},100),l(k.target))}function S(k){document.visibilityState==="hidden"&&(n&&(o=!0),X())}function X(){document.addEventListener("mousemove",ee),document.addEventListener("mousedown",ee),document.addEventListener("mouseup",ee),document.addEventListener("pointermove",ee),document.addEventListener("pointerdown",ee),document.addEventListener("pointerup",ee),document.addEventListener("touchmove",ee),document.addEventListener("touchstart",ee),document.addEventListener("touchend",ee)}function re(){document.removeEventListener("mousemove",ee),document.removeEventListener("mousedown",ee),document.removeEventListener("mouseup",ee),document.removeEventListener("pointermove",ee),document.removeEventListener("pointerdown",ee),document.removeEventListener("pointerup",ee),document.removeEventListener("touchmove",ee),document.removeEventListener("touchstart",ee),document.removeEventListener("touchend",ee)}function ee(k){k.target.nodeName&&k.target.nodeName.toLowerCase()==="html"||(o=!1,re())}document.addEventListener("keydown",f,!0),document.addEventListener("mousedown",u,!0),document.addEventListener("pointerdown",u,!0),document.addEventListener("touchstart",u,!0),document.addEventListener("visibilitychange",S,!0),X(),r.addEventListener("focus",d,!0),r.addEventListener("blur",v,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)}))});var ro=Cr((jy,Rn)=>{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var qa=/["'&<>]/;Rn.exports=Ka;function Ka(e){var t=""+e,r=qa.exec(t);if(!r)return t;var o,n="",i=0,s=0;for(i=r.index;i{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof Nt=="object"&&typeof io=="object"?io.exports=r():typeof define=="function"&&define.amd?define([],r):typeof Nt=="object"?Nt.ClipboardJS=r():t.ClipboardJS=r()})(Nt,function(){return(function(){var e={686:(function(o,n,i){"use strict";i.d(n,{default:function(){return Xi}});var s=i(279),a=i.n(s),c=i(370),p=i.n(c),l=i(817),f=i.n(l);function u(q){try{return document.execCommand(q)}catch(C){return!1}}var d=function(C){var _=f()(C);return u("cut"),_},v=d;function S(q){var C=document.documentElement.getAttribute("dir")==="rtl",_=document.createElement("textarea");_.style.fontSize="12pt",_.style.border="0",_.style.padding="0",_.style.margin="0",_.style.position="absolute",_.style[C?"right":"left"]="-9999px";var D=window.pageYOffset||document.documentElement.scrollTop;return _.style.top="".concat(D,"px"),_.setAttribute("readonly",""),_.value=q,_}var X=function(C,_){var D=S(C);_.container.appendChild(D);var N=f()(D);return u("copy"),D.remove(),N},re=function(C){var _=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},D="";return typeof C=="string"?D=X(C,_):C instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(C==null?void 0:C.type)?D=X(C.value,_):(D=f()(C),u("copy")),D},ee=re;function k(q){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?k=function(_){return typeof _}:k=function(_){return _&&typeof Symbol=="function"&&_.constructor===Symbol&&_!==Symbol.prototype?"symbol":typeof _},k(q)}var ut=function(){var C=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},_=C.action,D=_===void 0?"copy":_,N=C.container,G=C.target,We=C.text;if(D!=="copy"&&D!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(G!==void 0)if(G&&k(G)==="object"&&G.nodeType===1){if(D==="copy"&&G.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(D==="cut"&&(G.hasAttribute("readonly")||G.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(We)return ee(We,{container:N});if(G)return D==="cut"?v(G):ee(G,{container:N})},je=ut;function R(q){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?R=function(_){return typeof _}:R=function(_){return _&&typeof Symbol=="function"&&_.constructor===Symbol&&_!==Symbol.prototype?"symbol":typeof _},R(q)}function se(q,C){if(!(q instanceof C))throw new TypeError("Cannot call a class as a function")}function ce(q,C){for(var _=0;_0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof N.action=="function"?N.action:this.defaultAction,this.target=typeof N.target=="function"?N.target:this.defaultTarget,this.text=typeof N.text=="function"?N.text:this.defaultText,this.container=R(N.container)==="object"?N.container:document.body}},{key:"listenClick",value:function(N){var G=this;this.listener=p()(N,"click",function(We){return G.onClick(We)})}},{key:"onClick",value:function(N){var G=N.delegateTarget||N.currentTarget,We=this.action(G)||"copy",Yt=je({action:We,container:this.container,target:this.target(G),text:this.text(G)});this.emit(Yt?"success":"error",{action:We,text:Yt,trigger:G,clearSelection:function(){G&&G.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(N){return Mr("action",N)}},{key:"defaultTarget",value:function(N){var G=Mr("target",N);if(G)return document.querySelector(G)}},{key:"defaultText",value:function(N){return Mr("text",N)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(N){var G=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return ee(N,G)}},{key:"cut",value:function(N){return v(N)}},{key:"isSupported",value:function(){var N=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],G=typeof N=="string"?[N]:N,We=!!document.queryCommandSupported;return G.forEach(function(Yt){We=We&&!!document.queryCommandSupported(Yt)}),We}}]),_})(a()),Xi=Ji}),828:(function(o){var n=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function s(a,c){for(;a&&a.nodeType!==n;){if(typeof a.matches=="function"&&a.matches(c))return a;a=a.parentNode}}o.exports=s}),438:(function(o,n,i){var s=i(828);function a(l,f,u,d,v){var S=p.apply(this,arguments);return l.addEventListener(u,S,v),{destroy:function(){l.removeEventListener(u,S,v)}}}function c(l,f,u,d,v){return typeof l.addEventListener=="function"?a.apply(null,arguments):typeof u=="function"?a.bind(null,document).apply(null,arguments):(typeof l=="string"&&(l=document.querySelectorAll(l)),Array.prototype.map.call(l,function(S){return a(S,f,u,d,v)}))}function p(l,f,u,d){return function(v){v.delegateTarget=s(v.target,f),v.delegateTarget&&d.call(l,v)}}o.exports=c}),879:(function(o,n){n.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},n.nodeList=function(i){var s=Object.prototype.toString.call(i);return i!==void 0&&(s==="[object NodeList]"||s==="[object HTMLCollection]")&&"length"in i&&(i.length===0||n.node(i[0]))},n.string=function(i){return typeof i=="string"||i instanceof String},n.fn=function(i){var s=Object.prototype.toString.call(i);return s==="[object Function]"}}),370:(function(o,n,i){var s=i(879),a=i(438);function c(u,d,v){if(!u&&!d&&!v)throw new Error("Missing required arguments");if(!s.string(d))throw new TypeError("Second argument must be a String");if(!s.fn(v))throw new TypeError("Third argument must be a Function");if(s.node(u))return p(u,d,v);if(s.nodeList(u))return l(u,d,v);if(s.string(u))return f(u,d,v);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function p(u,d,v){return u.addEventListener(d,v),{destroy:function(){u.removeEventListener(d,v)}}}function l(u,d,v){return Array.prototype.forEach.call(u,function(S){S.addEventListener(d,v)}),{destroy:function(){Array.prototype.forEach.call(u,function(S){S.removeEventListener(d,v)})}}}function f(u,d,v){return a(document.body,u,d,v)}o.exports=c}),817:(function(o){function n(i){var s;if(i.nodeName==="SELECT")i.focus(),s=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var a=i.hasAttribute("readonly");a||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),a||i.removeAttribute("readonly"),s=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var c=window.getSelection(),p=document.createRange();p.selectNodeContents(i),c.removeAllRanges(),c.addRange(p),s=c.toString()}return s}o.exports=n}),279:(function(o){function n(){}n.prototype={on:function(i,s,a){var c=this.e||(this.e={});return(c[i]||(c[i]=[])).push({fn:s,ctx:a}),this},once:function(i,s,a){var c=this;function p(){c.off(i,p),s.apply(a,arguments)}return p._=s,this.on(i,p,a)},emit:function(i){var s=[].slice.call(arguments,1),a=((this.e||(this.e={}))[i]||[]).slice(),c=0,p=a.length;for(c;c0&&i[i.length-1])&&(p[0]===6||p[0]===2)){r=0;continue}if(p[0]===3&&(!i||p[1]>i[0]&&p[1]=e.length&&(e=void 0),{value:e&&e[o++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function K(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var o=r.call(e),n,i=[],s;try{for(;(t===void 0||t-- >0)&&!(n=o.next()).done;)i.push(n.value)}catch(a){s={error:a}}finally{try{n&&!n.done&&(r=o.return)&&r.call(o)}finally{if(s)throw s.error}}return i}function B(e,t,r){if(r||arguments.length===2)for(var o=0,n=t.length,i;o1||c(d,S)})},v&&(n[d]=v(n[d])))}function c(d,v){try{p(o[d](v))}catch(S){u(i[0][3],S)}}function p(d){d.value instanceof dt?Promise.resolve(d.value.v).then(l,f):u(i[0][2],d)}function l(d){c("next",d)}function f(d){c("throw",d)}function u(d,v){d(v),i.shift(),i.length&&c(i[0][0],i[0][1])}}function To(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof Oe=="function"?Oe(e):e[Symbol.iterator](),r={},o("next"),o("throw"),o("return"),r[Symbol.asyncIterator]=function(){return this},r);function o(i){r[i]=e[i]&&function(s){return new Promise(function(a,c){s=e[i](s),n(a,c,s.done,s.value)})}}function n(i,s,a,c){Promise.resolve(c).then(function(p){i({value:p,done:a})},s)}}function I(e){return typeof e=="function"}function yt(e){var t=function(o){Error.call(o),o.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var Jt=yt(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: +`+r.map(function(o,n){return n+1+") "+o.toString()}).join(` + `):"",this.name="UnsubscriptionError",this.errors=r}});function Ze(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var qe=(function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,o,n,i;if(!this.closed){this.closed=!0;var s=this._parentage;if(s)if(this._parentage=null,Array.isArray(s))try{for(var a=Oe(s),c=a.next();!c.done;c=a.next()){var p=c.value;p.remove(this)}}catch(S){t={error:S}}finally{try{c&&!c.done&&(r=a.return)&&r.call(a)}finally{if(t)throw t.error}}else s.remove(this);var l=this.initialTeardown;if(I(l))try{l()}catch(S){i=S instanceof Jt?S.errors:[S]}var f=this._finalizers;if(f){this._finalizers=null;try{for(var u=Oe(f),d=u.next();!d.done;d=u.next()){var v=d.value;try{So(v)}catch(S){i=i!=null?i:[],S instanceof Jt?i=B(B([],K(i)),K(S.errors)):i.push(S)}}}catch(S){o={error:S}}finally{try{d&&!d.done&&(n=u.return)&&n.call(u)}finally{if(o)throw o.error}}}if(i)throw new Jt(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)So(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&Ze(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&Ze(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=(function(){var t=new e;return t.closed=!0,t})(),e})();var $r=qe.EMPTY;function Xt(e){return e instanceof qe||e&&"closed"in e&&I(e.remove)&&I(e.add)&&I(e.unsubscribe)}function So(e){I(e)?e():e.unsubscribe()}var De={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var xt={setTimeout:function(e,t){for(var r=[],o=2;o0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var o=this,n=this,i=n.hasError,s=n.isStopped,a=n.observers;return i||s?$r:(this.currentObservers=null,a.push(r),new qe(function(){o.currentObservers=null,Ze(a,r)}))},t.prototype._checkFinalizedStatuses=function(r){var o=this,n=o.hasError,i=o.thrownError,s=o.isStopped;n?r.error(i):s&&r.complete()},t.prototype.asObservable=function(){var r=new F;return r.source=this,r},t.create=function(r,o){return new Ho(r,o)},t})(F);var Ho=(function(e){ie(t,e);function t(r,o){var n=e.call(this)||this;return n.destination=r,n.source=o,n}return t.prototype.next=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.next)===null||n===void 0||n.call(o,r)},t.prototype.error=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.error)===null||n===void 0||n.call(o,r)},t.prototype.complete=function(){var r,o;(o=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||o===void 0||o.call(r)},t.prototype._subscribe=function(r){var o,n;return(n=(o=this.source)===null||o===void 0?void 0:o.subscribe(r))!==null&&n!==void 0?n:$r},t})(T);var jr=(function(e){ie(t,e);function t(r){var o=e.call(this)||this;return o._value=r,o}return Object.defineProperty(t.prototype,"value",{get:function(){return this.getValue()},enumerable:!1,configurable:!0}),t.prototype._subscribe=function(r){var o=e.prototype._subscribe.call(this,r);return!o.closed&&r.next(this._value),o},t.prototype.getValue=function(){var r=this,o=r.hasError,n=r.thrownError,i=r._value;if(o)throw n;return this._throwIfClosed(),i},t.prototype.next=function(r){e.prototype.next.call(this,this._value=r)},t})(T);var Rt={now:function(){return(Rt.delegate||Date).now()},delegate:void 0};var It=(function(e){ie(t,e);function t(r,o,n){r===void 0&&(r=1/0),o===void 0&&(o=1/0),n===void 0&&(n=Rt);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=o,i._timestampProvider=n,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=o===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,o),i}return t.prototype.next=function(r){var o=this,n=o.isStopped,i=o._buffer,s=o._infiniteTimeWindow,a=o._timestampProvider,c=o._windowTime;n||(i.push(r),!s&&i.push(a.now()+c)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var o=this._innerSubscribe(r),n=this,i=n._infiniteTimeWindow,s=n._buffer,a=s.slice(),c=0;c0?e.prototype.schedule.call(this,r,o):(this.delay=o,this.state=r,this.scheduler.flush(this),this)},t.prototype.execute=function(r,o){return o>0||this.closed?e.prototype.execute.call(this,r,o):this._execute(r,o)},t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!=null&&n>0||n==null&&this.delay>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.flush(this),0)},t})(St);var Ro=(function(e){ie(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t})(Ot);var Dr=new Ro(Po);var Io=(function(e){ie(t,e);function t(r,o){var n=e.call(this,r,o)||this;return n.scheduler=r,n.work=o,n}return t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!==null&&n>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.actions.push(this),r._scheduled||(r._scheduled=Tt.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,o,n){var i;if(n===void 0&&(n=0),n!=null?n>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,o,n);var s=r.actions;o!=null&&o===r._scheduled&&((i=s[s.length-1])===null||i===void 0?void 0:i.id)!==o&&(Tt.cancelAnimationFrame(o),r._scheduled=void 0)},t})(St);var Fo=(function(e){ie(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var o;r?o=r.id:(o=this._scheduled,this._scheduled=void 0);var n=this.actions,i;r=r||n.shift();do if(i=r.execute(r.state,r.delay))break;while((r=n[0])&&r.id===o&&n.shift());if(this._active=!1,i){for(;(r=n[0])&&r.id===o&&n.shift();)r.unsubscribe();throw i}},t})(Ot);var ye=new Fo(Io);var y=new F(function(e){return e.complete()});function tr(e){return e&&I(e.schedule)}function Vr(e){return e[e.length-1]}function pt(e){return I(Vr(e))?e.pop():void 0}function Fe(e){return tr(Vr(e))?e.pop():void 0}function rr(e,t){return typeof Vr(e)=="number"?e.pop():t}var Lt=(function(e){return e&&typeof e.length=="number"&&typeof e!="function"});function or(e){return I(e==null?void 0:e.then)}function nr(e){return I(e[wt])}function ir(e){return Symbol.asyncIterator&&I(e==null?void 0:e[Symbol.asyncIterator])}function ar(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function fa(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var sr=fa();function cr(e){return I(e==null?void 0:e[sr])}function pr(e){return wo(this,arguments,function(){var r,o,n,i;return Gt(this,function(s){switch(s.label){case 0:r=e.getReader(),s.label=1;case 1:s.trys.push([1,,9,10]),s.label=2;case 2:return[4,dt(r.read())];case 3:return o=s.sent(),n=o.value,i=o.done,i?[4,dt(void 0)]:[3,5];case 4:return[2,s.sent()];case 5:return[4,dt(n)];case 6:return[4,s.sent()];case 7:return s.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function lr(e){return I(e==null?void 0:e.getReader)}function U(e){if(e instanceof F)return e;if(e!=null){if(nr(e))return ua(e);if(Lt(e))return da(e);if(or(e))return ha(e);if(ir(e))return jo(e);if(cr(e))return ba(e);if(lr(e))return va(e)}throw ar(e)}function ua(e){return new F(function(t){var r=e[wt]();if(I(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function da(e){return new F(function(t){for(var r=0;r=2;return function(o){return o.pipe(e?g(function(n,i){return e(n,i,o)}):be,Ee(1),r?Qe(t):tn(function(){return new fr}))}}function Yr(e){return e<=0?function(){return y}:E(function(t,r){var o=[];t.subscribe(w(r,function(n){o.push(n),e=2,!0))}function le(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new T}:t,o=e.resetOnError,n=o===void 0?!0:o,i=e.resetOnComplete,s=i===void 0?!0:i,a=e.resetOnRefCountZero,c=a===void 0?!0:a;return function(p){var l,f,u,d=0,v=!1,S=!1,X=function(){f==null||f.unsubscribe(),f=void 0},re=function(){X(),l=u=void 0,v=S=!1},ee=function(){var k=l;re(),k==null||k.unsubscribe()};return E(function(k,ut){d++,!S&&!v&&X();var je=u=u!=null?u:r();ut.add(function(){d--,d===0&&!S&&!v&&(f=Br(ee,c))}),je.subscribe(ut),!l&&d>0&&(l=new bt({next:function(R){return je.next(R)},error:function(R){S=!0,X(),f=Br(re,n,R),je.error(R)},complete:function(){v=!0,X(),f=Br(re,s),je.complete()}}),U(k).subscribe(l))})(p)}}function Br(e,t){for(var r=[],o=2;oe.next(document)),e}function M(e,t=document){return Array.from(t.querySelectorAll(e))}function j(e,t=document){let r=ue(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function ue(e,t=document){return t.querySelector(e)||void 0}function Ne(){var e,t,r,o;return(o=(r=(t=(e=document.activeElement)==null?void 0:e.shadowRoot)==null?void 0:t.activeElement)!=null?r:document.activeElement)!=null?o:void 0}var Ra=L(h(document.body,"focusin"),h(document.body,"focusout")).pipe(Ae(1),Q(void 0),m(()=>Ne()||document.body),Z(1));function Ye(e){return Ra.pipe(m(t=>e.contains(t)),Y())}function it(e,t){return H(()=>L(h(e,"mouseenter").pipe(m(()=>!0)),h(e,"mouseleave").pipe(m(()=>!1))).pipe(t?jt(r=>He(+!r*t)):be,Q(e.matches(":hover"))))}function sn(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)sn(e,r)}function x(e,t,...r){let o=document.createElement(e);if(t)for(let n of Object.keys(t))typeof t[n]!="undefined"&&(typeof t[n]!="boolean"?o.setAttribute(n,t[n]):o.setAttribute(n,""));for(let n of r)sn(o,n);return o}function br(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function _t(e){let t=x("script",{src:e});return H(()=>(document.head.appendChild(t),L(h(t,"load"),h(t,"error").pipe(b(()=>Nr(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(m(()=>{}),A(()=>document.head.removeChild(t)),Ee(1))))}var cn=new T,Ia=H(()=>typeof ResizeObserver=="undefined"?_t("https://unpkg.com/resize-observer-polyfill"):$(void 0)).pipe(m(()=>new ResizeObserver(e=>e.forEach(t=>cn.next(t)))),b(e=>L(tt,$(e)).pipe(A(()=>e.disconnect()))),Z(1));function de(e){return{width:e.offsetWidth,height:e.offsetHeight}}function Le(e){let t=e;for(;t.clientWidth===0&&t.parentElement;)t=t.parentElement;return Ia.pipe(O(r=>r.observe(t)),b(r=>cn.pipe(g(o=>o.target===t),A(()=>r.unobserve(t)))),m(()=>de(e)),Q(de(e)))}function At(e){return{width:e.scrollWidth,height:e.scrollHeight}}function vr(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}function pn(e){let t=[],r=e.parentElement;for(;r;)(e.clientWidth>r.clientWidth||e.clientHeight>r.clientHeight)&&t.push(r),r=(e=r).parentElement;return t.length===0&&t.push(document.documentElement),t}function Be(e){return{x:e.offsetLeft,y:e.offsetTop}}function ln(e){let t=e.getBoundingClientRect();return{x:t.x+window.scrollX,y:t.y+window.scrollY}}function mn(e){return L(h(window,"load"),h(window,"resize")).pipe($e(0,ye),m(()=>Be(e)),Q(Be(e)))}function gr(e){return{x:e.scrollLeft,y:e.scrollTop}}function Ge(e){return L(h(e,"scroll"),h(window,"scroll"),h(window,"resize")).pipe($e(0,ye),m(()=>gr(e)),Q(gr(e)))}var fn=new T,Fa=H(()=>$(new IntersectionObserver(e=>{for(let t of e)fn.next(t)},{threshold:0}))).pipe(b(e=>L(tt,$(e)).pipe(A(()=>e.disconnect()))),Z(1));function mt(e){return Fa.pipe(O(t=>t.observe(e)),b(t=>fn.pipe(g(({target:r})=>r===e),A(()=>t.unobserve(e)),m(({isIntersecting:r})=>r))))}function un(e,t=16){return Ge(e).pipe(m(({y:r})=>{let o=de(e),n=At(e);return r>=n.height-o.height-t}),Y())}var yr={drawer:j("[data-md-toggle=drawer]"),search:j("[data-md-toggle=search]")};function dn(e){return yr[e].checked}function at(e,t){yr[e].checked!==t&&yr[e].click()}function Je(e){let t=yr[e];return h(t,"change").pipe(m(()=>t.checked),Q(t.checked))}function ja(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function Ua(){return L(h(window,"compositionstart").pipe(m(()=>!0)),h(window,"compositionend").pipe(m(()=>!1))).pipe(Q(!1))}function hn(){let e=h(window,"keydown").pipe(g(t=>!(t.metaKey||t.ctrlKey)),m(t=>({mode:dn("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),g(({mode:t,type:r})=>{if(t==="global"){let o=Ne();if(typeof o!="undefined")return!ja(o,r)}return!0}),le());return Ua().pipe(b(t=>t?y:e))}function we(){return new URL(location.href)}function st(e,t=!1){if(V("navigation.instant")&&!t){let r=x("a",{href:e.href});document.body.appendChild(r),r.click(),r.remove()}else location.href=e.href}function bn(){return new T}function vn(){return location.hash.slice(1)}function gn(e){let t=x("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function Zr(e){return L(h(window,"hashchange"),e).pipe(m(vn),Q(vn()),g(t=>t.length>0),Z(1))}function yn(e){return Zr(e).pipe(m(t=>ue(`[id="${t}"]`)),g(t=>typeof t!="undefined"))}function Wt(e){let t=matchMedia(e);return ur(r=>t.addListener(()=>r(t.matches))).pipe(Q(t.matches))}function xn(){let e=matchMedia("print");return L(h(window,"beforeprint").pipe(m(()=>!0)),h(window,"afterprint").pipe(m(()=>!1))).pipe(Q(e.matches))}function eo(e,t){return e.pipe(b(r=>r?t():y))}function to(e,t){return new F(r=>{let o=new XMLHttpRequest;return o.open("GET",`${e}`),o.responseType="blob",o.addEventListener("load",()=>{o.status>=200&&o.status<300?(r.next(o.response),r.complete()):r.error(new Error(o.statusText))}),o.addEventListener("error",()=>{r.error(new Error("Network error"))}),o.addEventListener("abort",()=>{r.complete()}),typeof(t==null?void 0:t.progress$)!="undefined"&&(o.addEventListener("progress",n=>{var i;if(n.lengthComputable)t.progress$.next(n.loaded/n.total*100);else{let s=(i=o.getResponseHeader("Content-Length"))!=null?i:0;t.progress$.next(n.loaded/+s*100)}}),t.progress$.next(5)),o.send(),()=>o.abort()})}function ze(e,t){return to(e,t).pipe(b(r=>r.text()),m(r=>JSON.parse(r)),Z(1))}function xr(e,t){let r=new DOMParser;return to(e,t).pipe(b(o=>o.text()),m(o=>r.parseFromString(o,"text/html")),Z(1))}function En(e,t){let r=new DOMParser;return to(e,t).pipe(b(o=>o.text()),m(o=>r.parseFromString(o,"text/xml")),Z(1))}function wn(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function Tn(){return L(h(window,"scroll",{passive:!0}),h(window,"resize",{passive:!0})).pipe(m(wn),Q(wn()))}function Sn(){return{width:innerWidth,height:innerHeight}}function On(){return h(window,"resize",{passive:!0}).pipe(m(Sn),Q(Sn()))}function Ln(){return z([Tn(),On()]).pipe(m(([e,t])=>({offset:e,size:t})),Z(1))}function Er(e,{viewport$:t,header$:r}){let o=t.pipe(ne("size")),n=z([o,r]).pipe(m(()=>Be(e)));return z([r,t,n]).pipe(m(([{height:i},{offset:s,size:a},{x:c,y:p}])=>({offset:{x:s.x-c,y:s.y-p+i},size:a})))}function Wa(e){return h(e,"message",t=>t.data)}function Da(e){let t=new T;return t.subscribe(r=>e.postMessage(r)),t}function Mn(e,t=new Worker(e)){let r=Wa(t),o=Da(t),n=new T;n.subscribe(o);let i=o.pipe(oe(),ae(!0));return n.pipe(oe(),Ve(r.pipe(W(i))),le())}var Va=j("#__config"),Ct=JSON.parse(Va.textContent);Ct.base=`${new URL(Ct.base,we())}`;function Te(){return Ct}function V(e){return Ct.features.includes(e)}function Me(e,t){return typeof t!="undefined"?Ct.translations[e].replace("#",t.toString()):Ct.translations[e]}function Ce(e,t=document){return j(`[data-md-component=${e}]`,t)}function me(e,t=document){return M(`[data-md-component=${e}]`,t)}function Na(e){let t=j(".md-typeset > :first-child",e);return h(t,"click",{once:!0}).pipe(m(()=>j(".md-typeset",e)),m(r=>({hash:__md_hash(r.innerHTML)})))}function _n(e){if(!V("announce.dismiss")||!e.childElementCount)return y;if(!e.hidden){let t=j(".md-typeset",e);__md_hash(t.innerHTML)===__md_get("__announce")&&(e.hidden=!0)}return H(()=>{let t=new T;return t.subscribe(({hash:r})=>{e.hidden=!0,__md_set("__announce",r)}),Na(e).pipe(O(r=>t.next(r)),A(()=>t.complete()),m(r=>P({ref:e},r)))})}function za(e,{target$:t}){return t.pipe(m(r=>({hidden:r!==e})))}function An(e,t){let r=new T;return r.subscribe(({hidden:o})=>{e.hidden=o}),za(e,t).pipe(O(o=>r.next(o)),A(()=>r.complete()),m(o=>P({ref:e},o)))}function Dt(e,t){return t==="inline"?x("div",{class:"md-tooltip md-tooltip--inline",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"})):x("div",{class:"md-tooltip",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"}))}function wr(...e){return x("div",{class:"md-tooltip2",role:"dialog"},x("div",{class:"md-tooltip2__inner md-typeset"},e))}function Cn(...e){return x("div",{class:"md-tooltip2",role:"tooltip"},x("div",{class:"md-tooltip2__inner md-typeset"},e))}function kn(e,t){if(t=t?`${t}_annotation_${e}`:void 0,t){let r=t?`#${t}`:void 0;return x("aside",{class:"md-annotation",tabIndex:0},Dt(t),x("a",{href:r,class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}else return x("aside",{class:"md-annotation",tabIndex:0},Dt(t),x("span",{class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}function Hn(e){return x("button",{class:"md-code__button",title:Me("clipboard.copy"),"data-clipboard-target":`#${e} > code`,"data-md-type":"copy"})}function $n(){return x("button",{class:"md-code__button",title:"Toggle line selection","data-md-type":"select"})}function Pn(){return x("nav",{class:"md-code__nav"})}var In=$t(ro());function oo(e,t){let r=t&2,o=t&1,n=Object.keys(e.terms).filter(c=>!e.terms[c]).reduce((c,p)=>[...c,x("del",null,(0,In.default)(p))," "],[]).slice(0,-1),i=Te(),s=new URL(e.location,i.base);V("search.highlight")&&s.searchParams.set("h",Object.entries(e.terms).filter(([,c])=>c).reduce((c,[p])=>`${c} ${p}`.trim(),""));let{tags:a}=Te();return x("a",{href:`${s}`,class:"md-search-result__link",tabIndex:-1},x("article",{class:"md-search-result__article md-typeset","data-md-score":e.score.toFixed(2)},r>0&&x("div",{class:"md-search-result__icon md-icon"}),r>0&&x("h1",null,e.title),r<=0&&x("h2",null,e.title),o>0&&e.text.length>0&&e.text,e.tags&&x("nav",{class:"md-tags"},e.tags.map(c=>{let p=a?c in a?`md-tag-icon md-tag--${a[c]}`:"md-tag-icon":"";return x("span",{class:`md-tag ${p}`},c)})),o>0&&n.length>0&&x("p",{class:"md-search-result__terms"},Me("search.result.term.missing"),": ",...n)))}function Fn(e){let t=e[0].score,r=[...e],o=Te(),n=r.findIndex(l=>!`${new URL(l.location,o.base)}`.includes("#")),[i]=r.splice(n,1),s=r.findIndex(l=>l.scoreoo(l,1)),...c.length?[x("details",{class:"md-search-result__more"},x("summary",{tabIndex:-1},x("div",null,c.length>0&&c.length===1?Me("search.result.more.one"):Me("search.result.more.other",c.length))),...c.map(l=>oo(l,1)))]:[]];return x("li",{class:"md-search-result__item"},p)}function jn(e){return x("ul",{class:"md-source__facts"},Object.entries(e).map(([t,r])=>x("li",{class:`md-source__fact md-source__fact--${t}`},typeof r=="number"?br(r):r)))}function no(e){let t=`tabbed-control tabbed-control--${e}`;return x("div",{class:t,hidden:!0},x("button",{class:"tabbed-button",tabIndex:-1,"aria-hidden":"true"}))}function Un(e){return x("div",{class:"md-typeset__scrollwrap"},x("div",{class:"md-typeset__table"},e))}function Qa(e){var o;let t=Te(),r=new URL(`../${e.version}/`,t.base);return x("li",{class:"md-version__item"},x("a",{href:`${r}`,class:"md-version__link"},e.title,((o=t.version)==null?void 0:o.alias)&&e.aliases.length>0&&x("span",{class:"md-version__alias"},e.aliases[0])))}function Wn(e,t){var o;let r=Te();return e=e.filter(n=>{var i;return!((i=n.properties)!=null&&i.hidden)}),x("div",{class:"md-version"},x("button",{class:"md-version__current","aria-label":Me("select.version")},t.title,((o=r.version)==null?void 0:o.alias)&&t.aliases.length>0&&x("span",{class:"md-version__alias"},t.aliases[0])),x("ul",{class:"md-version__list"},e.map(Qa)))}var Ya=0;function Ba(e,t=250){let r=z([Ye(e),it(e,t)]).pipe(m(([n,i])=>n||i),Y()),o=H(()=>pn(e)).pipe(J(Ge),gt(1),Pe(r),m(()=>ln(e)));return r.pipe(Re(n=>n),b(()=>z([r,o])),m(([n,i])=>({active:n,offset:i})),le())}function Vt(e,t,r=250){let{content$:o,viewport$:n}=t,i=`__tooltip2_${Ya++}`;return H(()=>{let s=new T,a=new jr(!1);s.pipe(oe(),ae(!1)).subscribe(a);let c=a.pipe(jt(l=>He(+!l*250,Dr)),Y(),b(l=>l?o:y),O(l=>l.id=i),le());z([s.pipe(m(({active:l})=>l)),c.pipe(b(l=>it(l,250)),Q(!1))]).pipe(m(l=>l.some(f=>f))).subscribe(a);let p=a.pipe(g(l=>l),te(c,n),m(([l,f,{size:u}])=>{let d=e.getBoundingClientRect(),v=d.width/2;if(f.role==="tooltip")return{x:v,y:8+d.height};if(d.y>=u.height/2){let{height:S}=de(f);return{x:v,y:-16-S}}else return{x:v,y:16+d.height}}));return z([c,s,p]).subscribe(([l,{offset:f},u])=>{l.style.setProperty("--md-tooltip-host-x",`${f.x}px`),l.style.setProperty("--md-tooltip-host-y",`${f.y}px`),l.style.setProperty("--md-tooltip-x",`${u.x}px`),l.style.setProperty("--md-tooltip-y",`${u.y}px`),l.classList.toggle("md-tooltip2--top",u.y<0),l.classList.toggle("md-tooltip2--bottom",u.y>=0)}),a.pipe(g(l=>l),te(c,(l,f)=>f),g(l=>l.role==="tooltip")).subscribe(l=>{let f=de(j(":scope > *",l));l.style.setProperty("--md-tooltip-width",`${f.width}px`),l.style.setProperty("--md-tooltip-tail","0px")}),a.pipe(Y(),xe(ye),te(c)).subscribe(([l,f])=>{f.classList.toggle("md-tooltip2--active",l)}),z([a.pipe(g(l=>l)),c]).subscribe(([l,f])=>{f.role==="dialog"?(e.setAttribute("aria-controls",i),e.setAttribute("aria-haspopup","dialog")):e.setAttribute("aria-describedby",i)}),a.pipe(g(l=>!l)).subscribe(()=>{e.removeAttribute("aria-controls"),e.removeAttribute("aria-describedby"),e.removeAttribute("aria-haspopup")}),Ba(e,r).pipe(O(l=>s.next(l)),A(()=>s.complete()),m(l=>P({ref:e},l)))})}function Xe(e,{viewport$:t},r=document.body){return Vt(e,{content$:new F(o=>{let n=e.title,i=Cn(n);return o.next(i),e.removeAttribute("title"),r.append(i),()=>{i.remove(),e.setAttribute("title",n)}}),viewport$:t},0)}function Ga(e,t){let r=H(()=>z([mn(e),Ge(t)])).pipe(m(([{x:o,y:n},i])=>{let{width:s,height:a}=de(e);return{x:o-i.x+s/2,y:n-i.y+a/2}}));return Ye(e).pipe(b(o=>r.pipe(m(n=>({active:o,offset:n})),Ee(+!o||1/0))))}function Dn(e,t,{target$:r}){let[o,n]=Array.from(e.children);return H(()=>{let i=new T,s=i.pipe(oe(),ae(!0));return i.subscribe({next({offset:a}){e.style.setProperty("--md-tooltip-x",`${a.x}px`),e.style.setProperty("--md-tooltip-y",`${a.y}px`)},complete(){e.style.removeProperty("--md-tooltip-x"),e.style.removeProperty("--md-tooltip-y")}}),mt(e).pipe(W(s)).subscribe(a=>{e.toggleAttribute("data-md-visible",a)}),L(i.pipe(g(({active:a})=>a)),i.pipe(Ae(250),g(({active:a})=>!a))).subscribe({next({active:a}){a?e.prepend(o):o.remove()},complete(){e.prepend(o)}}),i.pipe($e(16,ye)).subscribe(({active:a})=>{o.classList.toggle("md-tooltip--active",a)}),i.pipe(gt(125,ye),g(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:a})=>a)).subscribe({next(a){a?e.style.setProperty("--md-tooltip-0",`${-a}px`):e.style.removeProperty("--md-tooltip-0")},complete(){e.style.removeProperty("--md-tooltip-0")}}),h(n,"click").pipe(W(s),g(a=>!(a.metaKey||a.ctrlKey))).subscribe(a=>{a.stopPropagation(),a.preventDefault()}),h(n,"mousedown").pipe(W(s),te(i)).subscribe(([a,{active:c}])=>{var p;if(a.button!==0||a.metaKey||a.ctrlKey)a.preventDefault();else if(c){a.preventDefault();let l=e.parentElement.closest(".md-annotation");l instanceof HTMLElement?l.focus():(p=Ne())==null||p.blur()}}),r.pipe(W(s),g(a=>a===o),nt(125)).subscribe(()=>e.focus()),Ga(e,t).pipe(O(a=>i.next(a)),A(()=>i.complete()),m(a=>P({ref:e},a)))})}function Ja(e){let t=Te();if(e.tagName!=="CODE")return[e];let r=[".c",".c1",".cm"];if(t.annotate&&typeof t.annotate=="object"){let o=e.closest("[class|=language]");if(o)for(let n of Array.from(o.classList)){if(!n.startsWith("language-"))continue;let[,i]=n.split("-");i in t.annotate&&r.push(...t.annotate[i])}}return M(r.join(", "),e)}function Xa(e){let t=[];for(let r of Ja(e)){let o=[],n=document.createNodeIterator(r,NodeFilter.SHOW_TEXT);for(let i=n.nextNode();i;i=n.nextNode())o.push(i);for(let i of o){let s;for(;s=/(\(\d+\))(!)?/.exec(i.textContent);){let[,a,c]=s;if(typeof c=="undefined"){let p=i.splitText(s.index);i=p.splitText(a.length),t.push(p)}else{i.textContent=a,t.push(i);break}}}}return t}function Vn(e,t){t.append(...Array.from(e.childNodes))}function Tr(e,t,{target$:r,print$:o}){let n=t.closest("[id]"),i=n==null?void 0:n.id,s=new Map;for(let a of Xa(t)){let[,c]=a.textContent.match(/\((\d+)\)/);ue(`:scope > li:nth-child(${c})`,e)&&(s.set(c,kn(c,i)),a.replaceWith(s.get(c)))}return s.size===0?y:H(()=>{let a=new T,c=a.pipe(oe(),ae(!0)),p=[];for(let[l,f]of s)p.push([j(".md-typeset",f),j(`:scope > li:nth-child(${l})`,e)]);return o.pipe(W(c)).subscribe(l=>{e.hidden=!l,e.classList.toggle("md-annotation-list",l);for(let[f,u]of p)l?Vn(f,u):Vn(u,f)}),L(...[...s].map(([,l])=>Dn(l,t,{target$:r}))).pipe(A(()=>a.complete()),le())})}function Nn(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return Nn(t)}}function zn(e,t){return H(()=>{let r=Nn(e);return typeof r!="undefined"?Tr(r,e,t):y})}var Kn=$t(ao());var Za=0,qn=L(h(window,"keydown").pipe(m(()=>!0)),L(h(window,"keyup"),h(window,"contextmenu")).pipe(m(()=>!1))).pipe(Q(!1),Z(1));function Qn(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return Qn(t)}}function es(e){return Le(e).pipe(m(({width:t})=>({scrollable:At(e).width>t})),ne("scrollable"))}function Yn(e,t){let{matches:r}=matchMedia("(hover)"),o=H(()=>{let n=new T,i=n.pipe(Yr(1));n.subscribe(({scrollable:d})=>{d&&r?e.setAttribute("tabindex","0"):e.removeAttribute("tabindex")});let s=[],a=e.closest("pre"),c=a.closest("[id]"),p=c?c.id:Za++;a.id=`__code_${p}`;let l=[],f=e.closest(".highlight");if(f instanceof HTMLElement){let d=Qn(f);if(typeof d!="undefined"&&(f.classList.contains("annotate")||V("content.code.annotate"))){let v=Tr(d,e,t);l.push(Le(f).pipe(W(i),m(({width:S,height:X})=>S&&X),Y(),b(S=>S?v:y)))}}let u=M(":scope > span[id]",e);if(u.length&&(e.classList.add("md-code__content"),e.closest(".select")||V("content.code.select")&&!e.closest(".no-select"))){let d=+u[0].id.split("-").pop(),v=$n();s.push(v),V("content.tooltips")&&l.push(Xe(v,{viewport$}));let S=h(v,"click").pipe(Ut(R=>!R,!1),O(()=>v.blur()),le());S.subscribe(R=>{v.classList.toggle("md-code__button--active",R)});let X=fe(u).pipe(J(R=>it(R).pipe(m(se=>[R,se]))));S.pipe(b(R=>R?X:y)).subscribe(([R,se])=>{let ce=ue(".hll.select",R);if(ce&&!se)ce.replaceWith(...Array.from(ce.childNodes));else if(!ce&&se){let he=document.createElement("span");he.className="hll select",he.append(...Array.from(R.childNodes).slice(1)),R.append(he)}});let re=fe(u).pipe(J(R=>h(R,"mousedown").pipe(O(se=>se.preventDefault()),m(()=>R)))),ee=S.pipe(b(R=>R?re:y),te(qn),m(([R,se])=>{var he;let ce=u.indexOf(R)+d;if(se===!1)return[ce,ce];{let Se=M(".hll",e).map(Ue=>u.indexOf(Ue.parentElement)+d);return(he=window.getSelection())==null||he.removeAllRanges(),[Math.min(ce,...Se),Math.max(ce,...Se)]}})),k=Zr(y).pipe(g(R=>R.startsWith(`__codelineno-${p}-`)));k.subscribe(R=>{let[,,se]=R.split("-"),ce=se.split(":").map(Se=>+Se-d+1);ce.length===1&&ce.push(ce[0]);for(let Se of M(".hll:not(.select)",e))Se.replaceWith(...Array.from(Se.childNodes));let he=u.slice(ce[0]-1,ce[1]);for(let Se of he){let Ue=document.createElement("span");Ue.className="hll",Ue.append(...Array.from(Se.childNodes).slice(1)),Se.append(Ue)}}),k.pipe(Ee(1),xe(pe)).subscribe(R=>{if(R.includes(":")){let se=document.getElementById(R.split(":")[0]);se&&setTimeout(()=>{let ce=se,he=-64;for(;ce!==document.body;)he+=ce.offsetTop,ce=ce.offsetParent;window.scrollTo({top:he})},1)}});let je=fe(M('a[href^="#__codelineno"]',f)).pipe(J(R=>h(R,"click").pipe(O(se=>se.preventDefault()),m(()=>R)))).pipe(W(i),te(qn),m(([R,se])=>{let he=+j(`[id="${R.hash.slice(1)}"]`).parentElement.id.split("-").pop();if(se===!1)return[he,he];{let Se=M(".hll",e).map(Ue=>+Ue.parentElement.id.split("-").pop());return[Math.min(he,...Se),Math.max(he,...Se)]}}));L(ee,je).subscribe(R=>{let se=`#__codelineno-${p}-`;R[0]===R[1]?se+=R[0]:se+=`${R[0]}:${R[1]}`,history.replaceState({},"",se),window.dispatchEvent(new HashChangeEvent("hashchange",{newURL:window.location.origin+window.location.pathname+se,oldURL:window.location.href}))})}if(Kn.default.isSupported()&&(e.closest(".copy")||V("content.code.copy")&&!e.closest(".no-copy"))){let d=Hn(a.id);s.push(d),V("content.tooltips")&&l.push(Xe(d,{viewport$}))}if(s.length){let d=Pn();d.append(...s),a.insertBefore(d,e)}return es(e).pipe(O(d=>n.next(d)),A(()=>n.complete()),m(d=>P({ref:e},d)),Ve(L(...l).pipe(W(i))))});return V("content.lazy")?mt(e).pipe(g(n=>n),Ee(1),b(()=>o)):o}function ts(e,{target$:t,print$:r}){let o=!0;return L(t.pipe(m(n=>n.closest("details:not([open])")),g(n=>e===n),m(()=>({action:"open",reveal:!0}))),r.pipe(g(n=>n||!o),O(()=>o=e.open),m(n=>({action:n?"open":"close"}))))}function Bn(e,t){return H(()=>{let r=new T;return r.subscribe(({action:o,reveal:n})=>{e.toggleAttribute("open",o==="open"),n&&e.scrollIntoView()}),ts(e,t).pipe(O(o=>r.next(o)),A(()=>r.complete()),m(o=>P({ref:e},o)))})}var Gn=0;function rs(e){let t=document.createElement("h3");t.innerHTML=e.innerHTML;let r=[t],o=e.nextElementSibling;for(;o&&!(o instanceof HTMLHeadingElement);)r.push(o),o=o.nextElementSibling;return r}function os(e,t){for(let r of M("[href], [src]",e))for(let o of["href","src"]){let n=r.getAttribute(o);if(n&&!/^(?:[a-z]+:)?\/\//i.test(n)){r[o]=new URL(r.getAttribute(o),t).toString();break}}for(let r of M("[name^=__], [for]",e))for(let o of["id","for","name"]){let n=r.getAttribute(o);n&&r.setAttribute(o,`${n}$preview_${Gn}`)}return Gn++,$(e)}function Jn(e,t){let{sitemap$:r}=t;if(!(e instanceof HTMLAnchorElement))return y;if(!(V("navigation.instant.preview")||e.hasAttribute("data-preview")))return y;e.removeAttribute("title");let o=z([Ye(e),it(e)]).pipe(m(([i,s])=>i||s),Y(),g(i=>i));return rt([r,o]).pipe(b(([i])=>{let s=new URL(e.href);return s.search=s.hash="",i.has(`${s}`)?$(s):y}),b(i=>xr(i).pipe(b(s=>os(s,i)))),b(i=>{let s=e.hash?`article [id="${e.hash.slice(1)}"]`:"article h1",a=ue(s,i);return typeof a=="undefined"?y:$(rs(a))})).pipe(b(i=>{let s=new F(a=>{let c=wr(...i);return a.next(c),document.body.append(c),()=>c.remove()});return Vt(e,P({content$:s},t))}))}var Xn=".node circle,.node ellipse,.node path,.node polygon,.node rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}marker{fill:var(--md-mermaid-edge-color)!important}.edgeLabel .label rect{fill:#0000}.flowchartTitleText{fill:var(--md-mermaid-label-fg-color)}.label{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.label foreignObject{line-height:normal;overflow:visible}.label div .edgeLabel{color:var(--md-mermaid-label-fg-color)}.edgeLabel,.edgeLabel p,.label div .edgeLabel{background-color:var(--md-mermaid-label-bg-color)}.edgeLabel,.edgeLabel p{fill:var(--md-mermaid-label-bg-color);color:var(--md-mermaid-edge-color)}.edgePath .path,.flowchart-link{stroke:var(--md-mermaid-edge-color)}.edgePath .arrowheadPath{fill:var(--md-mermaid-edge-color);stroke:none}.cluster rect{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}.cluster span{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}g #flowchart-circleEnd,g #flowchart-circleStart,g #flowchart-crossEnd,g #flowchart-crossStart,g #flowchart-pointEnd,g #flowchart-pointStart{stroke:none}.classDiagramTitleText{fill:var(--md-mermaid-label-fg-color)}g.classGroup line,g.classGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.classGroup text{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.classLabel .box{fill:var(--md-mermaid-label-bg-color);background-color:var(--md-mermaid-label-bg-color);opacity:1}.classLabel .label{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node .divider{stroke:var(--md-mermaid-node-fg-color)}.relation{stroke:var(--md-mermaid-edge-color)}.cardinality{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.cardinality text{fill:inherit!important}defs marker.marker.composition.class path,defs marker.marker.dependency.class path,defs marker.marker.extension.class path{fill:var(--md-mermaid-edge-color)!important;stroke:var(--md-mermaid-edge-color)!important}defs marker.marker.aggregation.class path{fill:var(--md-mermaid-label-bg-color)!important;stroke:var(--md-mermaid-edge-color)!important}.statediagramTitleText{fill:var(--md-mermaid-label-fg-color)}g.stateGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.stateGroup .state-title{fill:var(--md-mermaid-label-fg-color)!important;font-family:var(--md-mermaid-font-family)}g.stateGroup .composit{fill:var(--md-mermaid-label-bg-color)}.nodeLabel,.nodeLabel p{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}a .nodeLabel{text-decoration:underline}.node circle.state-end,.node circle.state-start,.start-state{fill:var(--md-mermaid-edge-color);stroke:none}.end-state-inner,.end-state-outer{fill:var(--md-mermaid-edge-color)}.end-state-inner,.node circle.state-end{stroke:var(--md-mermaid-label-bg-color)}.transition{stroke:var(--md-mermaid-edge-color)}[id^=state-fork] rect,[id^=state-join] rect{fill:var(--md-mermaid-edge-color)!important;stroke:none!important}.statediagram-cluster.statediagram-cluster .inner{fill:var(--md-default-bg-color)}.statediagram-cluster rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.statediagram-state rect.divider{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}defs #statediagram-barbEnd{stroke:var(--md-mermaid-edge-color)}[id^=entity] path,[id^=entity] rect{fill:var(--md-default-bg-color)}.relationshipLine{stroke:var(--md-mermaid-edge-color)}defs .marker.oneOrMore.er *,defs .marker.onlyOne.er *,defs .marker.zeroOrMore.er *,defs .marker.zeroOrOne.er *{stroke:var(--md-mermaid-edge-color)!important}text:not([class]):last-child{fill:var(--md-mermaid-label-fg-color)}.actor{fill:var(--md-mermaid-sequence-actor-bg-color);stroke:var(--md-mermaid-sequence-actor-border-color)}text.actor>tspan{fill:var(--md-mermaid-sequence-actor-fg-color);font-family:var(--md-mermaid-font-family)}line{stroke:var(--md-mermaid-sequence-actor-line-color)}.actor-man circle,.actor-man line{fill:var(--md-mermaid-sequence-actorman-bg-color);stroke:var(--md-mermaid-sequence-actorman-line-color)}.messageLine0,.messageLine1{stroke:var(--md-mermaid-sequence-message-line-color)}.note{fill:var(--md-mermaid-sequence-note-bg-color);stroke:var(--md-mermaid-sequence-note-border-color)}.loopText,.loopText>tspan,.messageText,.noteText>tspan{stroke:none;font-family:var(--md-mermaid-font-family)!important}.messageText{fill:var(--md-mermaid-sequence-message-fg-color)}.loopText,.loopText>tspan{fill:var(--md-mermaid-sequence-loop-fg-color)}.noteText>tspan{fill:var(--md-mermaid-sequence-note-fg-color)}#arrowhead path{fill:var(--md-mermaid-sequence-message-line-color);stroke:none}.loopLine{fill:var(--md-mermaid-sequence-loop-bg-color);stroke:var(--md-mermaid-sequence-loop-border-color)}.labelBox{fill:var(--md-mermaid-sequence-label-bg-color);stroke:none}.labelText,.labelText>span{fill:var(--md-mermaid-sequence-label-fg-color);font-family:var(--md-mermaid-font-family)}.sequenceNumber{fill:var(--md-mermaid-sequence-number-fg-color)}rect.rect{fill:var(--md-mermaid-sequence-box-bg-color);stroke:none}rect.rect+text.text{fill:var(--md-mermaid-sequence-box-fg-color)}defs #sequencenumber{fill:var(--md-mermaid-sequence-number-bg-color)!important}";var so,is=0;function as(){return typeof mermaid=="undefined"||mermaid instanceof Element?_t("https://unpkg.com/mermaid@11/dist/mermaid.min.js"):$(void 0)}function Zn(e){return e.classList.remove("mermaid"),so||(so=as().pipe(O(()=>mermaid.initialize({startOnLoad:!1,themeCSS:Xn,sequence:{actorFontSize:"16px",messageFontSize:"16px",noteFontSize:"16px"}})),m(()=>{}),Z(1))),so.subscribe(()=>go(null,null,function*(){e.classList.add("mermaid");let t=`__mermaid_${is++}`,r=x("div",{class:"mermaid"}),o=e.textContent,{svg:n,fn:i}=yield mermaid.render(t,o),s=r.attachShadow({mode:"closed"});s.innerHTML=n,e.replaceWith(r),i==null||i(s)})),so.pipe(m(()=>({ref:e})))}var ei=x("table");function ti(e){return e.replaceWith(ei),ei.replaceWith(Un(e)),$({ref:e})}function ss(e){let t=e.find(r=>r.checked)||e[0];return L(...e.map(r=>h(r,"change").pipe(m(()=>j(`label[for="${r.id}"]`))))).pipe(Q(j(`label[for="${t.id}"]`)),m(r=>({active:r})))}function ri(e,{viewport$:t,target$:r}){let o=j(".tabbed-labels",e),n=M(":scope > input",e),i=no("prev");e.append(i);let s=no("next");return e.append(s),H(()=>{let a=new T,c=a.pipe(oe(),ae(!0));z([a,Le(e),mt(e)]).pipe(W(c),$e(1,ye)).subscribe({next([{active:p},l]){let f=Be(p),{width:u}=de(p);e.style.setProperty("--md-indicator-x",`${f.x}px`),e.style.setProperty("--md-indicator-width",`${u}px`);let d=gr(o);(f.xd.x+l.width)&&o.scrollTo({left:Math.max(0,f.x-16),behavior:"smooth"})},complete(){e.style.removeProperty("--md-indicator-x"),e.style.removeProperty("--md-indicator-width")}}),z([Ge(o),Le(o)]).pipe(W(c)).subscribe(([p,l])=>{let f=At(o);i.hidden=p.x<16,s.hidden=p.x>f.width-l.width-16}),L(h(i,"click").pipe(m(()=>-1)),h(s,"click").pipe(m(()=>1))).pipe(W(c)).subscribe(p=>{let{width:l}=de(o);o.scrollBy({left:l*p,behavior:"smooth"})}),r.pipe(W(c),g(p=>n.includes(p))).subscribe(p=>p.click()),o.classList.add("tabbed-labels--linked");for(let p of n){let l=j(`label[for="${p.id}"]`);l.replaceChildren(x("a",{href:`#${l.htmlFor}`,tabIndex:-1},...Array.from(l.childNodes))),h(l.firstElementChild,"click").pipe(W(c),g(f=>!(f.metaKey||f.ctrlKey)),O(f=>{f.preventDefault(),f.stopPropagation()})).subscribe(()=>{history.replaceState({},"",`#${l.htmlFor}`),l.click()})}return V("content.tabs.link")&&a.pipe(Ie(1),te(t)).subscribe(([{active:p},{offset:l}])=>{let f=p.innerText.trim();if(p.hasAttribute("data-md-switching"))p.removeAttribute("data-md-switching");else{let u=e.offsetTop-l.y;for(let v of M("[data-tabs]"))for(let S of M(":scope > input",v)){let X=j(`label[for="${S.id}"]`);if(X!==p&&X.innerText.trim()===f){X.setAttribute("data-md-switching",""),S.click();break}}window.scrollTo({top:e.offsetTop-u});let d=__md_get("__tabs")||[];__md_set("__tabs",[...new Set([f,...d])])}}),a.pipe(W(c)).subscribe(()=>{for(let p of M("audio, video",e))p.offsetWidth&&p.autoplay?p.play().catch(()=>{}):p.pause()}),ss(n).pipe(O(p=>a.next(p)),A(()=>a.complete()),m(p=>P({ref:e},p)))}).pipe(et(pe))}function oi(e,t){let{viewport$:r,target$:o,print$:n}=t;return L(...M(".annotate:not(.highlight)",e).map(i=>zn(i,{target$:o,print$:n})),...M("pre:not(.mermaid) > code",e).map(i=>Yn(i,{target$:o,print$:n})),...M("a",e).map(i=>Jn(i,t)),...M("pre.mermaid",e).map(i=>Zn(i)),...M("table:not([class])",e).map(i=>ti(i)),...M("details",e).map(i=>Bn(i,{target$:o,print$:n})),...M("[data-tabs]",e).map(i=>ri(i,{viewport$:r,target$:o})),...M("[title]:not([data-preview])",e).filter(()=>V("content.tooltips")).map(i=>Xe(i,{viewport$:r})),...M(".footnote-ref",e).filter(()=>V("content.footnote.tooltips")).map(i=>Vt(i,{content$:new F(s=>{let a=new URL(i.href).hash.slice(1),c=Array.from(document.getElementById(a).cloneNode(!0).children),p=wr(...c);return s.next(p),document.body.append(p),()=>p.remove()}),viewport$:r})))}function cs(e,{alert$:t}){return t.pipe(b(r=>L($(!0),$(!1).pipe(nt(2e3))).pipe(m(o=>({message:r,active:o})))))}function ni(e,t){let r=j(".md-typeset",e);return H(()=>{let o=new T;return o.subscribe(({message:n,active:i})=>{e.classList.toggle("md-dialog--active",i),r.textContent=n}),cs(e,t).pipe(O(n=>o.next(n)),A(()=>o.complete()),m(n=>P({ref:e},n)))})}var ps=0;function ls(e,t){document.body.append(e);let{width:r}=de(e);e.style.setProperty("--md-tooltip-width",`${r}px`),e.remove();let o=vr(t),n=typeof o!="undefined"?Ge(o):$({x:0,y:0}),i=L(Ye(t),it(t)).pipe(Y());return z([i,n]).pipe(m(([s,a])=>{let{x:c,y:p}=Be(t),l=de(t),f=t.closest("table");return f&&t.parentElement&&(c+=f.offsetLeft+t.parentElement.offsetLeft,p+=f.offsetTop+t.parentElement.offsetTop),{active:s,offset:{x:c-a.x+l.width/2-r/2,y:p-a.y+l.height+8}}}))}function ii(e){let t=e.title;if(!t.length)return y;let r=`__tooltip_${ps++}`,o=Dt(r,"inline"),n=j(".md-typeset",o);return n.innerHTML=t,H(()=>{let i=new T;return i.subscribe({next({offset:s}){o.style.setProperty("--md-tooltip-x",`${s.x}px`),o.style.setProperty("--md-tooltip-y",`${s.y}px`)},complete(){o.style.removeProperty("--md-tooltip-x"),o.style.removeProperty("--md-tooltip-y")}}),L(i.pipe(g(({active:s})=>s)),i.pipe(Ae(250),g(({active:s})=>!s))).subscribe({next({active:s}){s?(e.insertAdjacentElement("afterend",o),e.setAttribute("aria-describedby",r),e.removeAttribute("title")):(o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t))},complete(){o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t)}}),i.pipe($e(16,ye)).subscribe(({active:s})=>{o.classList.toggle("md-tooltip--active",s)}),i.pipe(gt(125,ye),g(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:s})=>s)).subscribe({next(s){s?o.style.setProperty("--md-tooltip-0",`${-s}px`):o.style.removeProperty("--md-tooltip-0")},complete(){o.style.removeProperty("--md-tooltip-0")}}),ls(o,e).pipe(O(s=>i.next(s)),A(()=>i.complete()),m(s=>P({ref:e},s)))}).pipe(et(pe))}function ms({viewport$:e}){if(!V("header.autohide"))return $(!1);let t=e.pipe(m(({offset:{y:n}})=>n),ot(2,1),m(([n,i])=>[nMath.abs(i-n.y)>100),m(([,[n]])=>n),Y()),o=Je("search");return z([e,o]).pipe(m(([{offset:n},i])=>n.y>400&&!i),Y(),b(n=>n?r:$(!1)),Q(!1))}function ai(e,t){return H(()=>z([Le(e),ms(t)])).pipe(m(([{height:r},o])=>({height:r,hidden:o})),Y((r,o)=>r.height===o.height&&r.hidden===o.hidden),Z(1))}function si(e,{header$:t,main$:r}){return H(()=>{let o=new T,n=o.pipe(oe(),ae(!0));o.pipe(ne("active"),Pe(t)).subscribe(([{active:s},{hidden:a}])=>{e.classList.toggle("md-header--shadow",s&&!a),e.hidden=a});let i=fe(M("[title]",e)).pipe(g(()=>V("content.tooltips")),J(s=>ii(s)));return r.subscribe(o),t.pipe(W(n),m(s=>P({ref:e},s)),Ve(i.pipe(W(n))))})}function fs(e,{viewport$:t,header$:r}){return Er(e,{viewport$:t,header$:r}).pipe(m(({offset:{y:o}})=>{let{height:n}=de(e);return{active:n>0&&o>=n}}),ne("active"))}function ci(e,t){return H(()=>{let r=new T;r.subscribe({next({active:n}){e.classList.toggle("md-header__title--active",n)},complete(){e.classList.remove("md-header__title--active")}});let o=ue(".md-content h1");return typeof o=="undefined"?y:fs(o,t).pipe(O(n=>r.next(n)),A(()=>r.complete()),m(n=>P({ref:e},n)))})}function pi(e,{viewport$:t,header$:r}){let o=r.pipe(m(({height:i})=>i),Y()),n=o.pipe(b(()=>Le(e).pipe(m(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),ne("bottom"))));return z([o,n,t]).pipe(m(([i,{top:s,bottom:a},{offset:{y:c},size:{height:p}}])=>(p=Math.max(0,p-Math.max(0,s-c,i)-Math.max(0,p+c-a)),{offset:s-i,height:p,active:s-i<=c})),Y((i,s)=>i.offset===s.offset&&i.height===s.height&&i.active===s.active))}function us(e){let t=__md_get("__palette")||{index:e.findIndex(o=>matchMedia(o.getAttribute("data-md-color-media")).matches)},r=Math.max(0,Math.min(t.index,e.length-1));return $(...e).pipe(J(o=>h(o,"change").pipe(m(()=>o))),Q(e[r]),m(o=>({index:e.indexOf(o),color:{media:o.getAttribute("data-md-color-media"),scheme:o.getAttribute("data-md-color-scheme"),primary:o.getAttribute("data-md-color-primary"),accent:o.getAttribute("data-md-color-accent")}})),Z(1))}function li(e){let t=M("input",e),r=x("meta",{name:"theme-color"});document.head.appendChild(r);let o=x("meta",{name:"color-scheme"});document.head.appendChild(o);let n=Wt("(prefers-color-scheme: light)");return H(()=>{let i=new T;return i.subscribe(s=>{if(document.body.setAttribute("data-md-color-switching",""),s.color.media==="(prefers-color-scheme)"){let a=matchMedia("(prefers-color-scheme: light)"),c=document.querySelector(a.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");s.color.scheme=c.getAttribute("data-md-color-scheme"),s.color.primary=c.getAttribute("data-md-color-primary"),s.color.accent=c.getAttribute("data-md-color-accent")}for(let[a,c]of Object.entries(s.color))document.body.setAttribute(`data-md-color-${a}`,c);for(let a=0;as.key==="Enter"),te(i,(s,a)=>a)).subscribe(({index:s})=>{s=(s+1)%t.length,t[s].click(),t[s].focus()}),i.pipe(m(()=>{let s=Ce("header"),a=window.getComputedStyle(s);return o.content=a.colorScheme,a.backgroundColor.match(/\d+/g).map(c=>(+c).toString(16).padStart(2,"0")).join("")})).subscribe(s=>r.content=`#${s}`),i.pipe(xe(pe)).subscribe(()=>{document.body.removeAttribute("data-md-color-switching")}),us(t).pipe(W(n.pipe(Ie(1))),vt(),O(s=>i.next(s)),A(()=>i.complete()),m(s=>P({ref:e},s)))})}function mi(e,{progress$:t}){return H(()=>{let r=new T;return r.subscribe(({value:o})=>{e.style.setProperty("--md-progress-value",`${o}`)}),t.pipe(O(o=>r.next({value:o})),A(()=>r.complete()),m(o=>({ref:e,value:o})))})}function fi(e,t){return e.protocol=t.protocol,e.hostname=t.hostname,e}function ds(e,t){let r=new Map;for(let o of M("url",e)){let n=j("loc",o),i=[fi(new URL(n.textContent),t)];r.set(`${i[0]}`,i);for(let s of M("[rel=alternate]",o)){let a=s.getAttribute("href");a!=null&&i.push(fi(new URL(a),t))}}return r}function kt(e){return En(new URL("sitemap.xml",e)).pipe(m(t=>ds(t,new URL(e))),ve(()=>$(new Map)),le())}function ui({document$:e}){let t=new Map;e.pipe(b(()=>M("link[rel=alternate]")),m(r=>new URL(r.href)),g(r=>!t.has(r.toString())),J(r=>kt(r).pipe(m(o=>[r,o]),ve(()=>y)))).subscribe(([r,o])=>{t.set(r.toString().replace(/\/$/,""),o)}),h(document.body,"click").pipe(g(r=>!r.metaKey&&!r.ctrlKey),b(r=>{if(r.target instanceof Element){let o=r.target.closest("a");if(o&&!o.target){let n=[...t].find(([f])=>o.href.startsWith(`${f}/`));if(typeof n=="undefined")return y;let[i,s]=n,a=we();if(a.href.startsWith(i))return y;let c=Te(),p=a.href.replace(c.base,"");p=`${i}/${p}`;let l=s.has(p.split("#")[0])?new URL(p,c.base):new URL(i);return r.preventDefault(),$(l)}}return y})).subscribe(r=>st(r,!0))}var co=$t(ao());function hs(e){e.setAttribute("data-md-copying","");let t=e.closest("[data-copy]"),r=t?t.getAttribute("data-copy"):e.innerText;return e.removeAttribute("data-md-copying"),r.trimEnd()}function di({alert$:e}){co.default.isSupported()&&new F(t=>{new co.default("[data-clipboard-target], [data-clipboard-text]",{text:r=>r.getAttribute("data-clipboard-text")||hs(j(r.getAttribute("data-clipboard-target")))}).on("success",r=>t.next(r))}).pipe(O(t=>{t.trigger.focus()}),m(()=>Me("clipboard.copied"))).subscribe(e)}function hi(e,t){if(!(e.target instanceof Element))return y;let r=e.target.closest("a");if(r===null)return y;if(r.target||e.metaKey||e.ctrlKey)return y;let o=new URL(r.href);return o.search=o.hash="",t.has(`${o}`)?(e.preventDefault(),$(r)):y}function bi(e){let t=new Map;for(let r of M(":scope > *",e.head))t.set(r.outerHTML,r);return t}function vi(e){for(let t of M("[href], [src]",e))for(let r of["href","src"]){let o=t.getAttribute(r);if(o&&!/^(?:[a-z]+:)?\/\//i.test(o)){t[r]=t[r];break}}return $(e)}function bs(e){for(let o of["[data-md-component=announce]","[data-md-component=container]","[data-md-component=header-topic]","[data-md-component=outdated]","[data-md-component=logo]","[data-md-component=skip]",...V("navigation.tabs.sticky")?["[data-md-component=tabs]"]:[]]){let n=ue(o),i=ue(o,e);typeof n!="undefined"&&typeof i!="undefined"&&n.replaceWith(i)}let t=bi(document);for(let[o,n]of bi(e))t.has(o)?t.delete(o):document.head.appendChild(n);for(let o of t.values()){let n=o.getAttribute("name");n!=="theme-color"&&n!=="color-scheme"&&o.remove()}let r=Ce("container");return Ke(M("script",r)).pipe(b(o=>{let n=e.createElement("script");if(o.src){for(let i of o.getAttributeNames())n.setAttribute(i,o.getAttribute(i));return o.replaceWith(n),new F(i=>{n.onload=()=>i.complete()})}else return n.textContent=o.textContent,o.replaceWith(n),y}),oe(),ae(document))}function gi({sitemap$:e,location$:t,viewport$:r,progress$:o}){if(location.protocol==="file:")return y;$(document).subscribe(vi);let n=h(document.body,"click").pipe(Pe(e),b(([a,c])=>hi(a,c)),m(({href:a})=>new URL(a)),le()),i=h(window,"popstate").pipe(m(we),le());n.pipe(te(r)).subscribe(([a,{offset:c}])=>{history.replaceState(c,""),history.pushState(null,"",a)}),L(n,i).subscribe(t);let s=t.pipe(ne("pathname"),b(a=>xr(a,{progress$:o}).pipe(ve(()=>(st(a,!0),y)))),b(vi),b(bs),le());return L(s.pipe(te(t,(a,c)=>c)),s.pipe(b(()=>t),ne("hash")),t.pipe(Y((a,c)=>a.pathname===c.pathname&&a.hash===c.hash),b(()=>n),O(()=>history.back()))).subscribe(a=>{var c,p;history.state!==null||!a.hash?window.scrollTo(0,(p=(c=history.state)==null?void 0:c.y)!=null?p:0):(history.scrollRestoration="auto",gn(a.hash),history.scrollRestoration="manual")}),t.subscribe(()=>{history.scrollRestoration="manual"}),h(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}),r.pipe(ne("offset"),Ae(100)).subscribe(({offset:a})=>{history.replaceState(a,"")}),V("navigation.instant.prefetch")&&L(h(document.body,"mousemove"),h(document.body,"focusin")).pipe(Pe(e),b(([a,c])=>hi(a,c)),Ae(25),Qr(({href:a})=>a),hr(a=>{let c=document.createElement("link");return c.rel="prefetch",c.href=a.toString(),document.head.appendChild(c),h(c,"load").pipe(m(()=>c),Ee(1))})).subscribe(a=>a.remove()),s}var yi=$t(ro());function xi(e){let t=e.separator.split("|").map(n=>n.replace(/(\(\?[!=<][^)]+\))/g,"").length===0?"\uFFFD":n).join("|"),r=new RegExp(t,"img"),o=(n,i,s)=>`${i}${s}`;return n=>{n=n.replace(/[\s*+\-:~^]+/g," ").replace(/&/g,"&").trim();let i=new RegExp(`(^|${e.separator}|)(${n.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&").replace(r,"|")})`,"img");return s=>(0,yi.default)(s).replace(i,o).replace(/<\/mark>(\s+)]*>/img,"$1")}}function zt(e){return e.type===1}function Sr(e){return e.type===3}function Ei(e,t){let r=Mn(e);return L($(location.protocol!=="file:"),Je("search")).pipe(Re(o=>o),b(()=>t)).subscribe(({config:o,docs:n})=>r.next({type:0,data:{config:o,docs:n,options:{suggest:V("search.suggest")}}})),r}function wi(e){var l;let{selectedVersionSitemap:t,selectedVersionBaseURL:r,currentLocation:o,currentBaseURL:n}=e,i=(l=po(n))==null?void 0:l.pathname;if(i===void 0)return;let s=ys(o.pathname,i);if(s===void 0)return;let a=Es(t.keys());if(!t.has(a))return;let c=po(s,a);if(!c||!t.has(c.href))return;let p=po(s,r);if(p)return p.hash=o.hash,p.search=o.search,p}function po(e,t){try{return new URL(e,t)}catch(r){return}}function ys(e,t){if(e.startsWith(t))return e.slice(t.length)}function xs(e,t){let r=Math.min(e.length,t.length),o;for(o=0;oy)),o=r.pipe(m(n=>{let[,i]=t.base.match(/([^/]+)\/?$/);return n.find(({version:s,aliases:a})=>s===i||a.includes(i))||n[0]}));r.pipe(m(n=>new Map(n.map(i=>[`${new URL(`../${i.version}/`,t.base)}`,i]))),b(n=>h(document.body,"click").pipe(g(i=>!i.metaKey&&!i.ctrlKey),te(o),b(([i,s])=>{if(i.target instanceof Element){let a=i.target.closest("a");if(a&&!a.target&&n.has(a.href)){let c=a.href;return!i.target.closest(".md-version")&&n.get(c)===s?y:(i.preventDefault(),$(new URL(c)))}}return y}),b(i=>kt(i).pipe(m(s=>{var a;return(a=wi({selectedVersionSitemap:s,selectedVersionBaseURL:i,currentLocation:we(),currentBaseURL:t.base}))!=null?a:i})))))).subscribe(n=>st(n,!0)),z([r,o]).subscribe(([n,i])=>{j(".md-header__topic").appendChild(Wn(n,i))}),e.pipe(b(()=>o)).subscribe(n=>{var a;let i=new URL(t.base),s=__md_get("__outdated",sessionStorage,i);if(s===null){s=!0;let c=((a=t.version)==null?void 0:a.default)||"latest";Array.isArray(c)||(c=[c]);e:for(let p of c)for(let l of n.aliases.concat(n.version))if(new RegExp(p,"i").test(l)){s=!1;break e}__md_set("__outdated",s,sessionStorage,i)}if(s)for(let c of me("outdated"))c.hidden=!1})}function ws(e,{worker$:t}){let{searchParams:r}=we();r.has("q")&&(at("search",!0),e.value=r.get("q"),e.focus(),Je("search").pipe(Re(i=>!i)).subscribe(()=>{let i=we();i.searchParams.delete("q"),history.replaceState({},"",`${i}`)}));let o=Ye(e),n=L(t.pipe(Re(zt)),h(e,"keyup"),o).pipe(m(()=>e.value),Y());return z([n,o]).pipe(m(([i,s])=>({value:i,focus:s})),Z(1))}function Si(e,{worker$:t}){let r=new T,o=r.pipe(oe(),ae(!0));z([t.pipe(Re(zt)),r],(i,s)=>s).pipe(ne("value")).subscribe(({value:i})=>t.next({type:2,data:i})),r.pipe(ne("focus")).subscribe(({focus:i})=>{i&&at("search",i)}),h(e.form,"reset").pipe(W(o)).subscribe(()=>e.focus());let n=j("header [for=__search]");return h(n,"click").subscribe(()=>e.focus()),ws(e,{worker$:t}).pipe(O(i=>r.next(i)),A(()=>r.complete()),m(i=>P({ref:e},i)),Z(1))}function Oi(e,{worker$:t,query$:r}){let o=new T,n=un(e.parentElement).pipe(g(Boolean)),i=e.parentElement,s=j(":scope > :first-child",e),a=j(":scope > :last-child",e);Je("search").subscribe(l=>{a.setAttribute("role",l?"list":"presentation"),a.hidden=!l}),o.pipe(te(r),Gr(t.pipe(Re(zt)))).subscribe(([{items:l},{value:f}])=>{switch(l.length){case 0:s.textContent=f.length?Me("search.result.none"):Me("search.result.placeholder");break;case 1:s.textContent=Me("search.result.one");break;default:let u=br(l.length);s.textContent=Me("search.result.other",u)}});let c=o.pipe(O(()=>a.innerHTML=""),b(({items:l})=>L($(...l.slice(0,10)),$(...l.slice(10)).pipe(ot(4),Xr(n),b(([f])=>f)))),m(Fn),le());return c.subscribe(l=>a.appendChild(l)),c.pipe(J(l=>{let f=ue("details",l);return typeof f=="undefined"?y:h(f,"toggle").pipe(W(o),m(()=>f))})).subscribe(l=>{l.open===!1&&l.offsetTop<=i.scrollTop&&i.scrollTo({top:l.offsetTop})}),t.pipe(g(Sr),m(({data:l})=>l)).pipe(O(l=>o.next(l)),A(()=>o.complete()),m(l=>P({ref:e},l)))}function Ts(e,{query$:t}){return t.pipe(m(({value:r})=>{let o=we();return o.hash="",r=r.replace(/\s+/g,"+").replace(/&/g,"%26").replace(/=/g,"%3D"),o.search=`q=${r}`,{url:o}}))}function Li(e,t){let r=new T,o=r.pipe(oe(),ae(!0));return r.subscribe(({url:n})=>{e.setAttribute("data-clipboard-text",e.href),e.href=`${n}`}),h(e,"click").pipe(W(o)).subscribe(n=>n.preventDefault()),Ts(e,t).pipe(O(n=>r.next(n)),A(()=>r.complete()),m(n=>P({ref:e},n)))}function Mi(e,{worker$:t,keyboard$:r}){let o=new T,n=Ce("search-query"),i=L(h(n,"keydown"),h(n,"focus")).pipe(xe(pe),m(()=>n.value),Y());return o.pipe(Pe(i),m(([{suggest:a},c])=>{let p=c.split(/([\s-]+)/);if(a!=null&&a.length&&p[p.length-1]){let l=a[a.length-1];l.startsWith(p[p.length-1])&&(p[p.length-1]=l)}else p.length=0;return p})).subscribe(a=>e.textContent=a.join("")),r.pipe(g(({mode:a})=>a==="search")).subscribe(a=>{a.type==="ArrowRight"&&e.innerText.length&&n.selectionStart===n.value.length&&(n.value=e.innerText)}),t.pipe(g(Sr),m(({data:a})=>a)).pipe(O(a=>o.next(a)),A(()=>o.complete()),m(()=>({ref:e})))}function _i(e,{index$:t,keyboard$:r}){let o=Te();try{let n=Ei(o.search,t),i=Ce("search-query",e),s=Ce("search-result",e);h(e,"click").pipe(g(({target:c})=>c instanceof Element&&!!c.closest("a"))).subscribe(()=>at("search",!1)),r.pipe(g(({mode:c})=>c==="search")).subscribe(c=>{let p=Ne();switch(c.type){case"Enter":if(p===i){let l=new Map;for(let f of M(":first-child [href]",s)){let u=f.firstElementChild;l.set(f,parseFloat(u.getAttribute("data-md-score")))}if(l.size){let[[f]]=[...l].sort(([,u],[,d])=>d-u);f.click()}c.claim()}break;case"Escape":case"Tab":at("search",!1),i.blur();break;case"ArrowUp":case"ArrowDown":if(typeof p=="undefined")i.focus();else{let l=[i,...M(":not(details) > [href], summary, details[open] [href]",s)],f=Math.max(0,(Math.max(0,l.indexOf(p))+l.length+(c.type==="ArrowUp"?-1:1))%l.length);l[f].focus()}c.claim();break;default:i!==Ne()&&i.focus()}}),r.pipe(g(({mode:c})=>c==="global")).subscribe(c=>{switch(c.type){case"f":case"s":case"/":i.focus(),i.select(),c.claim();break}});let a=Si(i,{worker$:n});return L(a,Oi(s,{worker$:n,query$:a})).pipe(Ve(...me("search-share",e).map(c=>Li(c,{query$:a})),...me("search-suggest",e).map(c=>Mi(c,{worker$:n,keyboard$:r}))))}catch(n){return e.hidden=!0,tt}}function Ai(e,{index$:t,location$:r}){return z([t,r.pipe(Q(we()),g(o=>!!o.searchParams.get("h")))]).pipe(m(([o,n])=>xi(o.config)(n.searchParams.get("h"))),m(o=>{var s;let n=new Map,i=document.createNodeIterator(e,NodeFilter.SHOW_TEXT);for(let a=i.nextNode();a;a=i.nextNode())if((s=a.parentElement)!=null&&s.offsetHeight){let c=a.textContent,p=o(c);p.length>c.length&&n.set(a,p)}for(let[a,c]of n){let{childNodes:p}=x("span",null,c);a.replaceWith(...Array.from(p))}return{ref:e,nodes:n}}))}function Ss(e,{viewport$:t,main$:r}){let o=e.closest(".md-grid"),n=o.offsetTop-o.parentElement.offsetTop;return z([r,t]).pipe(m(([{offset:i,height:s},{offset:{y:a}}])=>(s=s+Math.min(n,Math.max(0,a-i))-n,{height:s,locked:a>=i+n})),Y((i,s)=>i.height===s.height&&i.locked===s.locked))}function lo(e,o){var n=o,{header$:t}=n,r=vo(n,["header$"]);let i=j(".md-sidebar__scrollwrap",e),{y:s}=Be(i);return H(()=>{let a=new T,c=a.pipe(oe(),ae(!0)),p=a.pipe($e(0,ye));return p.pipe(te(t)).subscribe({next([{height:l},{height:f}]){i.style.height=`${l-2*s}px`,e.style.top=`${f}px`},complete(){i.style.height="",e.style.top=""}}),p.pipe(Re()).subscribe(()=>{for(let l of M(".md-nav__link--active[href]",e)){if(!l.clientHeight)continue;let f=l.closest(".md-sidebar__scrollwrap");if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=de(f);f.scrollTo({top:u-d/2})}}}),fe(M("label[tabindex]",e)).pipe(J(l=>h(l,"click").pipe(xe(pe),m(()=>l),W(c)))).subscribe(l=>{let f=j(`[id="${l.htmlFor}"]`);j(`[aria-labelledby="${l.id}"]`).setAttribute("aria-expanded",`${f.checked}`)}),V("content.tooltips")&&fe(M("abbr[title]",e)).pipe(J(l=>Xe(l,{viewport$})),W(c)).subscribe(),Ss(e,r).pipe(O(l=>a.next(l)),A(()=>a.complete()),m(l=>P({ref:e},l)))})}function Ci(e,t){if(typeof t!="undefined"){let r=`https://api.github.com/repos/${e}/${t}`;return rt(ze(`${r}/releases/latest`).pipe(ve(()=>y),m(o=>({version:o.tag_name})),Qe({})),ze(r).pipe(ve(()=>y),m(o=>({stars:o.stargazers_count,forks:o.forks_count})),Qe({}))).pipe(m(([o,n])=>P(P({},o),n)))}else{let r=`https://api.github.com/users/${e}`;return ze(r).pipe(m(o=>({repositories:o.public_repos})),Qe({}))}}function ki(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return rt(ze(`${r}/releases/permalink/latest`).pipe(ve(()=>y),m(({tag_name:o})=>({version:o})),Qe({})),ze(r).pipe(ve(()=>y),m(({star_count:o,forks_count:n})=>({stars:o,forks:n})),Qe({}))).pipe(m(([o,n])=>P(P({},o),n)))}function Hi(e){let t=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);if(t){let[,r,o]=t;return Ci(r,o)}if(t=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i),t){let[,r,o]=t;return ki(r,o)}return y}var Os;function Ls(e){return Os||(Os=H(()=>{let t=__md_get("__source",sessionStorage);if(t)return $(t);if(me("consent").length){let o=__md_get("__consent");if(!(o&&o.github))return y}return Hi(e.href).pipe(O(o=>__md_set("__source",o,sessionStorage)))}).pipe(ve(()=>y),g(t=>Object.keys(t).length>0),m(t=>({facts:t})),Z(1)))}function $i(e){let t=j(":scope > :last-child",e);return H(()=>{let r=new T;return r.subscribe(({facts:o})=>{t.appendChild(jn(o)),t.classList.add("md-source__repository--active")}),Ls(e).pipe(O(o=>r.next(o)),A(()=>r.complete()),m(o=>P({ref:e},o)))})}function Ms(e,{viewport$:t,header$:r}){return Le(document.body).pipe(b(()=>Er(e,{header$:r,viewport$:t})),m(({offset:{y:o}})=>({hidden:o>=10})),ne("hidden"))}function Pi(e,t){return H(()=>{let r=new T;return r.subscribe({next({hidden:o}){e.hidden=o},complete(){e.hidden=!1}}),(V("navigation.tabs.sticky")?$({hidden:!1}):Ms(e,t)).pipe(O(o=>r.next(o)),A(()=>r.complete()),m(o=>P({ref:e},o)))})}function _s(e,{viewport$:t,header$:r}){let o=new Map,n=M(".md-nav__link",e);for(let a of n){let c=decodeURIComponent(a.hash.substring(1)),p=ue(`[id="${c}"]`);typeof p!="undefined"&&o.set(a,p)}let i=r.pipe(ne("height"),m(({height:a})=>{let c=Ce("main"),p=j(":scope > :first-child",c);return a+.8*(p.offsetTop-c.offsetTop)}),le());return Le(document.body).pipe(ne("height"),b(a=>H(()=>{let c=[];return $([...o].reduce((p,[l,f])=>{for(;c.length&&o.get(c[c.length-1]).tagName>=f.tagName;)c.pop();let u=f.offsetTop;for(;!u&&f.parentElement;)f=f.parentElement,u=f.offsetTop;let d=f.offsetParent;for(;d;d=d.offsetParent)u+=d.offsetTop;return p.set([...c=[...c,l]].reverse(),u)},new Map))}).pipe(m(c=>new Map([...c].sort(([,p],[,l])=>p-l))),Pe(i),b(([c,p])=>t.pipe(Ut(([l,f],{offset:{y:u},size:d})=>{let v=u+d.height>=Math.floor(a.height);for(;f.length;){let[,S]=f[0];if(S-p=u&&!v)f=[l.pop(),...f];else break}return[l,f]},[[],[...c]]),Y((l,f)=>l[0]===f[0]&&l[1]===f[1])))))).pipe(m(([a,c])=>({prev:a.map(([p])=>p),next:c.map(([p])=>p)})),Q({prev:[],next:[]}),ot(2,1),m(([a,c])=>a.prev.length{let i=new T,s=i.pipe(oe(),ae(!0));if(i.subscribe(({prev:a,next:c})=>{for(let[p]of c)p.classList.remove("md-nav__link--passed"),p.classList.remove("md-nav__link--active");for(let[p,[l]]of a.entries())l.classList.add("md-nav__link--passed"),l.classList.toggle("md-nav__link--active",p===a.length-1)}),V("toc.follow")){let a=L(t.pipe(Ae(1),m(()=>{})),t.pipe(Ae(250),m(()=>"smooth")));i.pipe(g(({prev:c})=>c.length>0),Pe(o.pipe(xe(pe))),te(a)).subscribe(([[{prev:c}],p])=>{let[l]=c[c.length-1];if(l.offsetHeight){let f=vr(l);if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=de(f);f.scrollTo({top:u-d/2,behavior:p})}}})}return V("navigation.tracking")&&t.pipe(W(s),ne("offset"),Ae(250),Ie(1),W(n.pipe(Ie(1))),vt({delay:250}),te(i)).subscribe(([,{prev:a}])=>{let c=we(),p=a[a.length-1];if(p&&p.length){let[l]=p,{hash:f}=new URL(l.href);c.hash!==f&&(c.hash=f,history.replaceState({},"",`${c}`))}else c.hash="",history.replaceState({},"",`${c}`)}),_s(e,{viewport$:t,header$:r}).pipe(O(a=>i.next(a)),A(()=>i.complete()),m(a=>P({ref:e},a)))})}function As(e,{viewport$:t,main$:r,target$:o}){let n=t.pipe(m(({offset:{y:s}})=>s),ot(2,1),m(([s,a])=>s>a&&a>0),Y()),i=r.pipe(m(({active:s})=>s));return z([i,n]).pipe(m(([s,a])=>!(s&&a)),Y(),W(o.pipe(Ie(1))),ae(!0),vt({delay:250}),m(s=>({hidden:s})))}function Ii(e,{viewport$:t,header$:r,main$:o,target$:n}){let i=new T,s=i.pipe(oe(),ae(!0));return i.subscribe({next({hidden:a}){e.hidden=a,a?(e.setAttribute("tabindex","-1"),e.blur()):e.removeAttribute("tabindex")},complete(){e.style.top="",e.hidden=!0,e.removeAttribute("tabindex")}}),r.pipe(W(s),ne("height")).subscribe(({height:a})=>{e.style.top=`${a+16}px`}),h(e,"click").subscribe(a=>{a.preventDefault(),window.scrollTo({top:0})}),As(e,{viewport$:t,main$:o,target$:n}).pipe(O(a=>i.next(a)),A(()=>i.complete()),m(a=>P({ref:e},a)))}function Fi({document$:e,viewport$:t}){e.pipe(b(()=>M(".md-ellipsis")),J(r=>mt(r).pipe(W(e.pipe(Ie(1))),g(o=>o),m(()=>r),Ee(1))),g(r=>r.offsetWidth{let o=r.innerText,n=r.closest("a")||r;return n.title=o,V("content.tooltips")?Xe(n,{viewport$:t}).pipe(W(e.pipe(Ie(1))),A(()=>n.removeAttribute("title"))):y})).subscribe(),V("content.tooltips")&&e.pipe(b(()=>M(".md-status")),J(r=>Xe(r,{viewport$:t}))).subscribe()}function ji({document$:e,tablet$:t}){e.pipe(b(()=>M(".md-toggle--indeterminate")),O(r=>{r.indeterminate=!0,r.checked=!1}),J(r=>h(r,"change").pipe(Jr(()=>r.classList.contains("md-toggle--indeterminate")),m(()=>r))),te(t)).subscribe(([r,o])=>{r.classList.remove("md-toggle--indeterminate"),o&&(r.checked=!1)})}function Cs(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function Ui({document$:e}){e.pipe(b(()=>M("[data-md-scrollfix]")),O(t=>t.removeAttribute("data-md-scrollfix")),g(Cs),J(t=>h(t,"touchstart").pipe(m(()=>t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}function Wi({viewport$:e,tablet$:t}){z([Je("search"),t]).pipe(m(([r,o])=>r&&!o),b(r=>$(r).pipe(nt(r?400:100))),te(e)).subscribe(([r,{offset:{y:o}}])=>{if(r)document.body.setAttribute("data-md-scrolllock",""),document.body.style.top=`-${o}px`;else{let n=-1*parseInt(document.body.style.top,10);document.body.removeAttribute("data-md-scrolllock"),document.body.style.top="",n&&window.scrollTo(0,n)}})}Object.entries||(Object.entries=function(e){let t=[];for(let r of Object.keys(e))t.push([r,e[r]]);return t});Object.values||(Object.values=function(e){let t=[];for(let r of Object.keys(e))t.push(e[r]);return t});typeof Element!="undefined"&&(Element.prototype.scrollTo||(Element.prototype.scrollTo=function(e,t){typeof e=="object"?(this.scrollLeft=e.left,this.scrollTop=e.top):(this.scrollLeft=e,this.scrollTop=t)}),Element.prototype.replaceWith||(Element.prototype.replaceWith=function(...e){let t=this.parentNode;if(t){e.length===0&&t.removeChild(this);for(let r=e.length-1;r>=0;r--){let o=e[r];typeof o=="string"?o=document.createTextNode(o):o.parentNode&&o.parentNode.removeChild(o),r?t.insertBefore(this.previousSibling,o):t.replaceChild(o,this)}}}));function ks(){return location.protocol==="file:"?_t(`${new URL("search/search_index.js",Or.base)}`).pipe(m(()=>__index),Z(1)):ze(new URL("search/search_index.json",Or.base))}document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var ct=an(),Kt=bn(),Ht=yn(Kt),mo=hn(),ke=Ln(),Lr=Wt("(min-width: 60em)"),Vi=Wt("(min-width: 76.25em)"),Ni=xn(),Or=Te(),zi=document.forms.namedItem("search")?ks():tt,fo=new T;di({alert$:fo});ui({document$:ct});var uo=new T,qi=kt(Or.base);V("navigation.instant")&&gi({sitemap$:qi,location$:Kt,viewport$:ke,progress$:uo}).subscribe(ct);var Di;((Di=Or.version)==null?void 0:Di.provider)==="mike"&&Ti({document$:ct});L(Kt,Ht).pipe(nt(125)).subscribe(()=>{at("drawer",!1),at("search",!1)});mo.pipe(g(({mode:e})=>e==="global")).subscribe(e=>{switch(e.type){case"p":case",":let t=ue("link[rel=prev]");typeof t!="undefined"&&st(t);break;case"n":case".":let r=ue("link[rel=next]");typeof r!="undefined"&&st(r);break;case"Enter":let o=Ne();o instanceof HTMLLabelElement&&o.click()}});Fi({viewport$:ke,document$:ct});ji({document$:ct,tablet$:Lr});Ui({document$:ct});Wi({viewport$:ke,tablet$:Lr});var ft=ai(Ce("header"),{viewport$:ke}),qt=ct.pipe(m(()=>Ce("main")),b(e=>pi(e,{viewport$:ke,header$:ft})),Z(1)),Hs=L(...me("consent").map(e=>An(e,{target$:Ht})),...me("dialog").map(e=>ni(e,{alert$:fo})),...me("palette").map(e=>li(e)),...me("progress").map(e=>mi(e,{progress$:uo})),...me("search").map(e=>_i(e,{index$:zi,keyboard$:mo})),...me("source").map(e=>$i(e))),$s=H(()=>L(...me("announce").map(e=>_n(e)),...me("content").map(e=>oi(e,{sitemap$:qi,viewport$:ke,target$:Ht,print$:Ni})),...me("content").map(e=>V("search.highlight")?Ai(e,{index$:zi,location$:Kt}):y),...me("header").map(e=>si(e,{viewport$:ke,header$:ft,main$:qt})),...me("header-title").map(e=>ci(e,{viewport$:ke,header$:ft})),...me("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?eo(Vi,()=>lo(e,{viewport$:ke,header$:ft,main$:qt})):eo(Lr,()=>lo(e,{viewport$:ke,header$:ft,main$:qt}))),...me("tabs").map(e=>Pi(e,{viewport$:ke,header$:ft})),...me("toc").map(e=>Ri(e,{viewport$:ke,header$:ft,main$:qt,target$:Ht})),...me("top").map(e=>Ii(e,{viewport$:ke,header$:ft,main$:qt,target$:Ht})))),Ki=ct.pipe(b(()=>$s),Ve(Hs),Z(1));Ki.subscribe();window.document$=ct;window.location$=Kt;window.target$=Ht;window.keyboard$=mo;window.viewport$=ke;window.tablet$=Lr;window.screen$=Vi;window.print$=Ni;window.alert$=fo;window.progress$=uo;window.component$=Ki;})(); +//# sourceMappingURL=bundle.d7400e89.min.js.map + diff --git a/site/assets/javascripts/bundle.d7400e89.min.js.map b/site/assets/javascripts/bundle.d7400e89.min.js.map new file mode 100644 index 0000000..bd88dc9 --- /dev/null +++ b/site/assets/javascripts/bundle.d7400e89.min.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["node_modules/focus-visible/dist/focus-visible.js", "node_modules/escape-html/index.js", "node_modules/clipboard/dist/clipboard.js", "src/templates/assets/javascripts/bundle.ts", "node_modules/tslib/tslib.es6.mjs", "node_modules/rxjs/src/internal/util/isFunction.ts", "node_modules/rxjs/src/internal/util/createErrorClass.ts", "node_modules/rxjs/src/internal/util/UnsubscriptionError.ts", "node_modules/rxjs/src/internal/util/arrRemove.ts", "node_modules/rxjs/src/internal/Subscription.ts", "node_modules/rxjs/src/internal/config.ts", "node_modules/rxjs/src/internal/scheduler/timeoutProvider.ts", "node_modules/rxjs/src/internal/util/reportUnhandledError.ts", "node_modules/rxjs/src/internal/util/noop.ts", "node_modules/rxjs/src/internal/NotificationFactories.ts", "node_modules/rxjs/src/internal/util/errorContext.ts", "node_modules/rxjs/src/internal/Subscriber.ts", "node_modules/rxjs/src/internal/symbol/observable.ts", "node_modules/rxjs/src/internal/util/identity.ts", "node_modules/rxjs/src/internal/util/pipe.ts", "node_modules/rxjs/src/internal/Observable.ts", "node_modules/rxjs/src/internal/util/lift.ts", "node_modules/rxjs/src/internal/operators/OperatorSubscriber.ts", "node_modules/rxjs/src/internal/scheduler/animationFrameProvider.ts", "node_modules/rxjs/src/internal/util/ObjectUnsubscribedError.ts", "node_modules/rxjs/src/internal/Subject.ts", "node_modules/rxjs/src/internal/BehaviorSubject.ts", "node_modules/rxjs/src/internal/scheduler/dateTimestampProvider.ts", "node_modules/rxjs/src/internal/ReplaySubject.ts", "node_modules/rxjs/src/internal/scheduler/Action.ts", "node_modules/rxjs/src/internal/scheduler/intervalProvider.ts", "node_modules/rxjs/src/internal/scheduler/AsyncAction.ts", "node_modules/rxjs/src/internal/Scheduler.ts", "node_modules/rxjs/src/internal/scheduler/AsyncScheduler.ts", "node_modules/rxjs/src/internal/scheduler/async.ts", "node_modules/rxjs/src/internal/scheduler/QueueAction.ts", "node_modules/rxjs/src/internal/scheduler/QueueScheduler.ts", "node_modules/rxjs/src/internal/scheduler/queue.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameAction.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameScheduler.ts", "node_modules/rxjs/src/internal/scheduler/animationFrame.ts", "node_modules/rxjs/src/internal/observable/empty.ts", "node_modules/rxjs/src/internal/util/isScheduler.ts", "node_modules/rxjs/src/internal/util/args.ts", "node_modules/rxjs/src/internal/util/isArrayLike.ts", "node_modules/rxjs/src/internal/util/isPromise.ts", "node_modules/rxjs/src/internal/util/isInteropObservable.ts", "node_modules/rxjs/src/internal/util/isAsyncIterable.ts", "node_modules/rxjs/src/internal/util/throwUnobservableError.ts", "node_modules/rxjs/src/internal/symbol/iterator.ts", "node_modules/rxjs/src/internal/util/isIterable.ts", "node_modules/rxjs/src/internal/util/isReadableStreamLike.ts", "node_modules/rxjs/src/internal/observable/innerFrom.ts", "node_modules/rxjs/src/internal/util/executeSchedule.ts", "node_modules/rxjs/src/internal/operators/observeOn.ts", "node_modules/rxjs/src/internal/operators/subscribeOn.ts", "node_modules/rxjs/src/internal/scheduled/scheduleObservable.ts", "node_modules/rxjs/src/internal/scheduled/schedulePromise.ts", "node_modules/rxjs/src/internal/scheduled/scheduleArray.ts", "node_modules/rxjs/src/internal/scheduled/scheduleIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleAsyncIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleReadableStreamLike.ts", "node_modules/rxjs/src/internal/scheduled/scheduled.ts", "node_modules/rxjs/src/internal/observable/from.ts", "node_modules/rxjs/src/internal/observable/of.ts", "node_modules/rxjs/src/internal/observable/throwError.ts", "node_modules/rxjs/src/internal/util/EmptyError.ts", "node_modules/rxjs/src/internal/util/isDate.ts", "node_modules/rxjs/src/internal/operators/map.ts", "node_modules/rxjs/src/internal/util/mapOneOrManyArgs.ts", "node_modules/rxjs/src/internal/util/argsArgArrayOrObject.ts", "node_modules/rxjs/src/internal/util/createObject.ts", "node_modules/rxjs/src/internal/observable/combineLatest.ts", "node_modules/rxjs/src/internal/operators/mergeInternals.ts", "node_modules/rxjs/src/internal/operators/mergeMap.ts", "node_modules/rxjs/src/internal/operators/mergeAll.ts", "node_modules/rxjs/src/internal/operators/concatAll.ts", "node_modules/rxjs/src/internal/observable/concat.ts", "node_modules/rxjs/src/internal/observable/defer.ts", "node_modules/rxjs/src/internal/observable/fromEvent.ts", "node_modules/rxjs/src/internal/observable/fromEventPattern.ts", "node_modules/rxjs/src/internal/observable/timer.ts", "node_modules/rxjs/src/internal/observable/merge.ts", "node_modules/rxjs/src/internal/observable/never.ts", "node_modules/rxjs/src/internal/util/argsOrArgArray.ts", "node_modules/rxjs/src/internal/operators/filter.ts", "node_modules/rxjs/src/internal/observable/zip.ts", "node_modules/rxjs/src/internal/operators/audit.ts", "node_modules/rxjs/src/internal/operators/auditTime.ts", "node_modules/rxjs/src/internal/operators/bufferCount.ts", "node_modules/rxjs/src/internal/operators/catchError.ts", "node_modules/rxjs/src/internal/operators/scanInternals.ts", "node_modules/rxjs/src/internal/operators/combineLatest.ts", "node_modules/rxjs/src/internal/operators/combineLatestWith.ts", "node_modules/rxjs/src/internal/operators/debounce.ts", "node_modules/rxjs/src/internal/operators/debounceTime.ts", "node_modules/rxjs/src/internal/operators/defaultIfEmpty.ts", "node_modules/rxjs/src/internal/operators/take.ts", "node_modules/rxjs/src/internal/operators/ignoreElements.ts", "node_modules/rxjs/src/internal/operators/mapTo.ts", "node_modules/rxjs/src/internal/operators/delayWhen.ts", "node_modules/rxjs/src/internal/operators/delay.ts", "node_modules/rxjs/src/internal/operators/distinct.ts", "node_modules/rxjs/src/internal/operators/distinctUntilChanged.ts", "node_modules/rxjs/src/internal/operators/distinctUntilKeyChanged.ts", "node_modules/rxjs/src/internal/operators/throwIfEmpty.ts", "node_modules/rxjs/src/internal/operators/endWith.ts", "node_modules/rxjs/src/internal/operators/exhaustMap.ts", "node_modules/rxjs/src/internal/operators/finalize.ts", "node_modules/rxjs/src/internal/operators/first.ts", "node_modules/rxjs/src/internal/operators/takeLast.ts", "node_modules/rxjs/src/internal/operators/merge.ts", "node_modules/rxjs/src/internal/operators/mergeWith.ts", "node_modules/rxjs/src/internal/operators/repeat.ts", "node_modules/rxjs/src/internal/operators/scan.ts", "node_modules/rxjs/src/internal/operators/share.ts", "node_modules/rxjs/src/internal/operators/shareReplay.ts", "node_modules/rxjs/src/internal/operators/skip.ts", "node_modules/rxjs/src/internal/operators/skipUntil.ts", "node_modules/rxjs/src/internal/operators/startWith.ts", "node_modules/rxjs/src/internal/operators/switchMap.ts", "node_modules/rxjs/src/internal/operators/takeUntil.ts", "node_modules/rxjs/src/internal/operators/takeWhile.ts", "node_modules/rxjs/src/internal/operators/tap.ts", "node_modules/rxjs/src/internal/operators/throttle.ts", "node_modules/rxjs/src/internal/operators/throttleTime.ts", "node_modules/rxjs/src/internal/operators/withLatestFrom.ts", "node_modules/rxjs/src/internal/operators/zip.ts", "node_modules/rxjs/src/internal/operators/zipWith.ts", "src/templates/assets/javascripts/browser/document/index.ts", "src/templates/assets/javascripts/browser/element/_/index.ts", "src/templates/assets/javascripts/browser/element/focus/index.ts", "src/templates/assets/javascripts/browser/element/hover/index.ts", "src/templates/assets/javascripts/utilities/h/index.ts", "src/templates/assets/javascripts/utilities/round/index.ts", "src/templates/assets/javascripts/browser/script/index.ts", "src/templates/assets/javascripts/browser/element/size/_/index.ts", "src/templates/assets/javascripts/browser/element/size/content/index.ts", "src/templates/assets/javascripts/browser/element/offset/_/index.ts", "src/templates/assets/javascripts/browser/element/offset/content/index.ts", "src/templates/assets/javascripts/browser/element/visibility/index.ts", "src/templates/assets/javascripts/browser/toggle/index.ts", "src/templates/assets/javascripts/browser/keyboard/index.ts", "src/templates/assets/javascripts/browser/location/_/index.ts", "src/templates/assets/javascripts/browser/location/hash/index.ts", "src/templates/assets/javascripts/browser/media/index.ts", "src/templates/assets/javascripts/browser/request/index.ts", "src/templates/assets/javascripts/browser/viewport/offset/index.ts", "src/templates/assets/javascripts/browser/viewport/size/index.ts", "src/templates/assets/javascripts/browser/viewport/_/index.ts", "src/templates/assets/javascripts/browser/viewport/at/index.ts", "src/templates/assets/javascripts/browser/worker/index.ts", "src/templates/assets/javascripts/_/index.ts", "src/templates/assets/javascripts/components/_/index.ts", "src/templates/assets/javascripts/components/announce/index.ts", "src/templates/assets/javascripts/components/consent/index.ts", "src/templates/assets/javascripts/templates/tooltip/index.tsx", "src/templates/assets/javascripts/templates/annotation/index.tsx", "src/templates/assets/javascripts/templates/clipboard/index.tsx", "src/templates/assets/javascripts/templates/search/index.tsx", "src/templates/assets/javascripts/templates/source/index.tsx", "src/templates/assets/javascripts/templates/tabbed/index.tsx", "src/templates/assets/javascripts/templates/table/index.tsx", "src/templates/assets/javascripts/templates/version/index.tsx", "src/templates/assets/javascripts/components/tooltip2/index.ts", "src/templates/assets/javascripts/components/content/annotation/_/index.ts", "src/templates/assets/javascripts/components/content/annotation/list/index.ts", "src/templates/assets/javascripts/components/content/annotation/block/index.ts", "src/templates/assets/javascripts/components/content/code/_/index.ts", "src/templates/assets/javascripts/components/content/details/index.ts", "src/templates/assets/javascripts/components/content/link/index.ts", "src/templates/assets/javascripts/components/content/mermaid/index.css", "src/templates/assets/javascripts/components/content/mermaid/index.ts", "src/templates/assets/javascripts/components/content/table/index.ts", "src/templates/assets/javascripts/components/content/tabs/index.ts", "src/templates/assets/javascripts/components/content/_/index.ts", "src/templates/assets/javascripts/components/dialog/index.ts", "src/templates/assets/javascripts/components/tooltip/index.ts", "src/templates/assets/javascripts/components/header/_/index.ts", "src/templates/assets/javascripts/components/header/title/index.ts", "src/templates/assets/javascripts/components/main/index.ts", "src/templates/assets/javascripts/components/palette/index.ts", "src/templates/assets/javascripts/components/progress/index.ts", "src/templates/assets/javascripts/integrations/sitemap/index.ts", "src/templates/assets/javascripts/integrations/alternate/index.ts", "src/templates/assets/javascripts/integrations/clipboard/index.ts", "src/templates/assets/javascripts/integrations/instant/index.ts", "src/templates/assets/javascripts/integrations/search/highlighter/index.ts", "src/templates/assets/javascripts/integrations/search/worker/message/index.ts", "src/templates/assets/javascripts/integrations/search/worker/_/index.ts", "src/templates/assets/javascripts/integrations/version/findurl/index.ts", "src/templates/assets/javascripts/integrations/version/index.ts", "src/templates/assets/javascripts/components/search/query/index.ts", "src/templates/assets/javascripts/components/search/result/index.ts", "src/templates/assets/javascripts/components/search/share/index.ts", "src/templates/assets/javascripts/components/search/suggest/index.ts", "src/templates/assets/javascripts/components/search/_/index.ts", "src/templates/assets/javascripts/components/search/highlight/index.ts", "src/templates/assets/javascripts/components/sidebar/index.ts", "src/templates/assets/javascripts/components/source/facts/github/index.ts", "src/templates/assets/javascripts/components/source/facts/gitlab/index.ts", "src/templates/assets/javascripts/components/source/facts/_/index.ts", "src/templates/assets/javascripts/components/source/_/index.ts", "src/templates/assets/javascripts/components/tabs/index.ts", "src/templates/assets/javascripts/components/toc/index.ts", "src/templates/assets/javascripts/components/top/index.ts", "src/templates/assets/javascripts/patches/ellipsis/index.ts", "src/templates/assets/javascripts/patches/indeterminate/index.ts", "src/templates/assets/javascripts/patches/scrollfix/index.ts", "src/templates/assets/javascripts/patches/scrolllock/index.ts", "src/templates/assets/javascripts/polyfills/index.ts"], + "sourcesContent": ["(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? factory() :\n typeof define === 'function' && define.amd ? define(factory) :\n (factory());\n}(this, (function () { 'use strict';\n\n /**\n * Applies the :focus-visible polyfill at the given scope.\n * A scope in this case is either the top-level Document or a Shadow Root.\n *\n * @param {(Document|ShadowRoot)} scope\n * @see https://github.com/WICG/focus-visible\n */\n function applyFocusVisiblePolyfill(scope) {\n var hadKeyboardEvent = true;\n var hadFocusVisibleRecently = false;\n var hadFocusVisibleRecentlyTimeout = null;\n\n var inputTypesAllowlist = {\n text: true,\n search: true,\n url: true,\n tel: true,\n email: true,\n password: true,\n number: true,\n date: true,\n month: true,\n week: true,\n time: true,\n datetime: true,\n 'datetime-local': true\n };\n\n /**\n * Helper function for legacy browsers and iframes which sometimes focus\n * elements like document, body, and non-interactive SVG.\n * @param {Element} el\n */\n function isValidFocusTarget(el) {\n if (\n el &&\n el !== document &&\n el.nodeName !== 'HTML' &&\n el.nodeName !== 'BODY' &&\n 'classList' in el &&\n 'contains' in el.classList\n ) {\n return true;\n }\n return false;\n }\n\n /**\n * Computes whether the given element should automatically trigger the\n * `focus-visible` class being added, i.e. whether it should always match\n * `:focus-visible` when focused.\n * @param {Element} el\n * @return {boolean}\n */\n function focusTriggersKeyboardModality(el) {\n var type = el.type;\n var tagName = el.tagName;\n\n if (tagName === 'INPUT' && inputTypesAllowlist[type] && !el.readOnly) {\n return true;\n }\n\n if (tagName === 'TEXTAREA' && !el.readOnly) {\n return true;\n }\n\n if (el.isContentEditable) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Add the `focus-visible` class to the given element if it was not added by\n * the author.\n * @param {Element} el\n */\n function addFocusVisibleClass(el) {\n if (el.classList.contains('focus-visible')) {\n return;\n }\n el.classList.add('focus-visible');\n el.setAttribute('data-focus-visible-added', '');\n }\n\n /**\n * Remove the `focus-visible` class from the given element if it was not\n * originally added by the author.\n * @param {Element} el\n */\n function removeFocusVisibleClass(el) {\n if (!el.hasAttribute('data-focus-visible-added')) {\n return;\n }\n el.classList.remove('focus-visible');\n el.removeAttribute('data-focus-visible-added');\n }\n\n /**\n * If the most recent user interaction was via the keyboard;\n * and the key press did not include a meta, alt/option, or control key;\n * then the modality is keyboard. Otherwise, the modality is not keyboard.\n * Apply `focus-visible` to any current active element and keep track\n * of our keyboard modality state with `hadKeyboardEvent`.\n * @param {KeyboardEvent} e\n */\n function onKeyDown(e) {\n if (e.metaKey || e.altKey || e.ctrlKey) {\n return;\n }\n\n if (isValidFocusTarget(scope.activeElement)) {\n addFocusVisibleClass(scope.activeElement);\n }\n\n hadKeyboardEvent = true;\n }\n\n /**\n * If at any point a user clicks with a pointing device, ensure that we change\n * the modality away from keyboard.\n * This avoids the situation where a user presses a key on an already focused\n * element, and then clicks on a different element, focusing it with a\n * pointing device, while we still think we're in keyboard modality.\n * @param {Event} e\n */\n function onPointerDown(e) {\n hadKeyboardEvent = false;\n }\n\n /**\n * On `focus`, add the `focus-visible` class to the target if:\n * - the target received focus as a result of keyboard navigation, or\n * - the event target is an element that will likely require interaction\n * via the keyboard (e.g. a text box)\n * @param {Event} e\n */\n function onFocus(e) {\n // Prevent IE from focusing the document or HTML element.\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {\n addFocusVisibleClass(e.target);\n }\n }\n\n /**\n * On `blur`, remove the `focus-visible` class from the target.\n * @param {Event} e\n */\n function onBlur(e) {\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (\n e.target.classList.contains('focus-visible') ||\n e.target.hasAttribute('data-focus-visible-added')\n ) {\n // To detect a tab/window switch, we look for a blur event followed\n // rapidly by a visibility change.\n // If we don't see a visibility change within 100ms, it's probably a\n // regular focus change.\n hadFocusVisibleRecently = true;\n window.clearTimeout(hadFocusVisibleRecentlyTimeout);\n hadFocusVisibleRecentlyTimeout = window.setTimeout(function() {\n hadFocusVisibleRecently = false;\n }, 100);\n removeFocusVisibleClass(e.target);\n }\n }\n\n /**\n * If the user changes tabs, keep track of whether or not the previously\n * focused element had .focus-visible.\n * @param {Event} e\n */\n function onVisibilityChange(e) {\n if (document.visibilityState === 'hidden') {\n // If the tab becomes active again, the browser will handle calling focus\n // on the element (Safari actually calls it twice).\n // If this tab change caused a blur on an element with focus-visible,\n // re-apply the class when the user switches back to the tab.\n if (hadFocusVisibleRecently) {\n hadKeyboardEvent = true;\n }\n addInitialPointerMoveListeners();\n }\n }\n\n /**\n * Add a group of listeners to detect usage of any pointing devices.\n * These listeners will be added when the polyfill first loads, and anytime\n * the window is blurred, so that they are active when the window regains\n * focus.\n */\n function addInitialPointerMoveListeners() {\n document.addEventListener('mousemove', onInitialPointerMove);\n document.addEventListener('mousedown', onInitialPointerMove);\n document.addEventListener('mouseup', onInitialPointerMove);\n document.addEventListener('pointermove', onInitialPointerMove);\n document.addEventListener('pointerdown', onInitialPointerMove);\n document.addEventListener('pointerup', onInitialPointerMove);\n document.addEventListener('touchmove', onInitialPointerMove);\n document.addEventListener('touchstart', onInitialPointerMove);\n document.addEventListener('touchend', onInitialPointerMove);\n }\n\n function removeInitialPointerMoveListeners() {\n document.removeEventListener('mousemove', onInitialPointerMove);\n document.removeEventListener('mousedown', onInitialPointerMove);\n document.removeEventListener('mouseup', onInitialPointerMove);\n document.removeEventListener('pointermove', onInitialPointerMove);\n document.removeEventListener('pointerdown', onInitialPointerMove);\n document.removeEventListener('pointerup', onInitialPointerMove);\n document.removeEventListener('touchmove', onInitialPointerMove);\n document.removeEventListener('touchstart', onInitialPointerMove);\n document.removeEventListener('touchend', onInitialPointerMove);\n }\n\n /**\n * When the polfyill first loads, assume the user is in keyboard modality.\n * If any event is received from a pointing device (e.g. mouse, pointer,\n * touch), turn off keyboard modality.\n * This accounts for situations where focus enters the page from the URL bar.\n * @param {Event} e\n */\n function onInitialPointerMove(e) {\n // Work around a Safari quirk that fires a mousemove on whenever the\n // window blurs, even if you're tabbing out of the page. \u00AF\\_(\u30C4)_/\u00AF\n if (e.target.nodeName && e.target.nodeName.toLowerCase() === 'html') {\n return;\n }\n\n hadKeyboardEvent = false;\n removeInitialPointerMoveListeners();\n }\n\n // For some kinds of state, we are interested in changes at the global scope\n // only. For example, global pointer input, global key presses and global\n // visibility change should affect the state at every scope:\n document.addEventListener('keydown', onKeyDown, true);\n document.addEventListener('mousedown', onPointerDown, true);\n document.addEventListener('pointerdown', onPointerDown, true);\n document.addEventListener('touchstart', onPointerDown, true);\n document.addEventListener('visibilitychange', onVisibilityChange, true);\n\n addInitialPointerMoveListeners();\n\n // For focus and blur, we specifically care about state changes in the local\n // scope. This is because focus / blur events that originate from within a\n // shadow root are not re-dispatched from the host element if it was already\n // the active element in its own scope:\n scope.addEventListener('focus', onFocus, true);\n scope.addEventListener('blur', onBlur, true);\n\n // We detect that a node is a ShadowRoot by ensuring that it is a\n // DocumentFragment and also has a host property. This check covers native\n // implementation and polyfill implementation transparently. If we only cared\n // about the native implementation, we could just check if the scope was\n // an instance of a ShadowRoot.\n if (scope.nodeType === Node.DOCUMENT_FRAGMENT_NODE && scope.host) {\n // Since a ShadowRoot is a special kind of DocumentFragment, it does not\n // have a root element to add a class to. So, we add this attribute to the\n // host element instead:\n scope.host.setAttribute('data-js-focus-visible', '');\n } else if (scope.nodeType === Node.DOCUMENT_NODE) {\n document.documentElement.classList.add('js-focus-visible');\n document.documentElement.setAttribute('data-js-focus-visible', '');\n }\n }\n\n // It is important to wrap all references to global window and document in\n // these checks to support server-side rendering use cases\n // @see https://github.com/WICG/focus-visible/issues/199\n if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n // Make the polyfill helper globally available. This can be used as a signal\n // to interested libraries that wish to coordinate with the polyfill for e.g.,\n // applying the polyfill to a shadow root:\n window.applyFocusVisiblePolyfill = applyFocusVisiblePolyfill;\n\n // Notify interested libraries of the polyfill's presence, in case the\n // polyfill was loaded lazily:\n var event;\n\n try {\n event = new CustomEvent('focus-visible-polyfill-ready');\n } catch (error) {\n // IE11 does not support using CustomEvent as a constructor directly:\n event = document.createEvent('CustomEvent');\n event.initCustomEvent('focus-visible-polyfill-ready', false, false, {});\n }\n\n window.dispatchEvent(event);\n }\n\n if (typeof document !== 'undefined') {\n // Apply the polyfill to the global document, so that no JavaScript\n // coordination is required to use the polyfill in the top-level document:\n applyFocusVisiblePolyfill(document);\n }\n\n})));\n", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n var str = '' + string;\n var match = matchHtmlRegExp.exec(str);\n\n if (!match) {\n return str;\n }\n\n var escape;\n var html = '';\n var index = 0;\n var lastIndex = 0;\n\n for (index = match.index; index < str.length; index++) {\n switch (str.charCodeAt(index)) {\n case 34: // \"\n escape = '"';\n break;\n case 38: // &\n escape = '&';\n break;\n case 39: // '\n escape = ''';\n break;\n case 60: // <\n escape = '<';\n break;\n case 62: // >\n escape = '>';\n break;\n default:\n continue;\n }\n\n if (lastIndex !== index) {\n html += str.substring(lastIndex, index);\n }\n\n lastIndex = index + 1;\n html += escape;\n }\n\n return lastIndex !== index\n ? html + str.substring(lastIndex, index)\n : html;\n}\n", "/*!\n * clipboard.js v2.0.11\n * https://clipboardjs.com/\n *\n * Licensed MIT \u00A9 Zeno Rocha\n */\n(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ClipboardJS\"] = factory();\n\telse\n\t\troot[\"ClipboardJS\"] = factory();\n})(this, function() {\nreturn /******/ (function() { // webpackBootstrap\n/******/ \tvar __webpack_modules__ = ({\n\n/***/ 686:\n/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n\n// EXPORTS\n__webpack_require__.d(__webpack_exports__, {\n \"default\": function() { return /* binding */ clipboard; }\n});\n\n// EXTERNAL MODULE: ./node_modules/tiny-emitter/index.js\nvar tiny_emitter = __webpack_require__(279);\nvar tiny_emitter_default = /*#__PURE__*/__webpack_require__.n(tiny_emitter);\n// EXTERNAL MODULE: ./node_modules/good-listener/src/listen.js\nvar listen = __webpack_require__(370);\nvar listen_default = /*#__PURE__*/__webpack_require__.n(listen);\n// EXTERNAL MODULE: ./node_modules/select/src/select.js\nvar src_select = __webpack_require__(817);\nvar select_default = /*#__PURE__*/__webpack_require__.n(src_select);\n;// CONCATENATED MODULE: ./src/common/command.js\n/**\n * Executes a given operation type.\n * @param {String} type\n * @return {Boolean}\n */\nfunction command(type) {\n try {\n return document.execCommand(type);\n } catch (err) {\n return false;\n }\n}\n;// CONCATENATED MODULE: ./src/actions/cut.js\n\n\n/**\n * Cut action wrapper.\n * @param {String|HTMLElement} target\n * @return {String}\n */\n\nvar ClipboardActionCut = function ClipboardActionCut(target) {\n var selectedText = select_default()(target);\n command('cut');\n return selectedText;\n};\n\n/* harmony default export */ var actions_cut = (ClipboardActionCut);\n;// CONCATENATED MODULE: ./src/common/create-fake-element.js\n/**\n * Creates a fake textarea element with a value.\n * @param {String} value\n * @return {HTMLElement}\n */\nfunction createFakeElement(value) {\n var isRTL = document.documentElement.getAttribute('dir') === 'rtl';\n var fakeElement = document.createElement('textarea'); // Prevent zooming on iOS\n\n fakeElement.style.fontSize = '12pt'; // Reset box model\n\n fakeElement.style.border = '0';\n fakeElement.style.padding = '0';\n fakeElement.style.margin = '0'; // Move element out of screen horizontally\n\n fakeElement.style.position = 'absolute';\n fakeElement.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically\n\n var yPosition = window.pageYOffset || document.documentElement.scrollTop;\n fakeElement.style.top = \"\".concat(yPosition, \"px\");\n fakeElement.setAttribute('readonly', '');\n fakeElement.value = value;\n return fakeElement;\n}\n;// CONCATENATED MODULE: ./src/actions/copy.js\n\n\n\n/**\n * Create fake copy action wrapper using a fake element.\n * @param {String} target\n * @param {Object} options\n * @return {String}\n */\n\nvar fakeCopyAction = function fakeCopyAction(value, options) {\n var fakeElement = createFakeElement(value);\n options.container.appendChild(fakeElement);\n var selectedText = select_default()(fakeElement);\n command('copy');\n fakeElement.remove();\n return selectedText;\n};\n/**\n * Copy action wrapper.\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @return {String}\n */\n\n\nvar ClipboardActionCopy = function ClipboardActionCopy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n var selectedText = '';\n\n if (typeof target === 'string') {\n selectedText = fakeCopyAction(target, options);\n } else if (target instanceof HTMLInputElement && !['text', 'search', 'url', 'tel', 'password'].includes(target === null || target === void 0 ? void 0 : target.type)) {\n // If input type doesn't support `setSelectionRange`. Simulate it. https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange\n selectedText = fakeCopyAction(target.value, options);\n } else {\n selectedText = select_default()(target);\n command('copy');\n }\n\n return selectedText;\n};\n\n/* harmony default export */ var actions_copy = (ClipboardActionCopy);\n;// CONCATENATED MODULE: ./src/actions/default.js\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\n\n\n/**\n * Inner function which performs selection from either `text` or `target`\n * properties and then executes copy or cut operations.\n * @param {Object} options\n */\n\nvar ClipboardActionDefault = function ClipboardActionDefault() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n // Defines base properties passed from constructor.\n var _options$action = options.action,\n action = _options$action === void 0 ? 'copy' : _options$action,\n container = options.container,\n target = options.target,\n text = options.text; // Sets the `action` to be performed which can be either 'copy' or 'cut'.\n\n if (action !== 'copy' && action !== 'cut') {\n throw new Error('Invalid \"action\" value, use either \"copy\" or \"cut\"');\n } // Sets the `target` property using an element that will be have its content copied.\n\n\n if (target !== undefined) {\n if (target && _typeof(target) === 'object' && target.nodeType === 1) {\n if (action === 'copy' && target.hasAttribute('disabled')) {\n throw new Error('Invalid \"target\" attribute. Please use \"readonly\" instead of \"disabled\" attribute');\n }\n\n if (action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {\n throw new Error('Invalid \"target\" attribute. You can\\'t cut text from elements with \"readonly\" or \"disabled\" attributes');\n }\n } else {\n throw new Error('Invalid \"target\" value, use a valid Element');\n }\n } // Define selection strategy based on `text` property.\n\n\n if (text) {\n return actions_copy(text, {\n container: container\n });\n } // Defines which selection strategy based on `target` property.\n\n\n if (target) {\n return action === 'cut' ? actions_cut(target) : actions_copy(target, {\n container: container\n });\n }\n};\n\n/* harmony default export */ var actions_default = (ClipboardActionDefault);\n;// CONCATENATED MODULE: ./src/clipboard.js\nfunction clipboard_typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { clipboard_typeof = function _typeof(obj) { return typeof obj; }; } else { clipboard_typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return clipboard_typeof(obj); }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (clipboard_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\n\n\n\n\n\n/**\n * Helper function to retrieve attribute value.\n * @param {String} suffix\n * @param {Element} element\n */\n\nfunction getAttributeValue(suffix, element) {\n var attribute = \"data-clipboard-\".concat(suffix);\n\n if (!element.hasAttribute(attribute)) {\n return;\n }\n\n return element.getAttribute(attribute);\n}\n/**\n * Base class which takes one or more elements, adds event listeners to them,\n * and instantiates a new `ClipboardAction` on each click.\n */\n\n\nvar Clipboard = /*#__PURE__*/function (_Emitter) {\n _inherits(Clipboard, _Emitter);\n\n var _super = _createSuper(Clipboard);\n\n /**\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n * @param {Object} options\n */\n function Clipboard(trigger, options) {\n var _this;\n\n _classCallCheck(this, Clipboard);\n\n _this = _super.call(this);\n\n _this.resolveOptions(options);\n\n _this.listenClick(trigger);\n\n return _this;\n }\n /**\n * Defines if attributes would be resolved using internal setter functions\n * or custom functions that were passed in the constructor.\n * @param {Object} options\n */\n\n\n _createClass(Clipboard, [{\n key: \"resolveOptions\",\n value: function resolveOptions() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n this.action = typeof options.action === 'function' ? options.action : this.defaultAction;\n this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;\n this.text = typeof options.text === 'function' ? options.text : this.defaultText;\n this.container = clipboard_typeof(options.container) === 'object' ? options.container : document.body;\n }\n /**\n * Adds a click event listener to the passed trigger.\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n */\n\n }, {\n key: \"listenClick\",\n value: function listenClick(trigger) {\n var _this2 = this;\n\n this.listener = listen_default()(trigger, 'click', function (e) {\n return _this2.onClick(e);\n });\n }\n /**\n * Defines a new `ClipboardAction` on each click event.\n * @param {Event} e\n */\n\n }, {\n key: \"onClick\",\n value: function onClick(e) {\n var trigger = e.delegateTarget || e.currentTarget;\n var action = this.action(trigger) || 'copy';\n var text = actions_default({\n action: action,\n container: this.container,\n target: this.target(trigger),\n text: this.text(trigger)\n }); // Fires an event based on the copy operation result.\n\n this.emit(text ? 'success' : 'error', {\n action: action,\n text: text,\n trigger: trigger,\n clearSelection: function clearSelection() {\n if (trigger) {\n trigger.focus();\n }\n\n window.getSelection().removeAllRanges();\n }\n });\n }\n /**\n * Default `action` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultAction\",\n value: function defaultAction(trigger) {\n return getAttributeValue('action', trigger);\n }\n /**\n * Default `target` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultTarget\",\n value: function defaultTarget(trigger) {\n var selector = getAttributeValue('target', trigger);\n\n if (selector) {\n return document.querySelector(selector);\n }\n }\n /**\n * Allow fire programmatically a copy action\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @returns Text copied.\n */\n\n }, {\n key: \"defaultText\",\n\n /**\n * Default `text` lookup function.\n * @param {Element} trigger\n */\n value: function defaultText(trigger) {\n return getAttributeValue('text', trigger);\n }\n /**\n * Destroy lifecycle.\n */\n\n }, {\n key: \"destroy\",\n value: function destroy() {\n this.listener.destroy();\n }\n }], [{\n key: \"copy\",\n value: function copy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n return actions_copy(target, options);\n }\n /**\n * Allow fire programmatically a cut action\n * @param {String|HTMLElement} target\n * @returns Text cutted.\n */\n\n }, {\n key: \"cut\",\n value: function cut(target) {\n return actions_cut(target);\n }\n /**\n * Returns the support of the given action, or all actions if no action is\n * given.\n * @param {String} [action]\n */\n\n }, {\n key: \"isSupported\",\n value: function isSupported() {\n var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut'];\n var actions = typeof action === 'string' ? [action] : action;\n var support = !!document.queryCommandSupported;\n actions.forEach(function (action) {\n support = support && !!document.queryCommandSupported(action);\n });\n return support;\n }\n }]);\n\n return Clipboard;\n}((tiny_emitter_default()));\n\n/* harmony default export */ var clipboard = (Clipboard);\n\n/***/ }),\n\n/***/ 828:\n/***/ (function(module) {\n\nvar DOCUMENT_NODE_TYPE = 9;\n\n/**\n * A polyfill for Element.matches()\n */\nif (typeof Element !== 'undefined' && !Element.prototype.matches) {\n var proto = Element.prototype;\n\n proto.matches = proto.matchesSelector ||\n proto.mozMatchesSelector ||\n proto.msMatchesSelector ||\n proto.oMatchesSelector ||\n proto.webkitMatchesSelector;\n}\n\n/**\n * Finds the closest parent that matches a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @return {Function}\n */\nfunction closest (element, selector) {\n while (element && element.nodeType !== DOCUMENT_NODE_TYPE) {\n if (typeof element.matches === 'function' &&\n element.matches(selector)) {\n return element;\n }\n element = element.parentNode;\n }\n}\n\nmodule.exports = closest;\n\n\n/***/ }),\n\n/***/ 438:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar closest = __webpack_require__(828);\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction _delegate(element, selector, type, callback, useCapture) {\n var listenerFn = listener.apply(this, arguments);\n\n element.addEventListener(type, listenerFn, useCapture);\n\n return {\n destroy: function() {\n element.removeEventListener(type, listenerFn, useCapture);\n }\n }\n}\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element|String|Array} [elements]\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction delegate(elements, selector, type, callback, useCapture) {\n // Handle the regular Element usage\n if (typeof elements.addEventListener === 'function') {\n return _delegate.apply(null, arguments);\n }\n\n // Handle Element-less usage, it defaults to global delegation\n if (typeof type === 'function') {\n // Use `document` as the first parameter, then apply arguments\n // This is a short way to .unshift `arguments` without running into deoptimizations\n return _delegate.bind(null, document).apply(null, arguments);\n }\n\n // Handle Selector-based usage\n if (typeof elements === 'string') {\n elements = document.querySelectorAll(elements);\n }\n\n // Handle Array-like based usage\n return Array.prototype.map.call(elements, function (element) {\n return _delegate(element, selector, type, callback, useCapture);\n });\n}\n\n/**\n * Finds closest match and invokes callback.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Function}\n */\nfunction listener(element, selector, type, callback) {\n return function(e) {\n e.delegateTarget = closest(e.target, selector);\n\n if (e.delegateTarget) {\n callback.call(element, e);\n }\n }\n}\n\nmodule.exports = delegate;\n\n\n/***/ }),\n\n/***/ 879:\n/***/ (function(__unused_webpack_module, exports) {\n\n/**\n * Check if argument is a HTML element.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.node = function(value) {\n return value !== undefined\n && value instanceof HTMLElement\n && value.nodeType === 1;\n};\n\n/**\n * Check if argument is a list of HTML elements.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.nodeList = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return value !== undefined\n && (type === '[object NodeList]' || type === '[object HTMLCollection]')\n && ('length' in value)\n && (value.length === 0 || exports.node(value[0]));\n};\n\n/**\n * Check if argument is a string.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.string = function(value) {\n return typeof value === 'string'\n || value instanceof String;\n};\n\n/**\n * Check if argument is a function.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.fn = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return type === '[object Function]';\n};\n\n\n/***/ }),\n\n/***/ 370:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar is = __webpack_require__(879);\nvar delegate = __webpack_require__(438);\n\n/**\n * Validates all params and calls the right\n * listener function based on its target type.\n *\n * @param {String|HTMLElement|HTMLCollection|NodeList} target\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listen(target, type, callback) {\n if (!target && !type && !callback) {\n throw new Error('Missing required arguments');\n }\n\n if (!is.string(type)) {\n throw new TypeError('Second argument must be a String');\n }\n\n if (!is.fn(callback)) {\n throw new TypeError('Third argument must be a Function');\n }\n\n if (is.node(target)) {\n return listenNode(target, type, callback);\n }\n else if (is.nodeList(target)) {\n return listenNodeList(target, type, callback);\n }\n else if (is.string(target)) {\n return listenSelector(target, type, callback);\n }\n else {\n throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');\n }\n}\n\n/**\n * Adds an event listener to a HTML element\n * and returns a remove listener function.\n *\n * @param {HTMLElement} node\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNode(node, type, callback) {\n node.addEventListener(type, callback);\n\n return {\n destroy: function() {\n node.removeEventListener(type, callback);\n }\n }\n}\n\n/**\n * Add an event listener to a list of HTML elements\n * and returns a remove listener function.\n *\n * @param {NodeList|HTMLCollection} nodeList\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNodeList(nodeList, type, callback) {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.addEventListener(type, callback);\n });\n\n return {\n destroy: function() {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.removeEventListener(type, callback);\n });\n }\n }\n}\n\n/**\n * Add an event listener to a selector\n * and returns a remove listener function.\n *\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenSelector(selector, type, callback) {\n return delegate(document.body, selector, type, callback);\n}\n\nmodule.exports = listen;\n\n\n/***/ }),\n\n/***/ 817:\n/***/ (function(module) {\n\nfunction select(element) {\n var selectedText;\n\n if (element.nodeName === 'SELECT') {\n element.focus();\n\n selectedText = element.value;\n }\n else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {\n var isReadOnly = element.hasAttribute('readonly');\n\n if (!isReadOnly) {\n element.setAttribute('readonly', '');\n }\n\n element.select();\n element.setSelectionRange(0, element.value.length);\n\n if (!isReadOnly) {\n element.removeAttribute('readonly');\n }\n\n selectedText = element.value;\n }\n else {\n if (element.hasAttribute('contenteditable')) {\n element.focus();\n }\n\n var selection = window.getSelection();\n var range = document.createRange();\n\n range.selectNodeContents(element);\n selection.removeAllRanges();\n selection.addRange(range);\n\n selectedText = selection.toString();\n }\n\n return selectedText;\n}\n\nmodule.exports = select;\n\n\n/***/ }),\n\n/***/ 279:\n/***/ (function(module) {\n\nfunction E () {\n // Keep this empty so it's easier to inherit from\n // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)\n}\n\nE.prototype = {\n on: function (name, callback, ctx) {\n var e = this.e || (this.e = {});\n\n (e[name] || (e[name] = [])).push({\n fn: callback,\n ctx: ctx\n });\n\n return this;\n },\n\n once: function (name, callback, ctx) {\n var self = this;\n function listener () {\n self.off(name, listener);\n callback.apply(ctx, arguments);\n };\n\n listener._ = callback\n return this.on(name, listener, ctx);\n },\n\n emit: function (name) {\n var data = [].slice.call(arguments, 1);\n var evtArr = ((this.e || (this.e = {}))[name] || []).slice();\n var i = 0;\n var len = evtArr.length;\n\n for (i; i < len; i++) {\n evtArr[i].fn.apply(evtArr[i].ctx, data);\n }\n\n return this;\n },\n\n off: function (name, callback) {\n var e = this.e || (this.e = {});\n var evts = e[name];\n var liveEvents = [];\n\n if (evts && callback) {\n for (var i = 0, len = evts.length; i < len; i++) {\n if (evts[i].fn !== callback && evts[i].fn._ !== callback)\n liveEvents.push(evts[i]);\n }\n }\n\n // Remove event from queue to prevent memory leak\n // Suggested by https://github.com/lazd\n // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910\n\n (liveEvents.length)\n ? e[name] = liveEvents\n : delete e[name];\n\n return this;\n }\n};\n\nmodule.exports = E;\nmodule.exports.TinyEmitter = E;\n\n\n/***/ })\n\n/******/ \t});\n/************************************************************************/\n/******/ \t// The module cache\n/******/ \tvar __webpack_module_cache__ = {};\n/******/ \t\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(__webpack_module_cache__[moduleId]) {\n/******/ \t\t\treturn __webpack_module_cache__[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = __webpack_module_cache__[moduleId] = {\n/******/ \t\t\t// no module.id needed\n/******/ \t\t\t// no module.loaded needed\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/ \t\n/******/ \t\t// Execute the module function\n/******/ \t\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n/******/ \t\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/ \t\n/************************************************************************/\n/******/ \t/* webpack/runtime/compat get default export */\n/******/ \t!function() {\n/******/ \t\t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t\t__webpack_require__.n = function(module) {\n/******/ \t\t\tvar getter = module && module.__esModule ?\n/******/ \t\t\t\tfunction() { return module['default']; } :\n/******/ \t\t\t\tfunction() { return module; };\n/******/ \t\t\t__webpack_require__.d(getter, { a: getter });\n/******/ \t\t\treturn getter;\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/define property getters */\n/******/ \t!function() {\n/******/ \t\t// define getter functions for harmony exports\n/******/ \t\t__webpack_require__.d = function(exports, definition) {\n/******/ \t\t\tfor(var key in definition) {\n/******/ \t\t\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n/******/ \t\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n/******/ \t\t\t\t}\n/******/ \t\t\t}\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/hasOwnProperty shorthand */\n/******/ \t!function() {\n/******/ \t\t__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }\n/******/ \t}();\n/******/ \t\n/************************************************************************/\n/******/ \t// module exports must be returned from runtime so entry inlining is disabled\n/******/ \t// startup\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(686);\n/******/ })()\n.default;\n});", "/*\n * Copyright (c) 2016-2025 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport \"focus-visible\"\n\nimport {\n EMPTY,\n NEVER,\n Observable,\n Subject,\n defer,\n delay,\n filter,\n map,\n merge,\n mergeWith,\n shareReplay,\n switchMap\n} from \"rxjs\"\n\nimport { configuration, feature } from \"./_\"\nimport {\n at,\n getActiveElement,\n getOptionalElement,\n requestJSON,\n setLocation,\n setToggle,\n watchDocument,\n watchKeyboard,\n watchLocation,\n watchLocationTarget,\n watchMedia,\n watchPrint,\n watchScript,\n watchViewport\n} from \"./browser\"\nimport {\n getComponentElement,\n getComponentElements,\n mountAnnounce,\n mountBackToTop,\n mountConsent,\n mountContent,\n mountDialog,\n mountHeader,\n mountHeaderTitle,\n mountPalette,\n mountProgress,\n mountSearch,\n mountSearchHiglight,\n mountSidebar,\n mountSource,\n mountTableOfContents,\n mountTabs,\n watchHeader,\n watchMain\n} from \"./components\"\nimport {\n SearchIndex,\n fetchSitemap,\n setupAlternate,\n setupClipboardJS,\n setupInstantNavigation,\n setupVersionSelector\n} from \"./integrations\"\nimport {\n patchEllipsis,\n patchIndeterminate,\n patchScrollfix,\n patchScrolllock\n} from \"./patches\"\nimport \"./polyfills\"\n\n/* ----------------------------------------------------------------------------\n * Functions - @todo refactor\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch search index\n *\n * @returns Search index observable\n */\nfunction fetchSearchIndex(): Observable {\n if (location.protocol === \"file:\") {\n return watchScript(\n `${new URL(\"search/search_index.js\", config.base)}`\n )\n .pipe(\n // @ts-ignore - @todo fix typings\n map(() => __index),\n shareReplay(1)\n )\n } else {\n return requestJSON(\n new URL(\"search/search_index.json\", config.base)\n )\n }\n}\n\n/* ----------------------------------------------------------------------------\n * Application\n * ------------------------------------------------------------------------- */\n\n/* Yay, JavaScript is available */\ndocument.documentElement.classList.remove(\"no-js\")\ndocument.documentElement.classList.add(\"js\")\n\n/* Set up navigation observables and subjects */\nconst document$ = watchDocument()\nconst location$ = watchLocation()\nconst target$ = watchLocationTarget(location$)\nconst keyboard$ = watchKeyboard()\n\n/* Set up media observables */\nconst viewport$ = watchViewport()\nconst tablet$ = watchMedia(\"(min-width: 60em)\")\nconst screen$ = watchMedia(\"(min-width: 76.25em)\")\nconst print$ = watchPrint()\n\n/* Retrieve search index, if search is enabled */\nconst config = configuration()\nconst index$ = document.forms.namedItem(\"search\")\n ? fetchSearchIndex()\n : NEVER\n\n/* Set up Clipboard.js integration */\nconst alert$ = new Subject()\nsetupClipboardJS({ alert$ })\n\n/* Set up language selector */\nsetupAlternate({ document$ })\n\n/* Set up progress indicator */\nconst progress$ = new Subject()\n\n/* Set up sitemap for instant navigation and previews */\nconst sitemap$ = fetchSitemap(config.base)\n\n/* Set up instant navigation, if enabled */\nif (feature(\"navigation.instant\"))\n setupInstantNavigation({ sitemap$, location$, viewport$, progress$ })\n .subscribe(document$)\n\n/* Set up version selector */\nif (config.version?.provider === \"mike\")\n setupVersionSelector({ document$ })\n\n/* Always close drawer and search on navigation */\nmerge(location$, target$)\n .pipe(\n delay(125)\n )\n .subscribe(() => {\n setToggle(\"drawer\", false)\n setToggle(\"search\", false)\n })\n\n/* Set up global keyboard handlers */\nkeyboard$\n .pipe(\n filter(({ mode }) => mode === \"global\")\n )\n .subscribe(key => {\n switch (key.type) {\n\n /* Go to previous page */\n case \"p\":\n case \",\":\n const prev = getOptionalElement(\"link[rel=prev]\")\n if (typeof prev !== \"undefined\")\n setLocation(prev)\n break\n\n /* Go to next page */\n case \"n\":\n case \".\":\n const next = getOptionalElement(\"link[rel=next]\")\n if (typeof next !== \"undefined\")\n setLocation(next)\n break\n\n /* Expand navigation, see https://bit.ly/3ZjG5io */\n case \"Enter\":\n const active = getActiveElement()\n if (active instanceof HTMLLabelElement)\n active.click()\n }\n })\n\n/* Set up patches */\npatchEllipsis({ viewport$, document$ })\npatchIndeterminate({ document$, tablet$ })\npatchScrollfix({ document$ })\npatchScrolllock({ viewport$, tablet$ })\n\n/* Set up header and main area observable */\nconst header$ = watchHeader(getComponentElement(\"header\"), { viewport$ })\nconst main$ = document$\n .pipe(\n map(() => getComponentElement(\"main\")),\n switchMap(el => watchMain(el, { viewport$, header$ })),\n shareReplay(1)\n )\n\n/* Set up control component observables */\nconst control$ = merge(\n\n /* Consent */\n ...getComponentElements(\"consent\")\n .map(el => mountConsent(el, { target$ })),\n\n /* Dialog */\n ...getComponentElements(\"dialog\")\n .map(el => mountDialog(el, { alert$ })),\n\n /* Color palette */\n ...getComponentElements(\"palette\")\n .map(el => mountPalette(el)),\n\n /* Progress bar */\n ...getComponentElements(\"progress\")\n .map(el => mountProgress(el, { progress$ })),\n\n /* Search */\n ...getComponentElements(\"search\")\n .map(el => mountSearch(el, { index$, keyboard$ })),\n\n /* Repository information */\n ...getComponentElements(\"source\")\n .map(el => mountSource(el))\n)\n\n/* Set up content component observables */\nconst content$ = defer(() => merge(\n\n /* Announcement bar */\n ...getComponentElements(\"announce\")\n .map(el => mountAnnounce(el)),\n\n /* Content */\n ...getComponentElements(\"content\")\n .map(el => mountContent(el, { sitemap$, viewport$, target$, print$ })),\n\n /* Search highlighting */\n ...getComponentElements(\"content\")\n .map(el => feature(\"search.highlight\")\n ? mountSearchHiglight(el, { index$, location$ })\n : EMPTY\n ),\n\n /* Header */\n ...getComponentElements(\"header\")\n .map(el => mountHeader(el, { viewport$, header$, main$ })),\n\n /* Header title */\n ...getComponentElements(\"header-title\")\n .map(el => mountHeaderTitle(el, { viewport$, header$ })),\n\n /* Sidebar */\n ...getComponentElements(\"sidebar\")\n .map(el => el.getAttribute(\"data-md-type\") === \"navigation\"\n ? at(screen$, () => mountSidebar(el, { viewport$, header$, main$ }))\n : at(tablet$, () => mountSidebar(el, { viewport$, header$, main$ }))\n ),\n\n /* Navigation tabs */\n ...getComponentElements(\"tabs\")\n .map(el => mountTabs(el, { viewport$, header$ })),\n\n /* Table of contents */\n ...getComponentElements(\"toc\")\n .map(el => mountTableOfContents(el, {\n viewport$, header$, main$, target$\n })),\n\n /* Back-to-top button */\n ...getComponentElements(\"top\")\n .map(el => mountBackToTop(el, { viewport$, header$, main$, target$ }))\n))\n\n/* Set up component observables */\nconst component$ = document$\n .pipe(\n switchMap(() => content$),\n mergeWith(control$),\n shareReplay(1)\n )\n\n/* Subscribe to all components */\ncomponent$.subscribe()\n\n/* ----------------------------------------------------------------------------\n * Exports\n * ------------------------------------------------------------------------- */\n\nwindow.document$ = document$ /* Document observable */\nwindow.location$ = location$ /* Location subject */\nwindow.target$ = target$ /* Location target observable */\nwindow.keyboard$ = keyboard$ /* Keyboard observable */\nwindow.viewport$ = viewport$ /* Viewport observable */\nwindow.tablet$ = tablet$ /* Media tablet observable */\nwindow.screen$ = screen$ /* Media screen observable */\nwindow.print$ = print$ /* Media print observable */\nwindow.alert$ = alert$ /* Alert subject */\nwindow.progress$ = progress$ /* Progress indicator subject */\nwindow.component$ = component$ /* Component observable */\n", "/******************************************************************************\nCopyright (c) Microsoft Corporation.\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\nPERFORMANCE OF THIS SOFTWARE.\n***************************************************************************** */\n/* global Reflect, Promise, SuppressedError, Symbol, Iterator */\n\nvar extendStatics = function(d, b) {\n extendStatics = Object.setPrototypeOf ||\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\n return extendStatics(d, b);\n};\n\nexport function __extends(d, b) {\n if (typeof b !== \"function\" && b !== null)\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\n extendStatics(d, b);\n function __() { this.constructor = d; }\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\n}\n\nexport var __assign = function() {\n __assign = Object.assign || function __assign(t) {\n for (var s, i = 1, n = arguments.length; i < n; i++) {\n s = arguments[i];\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\n }\n return t;\n }\n return __assign.apply(this, arguments);\n}\n\nexport function __rest(s, e) {\n var t = {};\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\n t[p] = s[p];\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\n t[p[i]] = s[p[i]];\n }\n return t;\n}\n\nexport function __decorate(decorators, target, key, desc) {\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n return c > 3 && r && Object.defineProperty(target, key, r), r;\n}\n\nexport function __param(paramIndex, decorator) {\n return function (target, key) { decorator(target, key, paramIndex); }\n}\n\nexport function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {\n function accept(f) { if (f !== void 0 && typeof f !== \"function\") throw new TypeError(\"Function expected\"); return f; }\n var kind = contextIn.kind, key = kind === \"getter\" ? \"get\" : kind === \"setter\" ? \"set\" : \"value\";\n var target = !descriptorIn && ctor ? contextIn[\"static\"] ? ctor : ctor.prototype : null;\n var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});\n var _, done = false;\n for (var i = decorators.length - 1; i >= 0; i--) {\n var context = {};\n for (var p in contextIn) context[p] = p === \"access\" ? {} : contextIn[p];\n for (var p in contextIn.access) context.access[p] = contextIn.access[p];\n context.addInitializer = function (f) { if (done) throw new TypeError(\"Cannot add initializers after decoration has completed\"); extraInitializers.push(accept(f || null)); };\n var result = (0, decorators[i])(kind === \"accessor\" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);\n if (kind === \"accessor\") {\n if (result === void 0) continue;\n if (result === null || typeof result !== \"object\") throw new TypeError(\"Object expected\");\n if (_ = accept(result.get)) descriptor.get = _;\n if (_ = accept(result.set)) descriptor.set = _;\n if (_ = accept(result.init)) initializers.unshift(_);\n }\n else if (_ = accept(result)) {\n if (kind === \"field\") initializers.unshift(_);\n else descriptor[key] = _;\n }\n }\n if (target) Object.defineProperty(target, contextIn.name, descriptor);\n done = true;\n};\n\nexport function __runInitializers(thisArg, initializers, value) {\n var useValue = arguments.length > 2;\n for (var i = 0; i < initializers.length; i++) {\n value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);\n }\n return useValue ? value : void 0;\n};\n\nexport function __propKey(x) {\n return typeof x === \"symbol\" ? x : \"\".concat(x);\n};\n\nexport function __setFunctionName(f, name, prefix) {\n if (typeof name === \"symbol\") name = name.description ? \"[\".concat(name.description, \"]\") : \"\";\n return Object.defineProperty(f, \"name\", { configurable: true, value: prefix ? \"\".concat(prefix, \" \", name) : name });\n};\n\nexport function __metadata(metadataKey, metadataValue) {\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\n}\n\nexport function __awaiter(thisArg, _arguments, P, generator) {\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n}\n\nexport function __generator(thisArg, body) {\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === \"function\" ? Iterator : Object).prototype);\n return g.next = verb(0), g[\"throw\"] = verb(1), g[\"return\"] = verb(2), typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\n function verb(n) { return function (v) { return step([n, v]); }; }\n function step(op) {\n if (f) throw new TypeError(\"Generator is already executing.\");\n while (g && (g = 0, op[0] && (_ = 0)), _) try {\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\n if (y = 0, t) op = [op[0] & 2, t.value];\n switch (op[0]) {\n case 0: case 1: t = op; break;\n case 4: _.label++; return { value: op[1], done: false };\n case 5: _.label++; y = op[1]; op = [0]; continue;\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\n default:\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\n if (t[2]) _.ops.pop();\n _.trys.pop(); continue;\n }\n op = body.call(thisArg, _);\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\n }\n}\n\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n var desc = Object.getOwnPropertyDescriptor(m, k);\n if (!desc || (\"get\" in desc ? !m.__esModule : desc.writable || desc.configurable)) {\n desc = { enumerable: true, get: function() { return m[k]; } };\n }\n Object.defineProperty(o, k2, desc);\n}) : (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n o[k2] = m[k];\n});\n\nexport function __exportStar(m, o) {\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\n}\n\nexport function __values(o) {\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\n if (m) return m.call(o);\n if (o && typeof o.length === \"number\") return {\n next: function () {\n if (o && i >= o.length) o = void 0;\n return { value: o && o[i++], done: !o };\n }\n };\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\n}\n\nexport function __read(o, n) {\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\n if (!m) return o;\n var i = m.call(o), r, ar = [], e;\n try {\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\n }\n catch (error) { e = { error: error }; }\n finally {\n try {\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\n }\n finally { if (e) throw e.error; }\n }\n return ar;\n}\n\n/** @deprecated */\nexport function __spread() {\n for (var ar = [], i = 0; i < arguments.length; i++)\n ar = ar.concat(__read(arguments[i]));\n return ar;\n}\n\n/** @deprecated */\nexport function __spreadArrays() {\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\n r[k] = a[j];\n return r;\n}\n\nexport function __spreadArray(to, from, pack) {\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\n if (ar || !(i in from)) {\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\n ar[i] = from[i];\n }\n }\n return to.concat(ar || Array.prototype.slice.call(from));\n}\n\nexport function __await(v) {\n return this instanceof __await ? (this.v = v, this) : new __await(v);\n}\n\nexport function __asyncGenerator(thisArg, _arguments, generator) {\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\n return i = Object.create((typeof AsyncIterator === \"function\" ? AsyncIterator : Object).prototype), verb(\"next\"), verb(\"throw\"), verb(\"return\", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;\n function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }\n function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\n function fulfill(value) { resume(\"next\", value); }\n function reject(value) { resume(\"throw\", value); }\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\n}\n\nexport function __asyncDelegator(o) {\n var i, p;\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: false } : f ? f(v) : v; } : f; }\n}\n\nexport function __asyncValues(o) {\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\n var m = o[Symbol.asyncIterator], i;\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\n}\n\nexport function __makeTemplateObject(cooked, raw) {\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\n return cooked;\n};\n\nvar __setModuleDefault = Object.create ? (function(o, v) {\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\n}) : function(o, v) {\n o[\"default\"] = v;\n};\n\nexport function __importStar(mod) {\n if (mod && mod.__esModule) return mod;\n var result = {};\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\n __setModuleDefault(result, mod);\n return result;\n}\n\nexport function __importDefault(mod) {\n return (mod && mod.__esModule) ? mod : { default: mod };\n}\n\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\n}\n\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\n}\n\nexport function __classPrivateFieldIn(state, receiver) {\n if (receiver === null || (typeof receiver !== \"object\" && typeof receiver !== \"function\")) throw new TypeError(\"Cannot use 'in' operator on non-object\");\n return typeof state === \"function\" ? receiver === state : state.has(receiver);\n}\n\nexport function __addDisposableResource(env, value, async) {\n if (value !== null && value !== void 0) {\n if (typeof value !== \"object\" && typeof value !== \"function\") throw new TypeError(\"Object expected.\");\n var dispose, inner;\n if (async) {\n if (!Symbol.asyncDispose) throw new TypeError(\"Symbol.asyncDispose is not defined.\");\n dispose = value[Symbol.asyncDispose];\n }\n if (dispose === void 0) {\n if (!Symbol.dispose) throw new TypeError(\"Symbol.dispose is not defined.\");\n dispose = value[Symbol.dispose];\n if (async) inner = dispose;\n }\n if (typeof dispose !== \"function\") throw new TypeError(\"Object not disposable.\");\n if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };\n env.stack.push({ value: value, dispose: dispose, async: async });\n }\n else if (async) {\n env.stack.push({ async: true });\n }\n return value;\n}\n\nvar _SuppressedError = typeof SuppressedError === \"function\" ? SuppressedError : function (error, suppressed, message) {\n var e = new Error(message);\n return e.name = \"SuppressedError\", e.error = error, e.suppressed = suppressed, e;\n};\n\nexport function __disposeResources(env) {\n function fail(e) {\n env.error = env.hasError ? new _SuppressedError(e, env.error, \"An error was suppressed during disposal.\") : e;\n env.hasError = true;\n }\n var r, s = 0;\n function next() {\n while (r = env.stack.pop()) {\n try {\n if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);\n if (r.dispose) {\n var result = r.dispose.call(r.value);\n if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });\n }\n else s |= 1;\n }\n catch (e) {\n fail(e);\n }\n }\n if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();\n if (env.hasError) throw env.error;\n }\n return next();\n}\n\nexport default {\n __extends,\n __assign,\n __rest,\n __decorate,\n __param,\n __metadata,\n __awaiter,\n __generator,\n __createBinding,\n __exportStar,\n __values,\n __read,\n __spread,\n __spreadArrays,\n __spreadArray,\n __await,\n __asyncGenerator,\n __asyncDelegator,\n __asyncValues,\n __makeTemplateObject,\n __importStar,\n __importDefault,\n __classPrivateFieldGet,\n __classPrivateFieldSet,\n __classPrivateFieldIn,\n __addDisposableResource,\n __disposeResources,\n};\n", "/**\n * Returns true if the object is a function.\n * @param value The value to check\n */\nexport function isFunction(value: any): value is (...args: any[]) => any {\n return typeof value === 'function';\n}\n", "/**\n * Used to create Error subclasses until the community moves away from ES5.\n *\n * This is because compiling from TypeScript down to ES5 has issues with subclassing Errors\n * as well as other built-in types: https://github.com/Microsoft/TypeScript/issues/12123\n *\n * @param createImpl A factory function to create the actual constructor implementation. The returned\n * function should be a named function that calls `_super` internally.\n */\nexport function createErrorClass(createImpl: (_super: any) => any): T {\n const _super = (instance: any) => {\n Error.call(instance);\n instance.stack = new Error().stack;\n };\n\n const ctorFunc = createImpl(_super);\n ctorFunc.prototype = Object.create(Error.prototype);\n ctorFunc.prototype.constructor = ctorFunc;\n return ctorFunc;\n}\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface UnsubscriptionError extends Error {\n readonly errors: any[];\n}\n\nexport interface UnsubscriptionErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (errors: any[]): UnsubscriptionError;\n}\n\n/**\n * An error thrown when one or more errors have occurred during the\n * `unsubscribe` of a {@link Subscription}.\n */\nexport const UnsubscriptionError: UnsubscriptionErrorCtor = createErrorClass(\n (_super) =>\n function UnsubscriptionErrorImpl(this: any, errors: (Error | string)[]) {\n _super(this);\n this.message = errors\n ? `${errors.length} errors occurred during unsubscription:\n${errors.map((err, i) => `${i + 1}) ${err.toString()}`).join('\\n ')}`\n : '';\n this.name = 'UnsubscriptionError';\n this.errors = errors;\n }\n);\n", "/**\n * Removes an item from an array, mutating it.\n * @param arr The array to remove the item from\n * @param item The item to remove\n */\nexport function arrRemove(arr: T[] | undefined | null, item: T) {\n if (arr) {\n const index = arr.indexOf(item);\n 0 <= index && arr.splice(index, 1);\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { UnsubscriptionError } from './util/UnsubscriptionError';\nimport { SubscriptionLike, TeardownLogic, Unsubscribable } from './types';\nimport { arrRemove } from './util/arrRemove';\n\n/**\n * Represents a disposable resource, such as the execution of an Observable. A\n * Subscription has one important method, `unsubscribe`, that takes no argument\n * and just disposes the resource held by the subscription.\n *\n * Additionally, subscriptions may be grouped together through the `add()`\n * method, which will attach a child Subscription to the current Subscription.\n * When a Subscription is unsubscribed, all its children (and its grandchildren)\n * will be unsubscribed as well.\n */\nexport class Subscription implements SubscriptionLike {\n public static EMPTY = (() => {\n const empty = new Subscription();\n empty.closed = true;\n return empty;\n })();\n\n /**\n * A flag to indicate whether this Subscription has already been unsubscribed.\n */\n public closed = false;\n\n private _parentage: Subscription[] | Subscription | null = null;\n\n /**\n * The list of registered finalizers to execute upon unsubscription. Adding and removing from this\n * list occurs in the {@link #add} and {@link #remove} methods.\n */\n private _finalizers: Exclude[] | null = null;\n\n /**\n * @param initialTeardown A function executed first as part of the finalization\n * process that is kicked off when {@link #unsubscribe} is called.\n */\n constructor(private initialTeardown?: () => void) {}\n\n /**\n * Disposes the resources held by the subscription. May, for instance, cancel\n * an ongoing Observable execution or cancel any other type of work that\n * started when the Subscription was created.\n */\n unsubscribe(): void {\n let errors: any[] | undefined;\n\n if (!this.closed) {\n this.closed = true;\n\n // Remove this from it's parents.\n const { _parentage } = this;\n if (_parentage) {\n this._parentage = null;\n if (Array.isArray(_parentage)) {\n for (const parent of _parentage) {\n parent.remove(this);\n }\n } else {\n _parentage.remove(this);\n }\n }\n\n const { initialTeardown: initialFinalizer } = this;\n if (isFunction(initialFinalizer)) {\n try {\n initialFinalizer();\n } catch (e) {\n errors = e instanceof UnsubscriptionError ? e.errors : [e];\n }\n }\n\n const { _finalizers } = this;\n if (_finalizers) {\n this._finalizers = null;\n for (const finalizer of _finalizers) {\n try {\n execFinalizer(finalizer);\n } catch (err) {\n errors = errors ?? [];\n if (err instanceof UnsubscriptionError) {\n errors = [...errors, ...err.errors];\n } else {\n errors.push(err);\n }\n }\n }\n }\n\n if (errors) {\n throw new UnsubscriptionError(errors);\n }\n }\n }\n\n /**\n * Adds a finalizer to this subscription, so that finalization will be unsubscribed/called\n * when this subscription is unsubscribed. If this subscription is already {@link #closed},\n * because it has already been unsubscribed, then whatever finalizer is passed to it\n * will automatically be executed (unless the finalizer itself is also a closed subscription).\n *\n * Closed Subscriptions cannot be added as finalizers to any subscription. Adding a closed\n * subscription to a any subscription will result in no operation. (A noop).\n *\n * Adding a subscription to itself, or adding `null` or `undefined` will not perform any\n * operation at all. (A noop).\n *\n * `Subscription` instances that are added to this instance will automatically remove themselves\n * if they are unsubscribed. Functions and {@link Unsubscribable} objects that you wish to remove\n * will need to be removed manually with {@link #remove}\n *\n * @param teardown The finalization logic to add to this subscription.\n */\n add(teardown: TeardownLogic): void {\n // Only add the finalizer if it's not undefined\n // and don't add a subscription to itself.\n if (teardown && teardown !== this) {\n if (this.closed) {\n // If this subscription is already closed,\n // execute whatever finalizer is handed to it automatically.\n execFinalizer(teardown);\n } else {\n if (teardown instanceof Subscription) {\n // We don't add closed subscriptions, and we don't add the same subscription\n // twice. Subscription unsubscribe is idempotent.\n if (teardown.closed || teardown._hasParent(this)) {\n return;\n }\n teardown._addParent(this);\n }\n (this._finalizers = this._finalizers ?? []).push(teardown);\n }\n }\n }\n\n /**\n * Checks to see if a this subscription already has a particular parent.\n * This will signal that this subscription has already been added to the parent in question.\n * @param parent the parent to check for\n */\n private _hasParent(parent: Subscription) {\n const { _parentage } = this;\n return _parentage === parent || (Array.isArray(_parentage) && _parentage.includes(parent));\n }\n\n /**\n * Adds a parent to this subscription so it can be removed from the parent if it\n * unsubscribes on it's own.\n *\n * NOTE: THIS ASSUMES THAT {@link _hasParent} HAS ALREADY BEEN CHECKED.\n * @param parent The parent subscription to add\n */\n private _addParent(parent: Subscription) {\n const { _parentage } = this;\n this._parentage = Array.isArray(_parentage) ? (_parentage.push(parent), _parentage) : _parentage ? [_parentage, parent] : parent;\n }\n\n /**\n * Called on a child when it is removed via {@link #remove}.\n * @param parent The parent to remove\n */\n private _removeParent(parent: Subscription) {\n const { _parentage } = this;\n if (_parentage === parent) {\n this._parentage = null;\n } else if (Array.isArray(_parentage)) {\n arrRemove(_parentage, parent);\n }\n }\n\n /**\n * Removes a finalizer from this subscription that was previously added with the {@link #add} method.\n *\n * Note that `Subscription` instances, when unsubscribed, will automatically remove themselves\n * from every other `Subscription` they have been added to. This means that using the `remove` method\n * is not a common thing and should be used thoughtfully.\n *\n * If you add the same finalizer instance of a function or an unsubscribable object to a `Subscription` instance\n * more than once, you will need to call `remove` the same number of times to remove all instances.\n *\n * All finalizer instances are removed to free up memory upon unsubscription.\n *\n * @param teardown The finalizer to remove from this subscription\n */\n remove(teardown: Exclude): void {\n const { _finalizers } = this;\n _finalizers && arrRemove(_finalizers, teardown);\n\n if (teardown instanceof Subscription) {\n teardown._removeParent(this);\n }\n }\n}\n\nexport const EMPTY_SUBSCRIPTION = Subscription.EMPTY;\n\nexport function isSubscription(value: any): value is Subscription {\n return (\n value instanceof Subscription ||\n (value && 'closed' in value && isFunction(value.remove) && isFunction(value.add) && isFunction(value.unsubscribe))\n );\n}\n\nfunction execFinalizer(finalizer: Unsubscribable | (() => void)) {\n if (isFunction(finalizer)) {\n finalizer();\n } else {\n finalizer.unsubscribe();\n }\n}\n", "import { Subscriber } from './Subscriber';\nimport { ObservableNotification } from './types';\n\n/**\n * The {@link GlobalConfig} object for RxJS. It is used to configure things\n * like how to react on unhandled errors.\n */\nexport const config: GlobalConfig = {\n onUnhandledError: null,\n onStoppedNotification: null,\n Promise: undefined,\n useDeprecatedSynchronousErrorHandling: false,\n useDeprecatedNextContext: false,\n};\n\n/**\n * The global configuration object for RxJS, used to configure things\n * like how to react on unhandled errors. Accessible via {@link config}\n * object.\n */\nexport interface GlobalConfig {\n /**\n * A registration point for unhandled errors from RxJS. These are errors that\n * cannot were not handled by consuming code in the usual subscription path. For\n * example, if you have this configured, and you subscribe to an observable without\n * providing an error handler, errors from that subscription will end up here. This\n * will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onUnhandledError: ((err: any) => void) | null;\n\n /**\n * A registration point for notifications that cannot be sent to subscribers because they\n * have completed, errored or have been explicitly unsubscribed. By default, next, complete\n * and error notifications sent to stopped subscribers are noops. However, sometimes callers\n * might want a different behavior. For example, with sources that attempt to report errors\n * to stopped subscribers, a caller can configure RxJS to throw an unhandled error instead.\n * This will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onStoppedNotification: ((notification: ObservableNotification, subscriber: Subscriber) => void) | null;\n\n /**\n * The promise constructor used by default for {@link Observable#toPromise toPromise} and {@link Observable#forEach forEach}\n * methods.\n *\n * @deprecated As of version 8, RxJS will no longer support this sort of injection of a\n * Promise constructor. If you need a Promise implementation other than native promises,\n * please polyfill/patch Promise as you see appropriate. Will be removed in v8.\n */\n Promise?: PromiseConstructorLike;\n\n /**\n * If true, turns on synchronous error rethrowing, which is a deprecated behavior\n * in v6 and higher. This behavior enables bad patterns like wrapping a subscribe\n * call in a try/catch block. It also enables producer interference, a nasty bug\n * where a multicast can be broken for all observers by a downstream consumer with\n * an unhandled error. DO NOT USE THIS FLAG UNLESS IT'S NEEDED TO BUY TIME\n * FOR MIGRATION REASONS.\n *\n * @deprecated As of version 8, RxJS will no longer support synchronous throwing\n * of unhandled errors. All errors will be thrown on a separate call stack to prevent bad\n * behaviors described above. Will be removed in v8.\n */\n useDeprecatedSynchronousErrorHandling: boolean;\n\n /**\n * If true, enables an as-of-yet undocumented feature from v5: The ability to access\n * `unsubscribe()` via `this` context in `next` functions created in observers passed\n * to `subscribe`.\n *\n * This is being removed because the performance was severely problematic, and it could also cause\n * issues when types other than POJOs are passed to subscribe as subscribers, as they will likely have\n * their `this` context overwritten.\n *\n * @deprecated As of version 8, RxJS will no longer support altering the\n * context of next functions provided as part of an observer to Subscribe. Instead,\n * you will have access to a subscription or a signal or token that will allow you to do things like\n * unsubscribe and test closed status. Will be removed in v8.\n */\n useDeprecatedNextContext: boolean;\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetTimeoutFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearTimeoutFunction = (handle: TimerHandle) => void;\n\ninterface TimeoutProvider {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n delegate:\n | {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n }\n | undefined;\n}\n\nexport const timeoutProvider: TimeoutProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setTimeout(handler: () => void, timeout?: number, ...args) {\n const { delegate } = timeoutProvider;\n if (delegate?.setTimeout) {\n return delegate.setTimeout(handler, timeout, ...args);\n }\n return setTimeout(handler, timeout, ...args);\n },\n clearTimeout(handle) {\n const { delegate } = timeoutProvider;\n return (delegate?.clearTimeout || clearTimeout)(handle as any);\n },\n delegate: undefined,\n};\n", "import { config } from '../config';\nimport { timeoutProvider } from '../scheduler/timeoutProvider';\n\n/**\n * Handles an error on another job either with the user-configured {@link onUnhandledError},\n * or by throwing it on that new job so it can be picked up by `window.onerror`, `process.on('error')`, etc.\n *\n * This should be called whenever there is an error that is out-of-band with the subscription\n * or when an error hits a terminal boundary of the subscription and no error handler was provided.\n *\n * @param err the error to report\n */\nexport function reportUnhandledError(err: any) {\n timeoutProvider.setTimeout(() => {\n const { onUnhandledError } = config;\n if (onUnhandledError) {\n // Execute the user-configured error handler.\n onUnhandledError(err);\n } else {\n // Throw so it is picked up by the runtime's uncaught error mechanism.\n throw err;\n }\n });\n}\n", "/* tslint:disable:no-empty */\nexport function noop() { }\n", "import { CompleteNotification, NextNotification, ErrorNotification } from './types';\n\n/**\n * A completion object optimized for memory use and created to be the\n * same \"shape\" as other notifications in v8.\n * @internal\n */\nexport const COMPLETE_NOTIFICATION = (() => createNotification('C', undefined, undefined) as CompleteNotification)();\n\n/**\n * Internal use only. Creates an optimized error notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function errorNotification(error: any): ErrorNotification {\n return createNotification('E', undefined, error) as any;\n}\n\n/**\n * Internal use only. Creates an optimized next notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function nextNotification(value: T) {\n return createNotification('N', value, undefined) as NextNotification;\n}\n\n/**\n * Ensures that all notifications created internally have the same \"shape\" in v8.\n *\n * TODO: This is only exported to support a crazy legacy test in `groupBy`.\n * @internal\n */\nexport function createNotification(kind: 'N' | 'E' | 'C', value: any, error: any) {\n return {\n kind,\n value,\n error,\n };\n}\n", "import { config } from '../config';\n\nlet context: { errorThrown: boolean; error: any } | null = null;\n\n/**\n * Handles dealing with errors for super-gross mode. Creates a context, in which\n * any synchronously thrown errors will be passed to {@link captureError}. Which\n * will record the error such that it will be rethrown after the call back is complete.\n * TODO: Remove in v8\n * @param cb An immediately executed function.\n */\nexport function errorContext(cb: () => void) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n const isRoot = !context;\n if (isRoot) {\n context = { errorThrown: false, error: null };\n }\n cb();\n if (isRoot) {\n const { errorThrown, error } = context!;\n context = null;\n if (errorThrown) {\n throw error;\n }\n }\n } else {\n // This is the general non-deprecated path for everyone that\n // isn't crazy enough to use super-gross mode (useDeprecatedSynchronousErrorHandling)\n cb();\n }\n}\n\n/**\n * Captures errors only in super-gross mode.\n * @param err the error to capture\n */\nexport function captureError(err: any) {\n if (config.useDeprecatedSynchronousErrorHandling && context) {\n context.errorThrown = true;\n context.error = err;\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { Observer, ObservableNotification } from './types';\nimport { isSubscription, Subscription } from './Subscription';\nimport { config } from './config';\nimport { reportUnhandledError } from './util/reportUnhandledError';\nimport { noop } from './util/noop';\nimport { nextNotification, errorNotification, COMPLETE_NOTIFICATION } from './NotificationFactories';\nimport { timeoutProvider } from './scheduler/timeoutProvider';\nimport { captureError } from './util/errorContext';\n\n/**\n * Implements the {@link Observer} interface and extends the\n * {@link Subscription} class. While the {@link Observer} is the public API for\n * consuming the values of an {@link Observable}, all Observers get converted to\n * a Subscriber, in order to provide Subscription-like capabilities such as\n * `unsubscribe`. Subscriber is a common type in RxJS, and crucial for\n * implementing operators, but it is rarely used as a public API.\n */\nexport class Subscriber extends Subscription implements Observer {\n /**\n * A static factory for a Subscriber, given a (potentially partial) definition\n * of an Observer.\n * @param next The `next` callback of an Observer.\n * @param error The `error` callback of an\n * Observer.\n * @param complete The `complete` callback of an\n * Observer.\n * @return A Subscriber wrapping the (partially defined)\n * Observer represented by the given arguments.\n * @deprecated Do not use. Will be removed in v8. There is no replacement for this\n * method, and there is no reason to be creating instances of `Subscriber` directly.\n * If you have a specific use case, please file an issue.\n */\n static create(next?: (x?: T) => void, error?: (e?: any) => void, complete?: () => void): Subscriber {\n return new SafeSubscriber(next, error, complete);\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected isStopped: boolean = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected destination: Subscriber | Observer; // this `any` is the escape hatch to erase extra type param (e.g. R)\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * There is no reason to directly create an instance of Subscriber. This type is exported for typings reasons.\n */\n constructor(destination?: Subscriber | Observer) {\n super();\n if (destination) {\n this.destination = destination;\n // Automatically chain subscriptions together here.\n // if destination is a Subscription, then it is a Subscriber.\n if (isSubscription(destination)) {\n destination.add(this);\n }\n } else {\n this.destination = EMPTY_OBSERVER;\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `next` from\n * the Observable, with a value. The Observable may call this method 0 or more\n * times.\n * @param value The `next` value.\n */\n next(value: T): void {\n if (this.isStopped) {\n handleStoppedNotification(nextNotification(value), this);\n } else {\n this._next(value!);\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `error` from\n * the Observable, with an attached `Error`. Notifies the Observer that\n * the Observable has experienced an error condition.\n * @param err The `error` exception.\n */\n error(err?: any): void {\n if (this.isStopped) {\n handleStoppedNotification(errorNotification(err), this);\n } else {\n this.isStopped = true;\n this._error(err);\n }\n }\n\n /**\n * The {@link Observer} callback to receive a valueless notification of type\n * `complete` from the Observable. Notifies the Observer that the Observable\n * has finished sending push-based notifications.\n */\n complete(): void {\n if (this.isStopped) {\n handleStoppedNotification(COMPLETE_NOTIFICATION, this);\n } else {\n this.isStopped = true;\n this._complete();\n }\n }\n\n unsubscribe(): void {\n if (!this.closed) {\n this.isStopped = true;\n super.unsubscribe();\n this.destination = null!;\n }\n }\n\n protected _next(value: T): void {\n this.destination.next(value);\n }\n\n protected _error(err: any): void {\n try {\n this.destination.error(err);\n } finally {\n this.unsubscribe();\n }\n }\n\n protected _complete(): void {\n try {\n this.destination.complete();\n } finally {\n this.unsubscribe();\n }\n }\n}\n\n/**\n * This bind is captured here because we want to be able to have\n * compatibility with monoid libraries that tend to use a method named\n * `bind`. In particular, a library called Monio requires this.\n */\nconst _bind = Function.prototype.bind;\n\nfunction bind any>(fn: Fn, thisArg: any): Fn {\n return _bind.call(fn, thisArg);\n}\n\n/**\n * Internal optimization only, DO NOT EXPOSE.\n * @internal\n */\nclass ConsumerObserver implements Observer {\n constructor(private partialObserver: Partial>) {}\n\n next(value: T): void {\n const { partialObserver } = this;\n if (partialObserver.next) {\n try {\n partialObserver.next(value);\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n\n error(err: any): void {\n const { partialObserver } = this;\n if (partialObserver.error) {\n try {\n partialObserver.error(err);\n } catch (error) {\n handleUnhandledError(error);\n }\n } else {\n handleUnhandledError(err);\n }\n }\n\n complete(): void {\n const { partialObserver } = this;\n if (partialObserver.complete) {\n try {\n partialObserver.complete();\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n}\n\nexport class SafeSubscriber extends Subscriber {\n constructor(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((e?: any) => void) | null,\n complete?: (() => void) | null\n ) {\n super();\n\n let partialObserver: Partial>;\n if (isFunction(observerOrNext) || !observerOrNext) {\n // The first argument is a function, not an observer. The next\n // two arguments *could* be observers, or they could be empty.\n partialObserver = {\n next: (observerOrNext ?? undefined) as ((value: T) => void) | undefined,\n error: error ?? undefined,\n complete: complete ?? undefined,\n };\n } else {\n // The first argument is a partial observer.\n let context: any;\n if (this && config.useDeprecatedNextContext) {\n // This is a deprecated path that made `this.unsubscribe()` available in\n // next handler functions passed to subscribe. This only exists behind a flag\n // now, as it is *very* slow.\n context = Object.create(observerOrNext);\n context.unsubscribe = () => this.unsubscribe();\n partialObserver = {\n next: observerOrNext.next && bind(observerOrNext.next, context),\n error: observerOrNext.error && bind(observerOrNext.error, context),\n complete: observerOrNext.complete && bind(observerOrNext.complete, context),\n };\n } else {\n // The \"normal\" path. Just use the partial observer directly.\n partialObserver = observerOrNext;\n }\n }\n\n // Wrap the partial observer to ensure it's a full observer, and\n // make sure proper error handling is accounted for.\n this.destination = new ConsumerObserver(partialObserver);\n }\n}\n\nfunction handleUnhandledError(error: any) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n captureError(error);\n } else {\n // Ideal path, we report this as an unhandled error,\n // which is thrown on a new call stack.\n reportUnhandledError(error);\n }\n}\n\n/**\n * An error handler used when no error handler was supplied\n * to the SafeSubscriber -- meaning no error handler was supplied\n * do the `subscribe` call on our observable.\n * @param err The error to handle\n */\nfunction defaultErrorHandler(err: any) {\n throw err;\n}\n\n/**\n * A handler for notifications that cannot be sent to a stopped subscriber.\n * @param notification The notification being sent.\n * @param subscriber The stopped subscriber.\n */\nfunction handleStoppedNotification(notification: ObservableNotification, subscriber: Subscriber) {\n const { onStoppedNotification } = config;\n onStoppedNotification && timeoutProvider.setTimeout(() => onStoppedNotification(notification, subscriber));\n}\n\n/**\n * The observer used as a stub for subscriptions where the user did not\n * pass any arguments to `subscribe`. Comes with the default error handling\n * behavior.\n */\nexport const EMPTY_OBSERVER: Readonly> & { closed: true } = {\n closed: true,\n next: noop,\n error: defaultErrorHandler,\n complete: noop,\n};\n", "/**\n * Symbol.observable or a string \"@@observable\". Used for interop\n *\n * @deprecated We will no longer be exporting this symbol in upcoming versions of RxJS.\n * Instead polyfill and use Symbol.observable directly *or* use https://www.npmjs.com/package/symbol-observable\n */\nexport const observable: string | symbol = (() => (typeof Symbol === 'function' && Symbol.observable) || '@@observable')();\n", "/**\n * This function takes one parameter and just returns it. Simply put,\n * this is like `(x: T): T => x`.\n *\n * ## Examples\n *\n * This is useful in some cases when using things like `mergeMap`\n *\n * ```ts\n * import { interval, take, map, range, mergeMap, identity } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(5));\n *\n * const result$ = source$.pipe(\n * map(i => range(i)),\n * mergeMap(identity) // same as mergeMap(x => x)\n * );\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * Or when you want to selectively apply an operator\n *\n * ```ts\n * import { interval, take, identity } from 'rxjs';\n *\n * const shouldLimit = () => Math.random() < 0.5;\n *\n * const source$ = interval(1000);\n *\n * const result$ = source$.pipe(shouldLimit() ? take(5) : identity);\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * @param x Any value that is returned by this function\n * @returns The value passed as the first parameter to this function\n */\nexport function identity(x: T): T {\n return x;\n}\n", "import { identity } from './identity';\nimport { UnaryFunction } from '../types';\n\nexport function pipe(): typeof identity;\nexport function pipe(fn1: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction, fn3: UnaryFunction): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction,\n ...fns: UnaryFunction[]\n): UnaryFunction;\n\n/**\n * pipe() can be called on one or more functions, each of which can take one argument (\"UnaryFunction\")\n * and uses it to return a value.\n * It returns a function that takes one argument, passes it to the first UnaryFunction, and then\n * passes the result to the next one, passes that result to the next one, and so on. \n */\nexport function pipe(...fns: Array>): UnaryFunction {\n return pipeFromArray(fns);\n}\n\n/** @internal */\nexport function pipeFromArray(fns: Array>): UnaryFunction {\n if (fns.length === 0) {\n return identity as UnaryFunction;\n }\n\n if (fns.length === 1) {\n return fns[0];\n }\n\n return function piped(input: T): R {\n return fns.reduce((prev: any, fn: UnaryFunction) => fn(prev), input as any);\n };\n}\n", "import { Operator } from './Operator';\nimport { SafeSubscriber, Subscriber } from './Subscriber';\nimport { isSubscription, Subscription } from './Subscription';\nimport { TeardownLogic, OperatorFunction, Subscribable, Observer } from './types';\nimport { observable as Symbol_observable } from './symbol/observable';\nimport { pipeFromArray } from './util/pipe';\nimport { config } from './config';\nimport { isFunction } from './util/isFunction';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A representation of any set of values over any amount of time. This is the most basic building block\n * of RxJS.\n */\nexport class Observable implements Subscribable {\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n source: Observable | undefined;\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n operator: Operator | undefined;\n\n /**\n * @param subscribe The function that is called when the Observable is\n * initially subscribed to. This function is given a Subscriber, to which new values\n * can be `next`ed, or an `error` method can be called to raise an error, or\n * `complete` can be called to notify of a successful completion.\n */\n constructor(subscribe?: (this: Observable, subscriber: Subscriber) => TeardownLogic) {\n if (subscribe) {\n this._subscribe = subscribe;\n }\n }\n\n // HACK: Since TypeScript inherits static properties too, we have to\n // fight against TypeScript here so Subject can have a different static create signature\n /**\n * Creates a new Observable by calling the Observable constructor\n * @param subscribe the subscriber function to be passed to the Observable constructor\n * @return A new observable.\n * @deprecated Use `new Observable()` instead. Will be removed in v8.\n */\n static create: (...args: any[]) => any = (subscribe?: (subscriber: Subscriber) => TeardownLogic) => {\n return new Observable(subscribe);\n };\n\n /**\n * Creates a new Observable, with this Observable instance as the source, and the passed\n * operator defined as the new observable's operator.\n * @param operator the operator defining the operation to take on the observable\n * @return A new observable with the Operator applied.\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * If you have implemented an operator using `lift`, it is recommended that you create an\n * operator by simply returning `new Observable()` directly. See \"Creating new operators from\n * scratch\" section here: https://rxjs.dev/guide/operators\n */\n lift(operator?: Operator): Observable {\n const observable = new Observable();\n observable.source = this;\n observable.operator = operator;\n return observable;\n }\n\n subscribe(observerOrNext?: Partial> | ((value: T) => void)): Subscription;\n /** @deprecated Instead of passing separate callback arguments, use an observer argument. Signatures taking separate callback arguments will be removed in v8. Details: https://rxjs.dev/deprecations/subscribe-arguments */\n subscribe(next?: ((value: T) => void) | null, error?: ((error: any) => void) | null, complete?: (() => void) | null): Subscription;\n /**\n * Invokes an execution of an Observable and registers Observer handlers for notifications it will emit.\n *\n * Use it when you have all these Observables, but still nothing is happening.\n *\n * `subscribe` is not a regular operator, but a method that calls Observable's internal `subscribe` function. It\n * might be for example a function that you passed to Observable's constructor, but most of the time it is\n * a library implementation, which defines what will be emitted by an Observable, and when it be will emitted. This means\n * that calling `subscribe` is actually the moment when Observable starts its work, not when it is created, as it is often\n * the thought.\n *\n * Apart from starting the execution of an Observable, this method allows you to listen for values\n * that an Observable emits, as well as for when it completes or errors. You can achieve this in two\n * of the following ways.\n *\n * The first way is creating an object that implements {@link Observer} interface. It should have methods\n * defined by that interface, but note that it should be just a regular JavaScript object, which you can create\n * yourself in any way you want (ES6 class, classic function constructor, object literal etc.). In particular, do\n * not attempt to use any RxJS implementation details to create Observers - you don't need them. Remember also\n * that your object does not have to implement all methods. If you find yourself creating a method that doesn't\n * do anything, you can simply omit it. Note however, if the `error` method is not provided and an error happens,\n * it will be thrown asynchronously. Errors thrown asynchronously cannot be caught using `try`/`catch`. Instead,\n * use the {@link onUnhandledError} configuration option or use a runtime handler (like `window.onerror` or\n * `process.on('error)`) to be notified of unhandled errors. Because of this, it's recommended that you provide\n * an `error` method to avoid missing thrown errors.\n *\n * The second way is to give up on Observer object altogether and simply provide callback functions in place of its methods.\n * This means you can provide three functions as arguments to `subscribe`, where the first function is equivalent\n * of a `next` method, the second of an `error` method and the third of a `complete` method. Just as in case of an Observer,\n * if you do not need to listen for something, you can omit a function by passing `undefined` or `null`,\n * since `subscribe` recognizes these functions by where they were placed in function call. When it comes\n * to the `error` function, as with an Observer, if not provided, errors emitted by an Observable will be thrown asynchronously.\n *\n * You can, however, subscribe with no parameters at all. This may be the case where you're not interested in terminal events\n * and you also handled emissions internally by using operators (e.g. using `tap`).\n *\n * Whichever style of calling `subscribe` you use, in both cases it returns a Subscription object.\n * This object allows you to call `unsubscribe` on it, which in turn will stop the work that an Observable does and will clean\n * up all resources that an Observable used. Note that cancelling a subscription will not call `complete` callback\n * provided to `subscribe` function, which is reserved for a regular completion signal that comes from an Observable.\n *\n * Remember that callbacks provided to `subscribe` are not guaranteed to be called asynchronously.\n * It is an Observable itself that decides when these functions will be called. For example {@link of}\n * by default emits all its values synchronously. Always check documentation for how given Observable\n * will behave when subscribed and if its default behavior can be modified with a `scheduler`.\n *\n * #### Examples\n *\n * Subscribe with an {@link guide/observer Observer}\n *\n * ```ts\n * import { of } from 'rxjs';\n *\n * const sumObserver = {\n * sum: 0,\n * next(value) {\n * console.log('Adding: ' + value);\n * this.sum = this.sum + value;\n * },\n * error() {\n * // We actually could just remove this method,\n * // since we do not really care about errors right now.\n * },\n * complete() {\n * console.log('Sum equals: ' + this.sum);\n * }\n * };\n *\n * of(1, 2, 3) // Synchronously emits 1, 2, 3 and then completes.\n * .subscribe(sumObserver);\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Subscribe with functions ({@link deprecations/subscribe-arguments deprecated})\n *\n * ```ts\n * import { of } from 'rxjs'\n *\n * let sum = 0;\n *\n * of(1, 2, 3).subscribe(\n * value => {\n * console.log('Adding: ' + value);\n * sum = sum + value;\n * },\n * undefined,\n * () => console.log('Sum equals: ' + sum)\n * );\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Cancel a subscription\n *\n * ```ts\n * import { interval } from 'rxjs';\n *\n * const subscription = interval(1000).subscribe({\n * next(num) {\n * console.log(num)\n * },\n * complete() {\n * // Will not be called, even when cancelling subscription.\n * console.log('completed!');\n * }\n * });\n *\n * setTimeout(() => {\n * subscription.unsubscribe();\n * console.log('unsubscribed!');\n * }, 2500);\n *\n * // Logs:\n * // 0 after 1s\n * // 1 after 2s\n * // 'unsubscribed!' after 2.5s\n * ```\n *\n * @param observerOrNext Either an {@link Observer} with some or all callback methods,\n * or the `next` handler that is called for each value emitted from the subscribed Observable.\n * @param error A handler for a terminal event resulting from an error. If no error handler is provided,\n * the error will be thrown asynchronously as unhandled.\n * @param complete A handler for a terminal event resulting from successful completion.\n * @return A subscription reference to the registered handlers.\n */\n subscribe(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((error: any) => void) | null,\n complete?: (() => void) | null\n ): Subscription {\n const subscriber = isSubscriber(observerOrNext) ? observerOrNext : new SafeSubscriber(observerOrNext, error, complete);\n\n errorContext(() => {\n const { operator, source } = this;\n subscriber.add(\n operator\n ? // We're dealing with a subscription in the\n // operator chain to one of our lifted operators.\n operator.call(subscriber, source)\n : source\n ? // If `source` has a value, but `operator` does not, something that\n // had intimate knowledge of our API, like our `Subject`, must have\n // set it. We're going to just call `_subscribe` directly.\n this._subscribe(subscriber)\n : // In all other cases, we're likely wrapping a user-provided initializer\n // function, so we need to catch errors and handle them appropriately.\n this._trySubscribe(subscriber)\n );\n });\n\n return subscriber;\n }\n\n /** @internal */\n protected _trySubscribe(sink: Subscriber): TeardownLogic {\n try {\n return this._subscribe(sink);\n } catch (err) {\n // We don't need to return anything in this case,\n // because it's just going to try to `add()` to a subscription\n // above.\n sink.error(err);\n }\n }\n\n /**\n * Used as a NON-CANCELLABLE means of subscribing to an observable, for use with\n * APIs that expect promises, like `async/await`. You cannot unsubscribe from this.\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * #### Example\n *\n * ```ts\n * import { interval, take } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(4));\n *\n * async function getTotal() {\n * let total = 0;\n *\n * await source$.forEach(value => {\n * total += value;\n * console.log('observable -> ' + value);\n * });\n *\n * return total;\n * }\n *\n * getTotal().then(\n * total => console.log('Total: ' + total)\n * );\n *\n * // Expected:\n * // 'observable -> 0'\n * // 'observable -> 1'\n * // 'observable -> 2'\n * // 'observable -> 3'\n * // 'Total: 6'\n * ```\n *\n * @param next A handler for each value emitted by the observable.\n * @return A promise that either resolves on observable completion or\n * rejects with the handled error.\n */\n forEach(next: (value: T) => void): Promise;\n\n /**\n * @param next a handler for each value emitted by the observable\n * @param promiseCtor a constructor function used to instantiate the Promise\n * @return a promise that either resolves on observable completion or\n * rejects with the handled error\n * @deprecated Passing a Promise constructor will no longer be available\n * in upcoming versions of RxJS. This is because it adds weight to the library, for very\n * little benefit. If you need this functionality, it is recommended that you either\n * polyfill Promise, or you create an adapter to convert the returned native promise\n * to whatever promise implementation you wanted. Will be removed in v8.\n */\n forEach(next: (value: T) => void, promiseCtor: PromiseConstructorLike): Promise;\n\n forEach(next: (value: T) => void, promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n const subscriber = new SafeSubscriber({\n next: (value) => {\n try {\n next(value);\n } catch (err) {\n reject(err);\n subscriber.unsubscribe();\n }\n },\n error: reject,\n complete: resolve,\n });\n this.subscribe(subscriber);\n }) as Promise;\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): TeardownLogic {\n return this.source?.subscribe(subscriber);\n }\n\n /**\n * An interop point defined by the es7-observable spec https://github.com/zenparsing/es-observable\n * @return This instance of the observable.\n */\n [Symbol_observable]() {\n return this;\n }\n\n /* tslint:disable:max-line-length */\n pipe(): Observable;\n pipe(op1: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction, op3: OperatorFunction): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction,\n ...operations: OperatorFunction[]\n ): Observable;\n /* tslint:enable:max-line-length */\n\n /**\n * Used to stitch together functional operators into a chain.\n *\n * ## Example\n *\n * ```ts\n * import { interval, filter, map, scan } from 'rxjs';\n *\n * interval(1000)\n * .pipe(\n * filter(x => x % 2 === 0),\n * map(x => x + x),\n * scan((acc, x) => acc + x)\n * )\n * .subscribe(x => console.log(x));\n * ```\n *\n * @return The Observable result of all the operators having been called\n * in the order they were passed in.\n */\n pipe(...operations: OperatorFunction[]): Observable {\n return pipeFromArray(operations)(this);\n }\n\n /* tslint:disable:max-line-length */\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: typeof Promise): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: PromiseConstructorLike): Promise;\n /* tslint:enable:max-line-length */\n\n /**\n * Subscribe to this Observable and get a Promise resolving on\n * `complete` with the last emission (if any).\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * @param [promiseCtor] a constructor function used to instantiate\n * the Promise\n * @return A Promise that resolves with the last value emit, or\n * rejects on an error. If there were no emissions, Promise\n * resolves with undefined.\n * @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise\n */\n toPromise(promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n let value: T | undefined;\n this.subscribe(\n (x: T) => (value = x),\n (err: any) => reject(err),\n () => resolve(value)\n );\n }) as Promise;\n }\n}\n\n/**\n * Decides between a passed promise constructor from consuming code,\n * A default configured promise constructor, and the native promise\n * constructor and returns it. If nothing can be found, it will throw\n * an error.\n * @param promiseCtor The optional promise constructor to passed by consuming code\n */\nfunction getPromiseCtor(promiseCtor: PromiseConstructorLike | undefined) {\n return promiseCtor ?? config.Promise ?? Promise;\n}\n\nfunction isObserver(value: any): value is Observer {\n return value && isFunction(value.next) && isFunction(value.error) && isFunction(value.complete);\n}\n\nfunction isSubscriber(value: any): value is Subscriber {\n return (value && value instanceof Subscriber) || (isObserver(value) && isSubscription(value));\n}\n", "import { Observable } from '../Observable';\nimport { Subscriber } from '../Subscriber';\nimport { OperatorFunction } from '../types';\nimport { isFunction } from './isFunction';\n\n/**\n * Used to determine if an object is an Observable with a lift function.\n */\nexport function hasLift(source: any): source is { lift: InstanceType['lift'] } {\n return isFunction(source?.lift);\n}\n\n/**\n * Creates an `OperatorFunction`. Used to define operators throughout the library in a concise way.\n * @param init The logic to connect the liftedSource to the subscriber at the moment of subscription.\n */\nexport function operate(\n init: (liftedSource: Observable, subscriber: Subscriber) => (() => void) | void\n): OperatorFunction {\n return (source: Observable) => {\n if (hasLift(source)) {\n return source.lift(function (this: Subscriber, liftedSource: Observable) {\n try {\n return init(liftedSource, this);\n } catch (err) {\n this.error(err);\n }\n });\n }\n throw new TypeError('Unable to lift unknown Observable type');\n };\n}\n", "import { Subscriber } from '../Subscriber';\n\n/**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional teardown logic here. This will only be called on teardown if the\n * subscriber itself is not already closed. This is called after all other teardown logic is executed.\n */\nexport function createOperatorSubscriber(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n onFinalize?: () => void\n): Subscriber {\n return new OperatorSubscriber(destination, onNext, onComplete, onError, onFinalize);\n}\n\n/**\n * A generic helper for allowing operators to be created with a Subscriber and\n * use closures to capture necessary state from the operator function itself.\n */\nexport class OperatorSubscriber extends Subscriber {\n /**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional finalization logic here. This will only be called on finalization if the\n * subscriber itself is not already closed. This is called after all other finalization logic is executed.\n * @param shouldUnsubscribe An optional check to see if an unsubscribe call should truly unsubscribe.\n * NOTE: This currently **ONLY** exists to support the strange behavior of {@link groupBy}, where unsubscription\n * to the resulting observable does not actually disconnect from the source if there are active subscriptions\n * to any grouped observable. (DO NOT EXPOSE OR USE EXTERNALLY!!!)\n */\n constructor(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n private onFinalize?: () => void,\n private shouldUnsubscribe?: () => boolean\n ) {\n // It's important - for performance reasons - that all of this class's\n // members are initialized and that they are always initialized in the same\n // order. This will ensure that all OperatorSubscriber instances have the\n // same hidden class in V8. This, in turn, will help keep the number of\n // hidden classes involved in property accesses within the base class as\n // low as possible. If the number of hidden classes involved exceeds four,\n // the property accesses will become megamorphic and performance penalties\n // will be incurred - i.e. inline caches won't be used.\n //\n // The reasons for ensuring all instances have the same hidden class are\n // further discussed in this blog post from Benedikt Meurer:\n // https://benediktmeurer.de/2018/03/23/impact-of-polymorphism-on-component-based-frameworks-like-react/\n super(destination);\n this._next = onNext\n ? function (this: OperatorSubscriber, value: T) {\n try {\n onNext(value);\n } catch (err) {\n destination.error(err);\n }\n }\n : super._next;\n this._error = onError\n ? function (this: OperatorSubscriber, err: any) {\n try {\n onError(err);\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._error;\n this._complete = onComplete\n ? function (this: OperatorSubscriber) {\n try {\n onComplete();\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._complete;\n }\n\n unsubscribe() {\n if (!this.shouldUnsubscribe || this.shouldUnsubscribe()) {\n const { closed } = this;\n super.unsubscribe();\n // Execute additional teardown if we have any and we didn't already do so.\n !closed && this.onFinalize?.();\n }\n }\n}\n", "import { Subscription } from '../Subscription';\n\ninterface AnimationFrameProvider {\n schedule(callback: FrameRequestCallback): Subscription;\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n delegate:\n | {\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n }\n | undefined;\n}\n\nexport const animationFrameProvider: AnimationFrameProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n schedule(callback) {\n let request = requestAnimationFrame;\n let cancel: typeof cancelAnimationFrame | undefined = cancelAnimationFrame;\n const { delegate } = animationFrameProvider;\n if (delegate) {\n request = delegate.requestAnimationFrame;\n cancel = delegate.cancelAnimationFrame;\n }\n const handle = request((timestamp) => {\n // Clear the cancel function. The request has been fulfilled, so\n // attempting to cancel the request upon unsubscription would be\n // pointless.\n cancel = undefined;\n callback(timestamp);\n });\n return new Subscription(() => cancel?.(handle));\n },\n requestAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.requestAnimationFrame || requestAnimationFrame)(...args);\n },\n cancelAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.cancelAnimationFrame || cancelAnimationFrame)(...args);\n },\n delegate: undefined,\n};\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface ObjectUnsubscribedError extends Error {}\n\nexport interface ObjectUnsubscribedErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (): ObjectUnsubscribedError;\n}\n\n/**\n * An error thrown when an action is invalid because the object has been\n * unsubscribed.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n *\n * @class ObjectUnsubscribedError\n */\nexport const ObjectUnsubscribedError: ObjectUnsubscribedErrorCtor = createErrorClass(\n (_super) =>\n function ObjectUnsubscribedErrorImpl(this: any) {\n _super(this);\n this.name = 'ObjectUnsubscribedError';\n this.message = 'object unsubscribed';\n }\n);\n", "import { Operator } from './Operator';\nimport { Observable } from './Observable';\nimport { Subscriber } from './Subscriber';\nimport { Subscription, EMPTY_SUBSCRIPTION } from './Subscription';\nimport { Observer, SubscriptionLike, TeardownLogic } from './types';\nimport { ObjectUnsubscribedError } from './util/ObjectUnsubscribedError';\nimport { arrRemove } from './util/arrRemove';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A Subject is a special type of Observable that allows values to be\n * multicasted to many Observers. Subjects are like EventEmitters.\n *\n * Every Subject is an Observable and an Observer. You can subscribe to a\n * Subject, and you can call next to feed values as well as error and complete.\n */\nexport class Subject extends Observable implements SubscriptionLike {\n closed = false;\n\n private currentObservers: Observer[] | null = null;\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n observers: Observer[] = [];\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n isStopped = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n hasError = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n thrownError: any = null;\n\n /**\n * Creates a \"subject\" by basically gluing an observer to an observable.\n *\n * @deprecated Recommended you do not use. Will be removed at some point in the future. Plans for replacement still under discussion.\n */\n static create: (...args: any[]) => any = (destination: Observer, source: Observable): AnonymousSubject => {\n return new AnonymousSubject(destination, source);\n };\n\n constructor() {\n // NOTE: This must be here to obscure Observable's constructor.\n super();\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n lift(operator: Operator): Observable {\n const subject = new AnonymousSubject(this, this);\n subject.operator = operator as any;\n return subject as any;\n }\n\n /** @internal */\n protected _throwIfClosed() {\n if (this.closed) {\n throw new ObjectUnsubscribedError();\n }\n }\n\n next(value: T) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n if (!this.currentObservers) {\n this.currentObservers = Array.from(this.observers);\n }\n for (const observer of this.currentObservers) {\n observer.next(value);\n }\n }\n });\n }\n\n error(err: any) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.hasError = this.isStopped = true;\n this.thrownError = err;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.error(err);\n }\n }\n });\n }\n\n complete() {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.isStopped = true;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.complete();\n }\n }\n });\n }\n\n unsubscribe() {\n this.isStopped = this.closed = true;\n this.observers = this.currentObservers = null!;\n }\n\n get observed() {\n return this.observers?.length > 0;\n }\n\n /** @internal */\n protected _trySubscribe(subscriber: Subscriber): TeardownLogic {\n this._throwIfClosed();\n return super._trySubscribe(subscriber);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._checkFinalizedStatuses(subscriber);\n return this._innerSubscribe(subscriber);\n }\n\n /** @internal */\n protected _innerSubscribe(subscriber: Subscriber) {\n const { hasError, isStopped, observers } = this;\n if (hasError || isStopped) {\n return EMPTY_SUBSCRIPTION;\n }\n this.currentObservers = null;\n observers.push(subscriber);\n return new Subscription(() => {\n this.currentObservers = null;\n arrRemove(observers, subscriber);\n });\n }\n\n /** @internal */\n protected _checkFinalizedStatuses(subscriber: Subscriber) {\n const { hasError, thrownError, isStopped } = this;\n if (hasError) {\n subscriber.error(thrownError);\n } else if (isStopped) {\n subscriber.complete();\n }\n }\n\n /**\n * Creates a new Observable with this Subject as the source. You can do this\n * to create custom Observer-side logic of the Subject and conceal it from\n * code that uses the Observable.\n * @return Observable that this Subject casts to.\n */\n asObservable(): Observable {\n const observable: any = new Observable();\n observable.source = this;\n return observable;\n }\n}\n\nexport class AnonymousSubject extends Subject {\n constructor(\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n public destination?: Observer,\n source?: Observable\n ) {\n super();\n this.source = source;\n }\n\n next(value: T) {\n this.destination?.next?.(value);\n }\n\n error(err: any) {\n this.destination?.error?.(err);\n }\n\n complete() {\n this.destination?.complete?.();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n return this.source?.subscribe(subscriber) ?? EMPTY_SUBSCRIPTION;\n }\n}\n", "import { Subject } from './Subject';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\n\n/**\n * A variant of Subject that requires an initial value and emits its current\n * value whenever it is subscribed to.\n */\nexport class BehaviorSubject extends Subject {\n constructor(private _value: T) {\n super();\n }\n\n get value(): T {\n return this.getValue();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n const subscription = super._subscribe(subscriber);\n !subscription.closed && subscriber.next(this._value);\n return subscription;\n }\n\n getValue(): T {\n const { hasError, thrownError, _value } = this;\n if (hasError) {\n throw thrownError;\n }\n this._throwIfClosed();\n return _value;\n }\n\n next(value: T): void {\n super.next((this._value = value));\n }\n}\n", "import { TimestampProvider } from '../types';\n\ninterface DateTimestampProvider extends TimestampProvider {\n delegate: TimestampProvider | undefined;\n}\n\nexport const dateTimestampProvider: DateTimestampProvider = {\n now() {\n // Use the variable rather than `this` so that the function can be called\n // without being bound to the provider.\n return (dateTimestampProvider.delegate || Date).now();\n },\n delegate: undefined,\n};\n", "import { Subject } from './Subject';\nimport { TimestampProvider } from './types';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * A variant of {@link Subject} that \"replays\" old values to new subscribers by emitting them when they first subscribe.\n *\n * `ReplaySubject` has an internal buffer that will store a specified number of values that it has observed. Like `Subject`,\n * `ReplaySubject` \"observes\" values by having them passed to its `next` method. When it observes a value, it will store that\n * value for a time determined by the configuration of the `ReplaySubject`, as passed to its constructor.\n *\n * When a new subscriber subscribes to the `ReplaySubject` instance, it will synchronously emit all values in its buffer in\n * a First-In-First-Out (FIFO) manner. The `ReplaySubject` will also complete, if it has observed completion; and it will\n * error if it has observed an error.\n *\n * There are two main configuration items to be concerned with:\n *\n * 1. `bufferSize` - This will determine how many items are stored in the buffer, defaults to infinite.\n * 2. `windowTime` - The amount of time to hold a value in the buffer before removing it from the buffer.\n *\n * Both configurations may exist simultaneously. So if you would like to buffer a maximum of 3 values, as long as the values\n * are less than 2 seconds old, you could do so with a `new ReplaySubject(3, 2000)`.\n *\n * ### Differences with BehaviorSubject\n *\n * `BehaviorSubject` is similar to `new ReplaySubject(1)`, with a couple of exceptions:\n *\n * 1. `BehaviorSubject` comes \"primed\" with a single value upon construction.\n * 2. `ReplaySubject` will replay values, even after observing an error, where `BehaviorSubject` will not.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n * @see {@link shareReplay}\n */\nexport class ReplaySubject extends Subject {\n private _buffer: (T | number)[] = [];\n private _infiniteTimeWindow = true;\n\n /**\n * @param _bufferSize The size of the buffer to replay on subscription\n * @param _windowTime The amount of time the buffered items will stay buffered\n * @param _timestampProvider An object with a `now()` method that provides the current timestamp. This is used to\n * calculate the amount of time something has been buffered.\n */\n constructor(\n private _bufferSize = Infinity,\n private _windowTime = Infinity,\n private _timestampProvider: TimestampProvider = dateTimestampProvider\n ) {\n super();\n this._infiniteTimeWindow = _windowTime === Infinity;\n this._bufferSize = Math.max(1, _bufferSize);\n this._windowTime = Math.max(1, _windowTime);\n }\n\n next(value: T): void {\n const { isStopped, _buffer, _infiniteTimeWindow, _timestampProvider, _windowTime } = this;\n if (!isStopped) {\n _buffer.push(value);\n !_infiniteTimeWindow && _buffer.push(_timestampProvider.now() + _windowTime);\n }\n this._trimBuffer();\n super.next(value);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._trimBuffer();\n\n const subscription = this._innerSubscribe(subscriber);\n\n const { _infiniteTimeWindow, _buffer } = this;\n // We use a copy here, so reentrant code does not mutate our array while we're\n // emitting it to a new subscriber.\n const copy = _buffer.slice();\n for (let i = 0; i < copy.length && !subscriber.closed; i += _infiniteTimeWindow ? 1 : 2) {\n subscriber.next(copy[i] as T);\n }\n\n this._checkFinalizedStatuses(subscriber);\n\n return subscription;\n }\n\n private _trimBuffer() {\n const { _bufferSize, _timestampProvider, _buffer, _infiniteTimeWindow } = this;\n // If we don't have an infinite buffer size, and we're over the length,\n // use splice to truncate the old buffer values off. Note that we have to\n // double the size for instances where we're not using an infinite time window\n // because we're storing the values and the timestamps in the same array.\n const adjustedBufferSize = (_infiniteTimeWindow ? 1 : 2) * _bufferSize;\n _bufferSize < Infinity && adjustedBufferSize < _buffer.length && _buffer.splice(0, _buffer.length - adjustedBufferSize);\n\n // Now, if we're not in an infinite time window, remove all values where the time is\n // older than what is allowed.\n if (!_infiniteTimeWindow) {\n const now = _timestampProvider.now();\n let last = 0;\n // Search the array for the first timestamp that isn't expired and\n // truncate the buffer up to that point.\n for (let i = 1; i < _buffer.length && (_buffer[i] as number) <= now; i += 2) {\n last = i;\n }\n last && _buffer.splice(0, last + 1);\n }\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Subscription } from '../Subscription';\nimport { SchedulerAction } from '../types';\n\n/**\n * A unit of work to be executed in a `scheduler`. An action is typically\n * created from within a {@link SchedulerLike} and an RxJS user does not need to concern\n * themselves about creating and manipulating an Action.\n *\n * ```ts\n * class Action extends Subscription {\n * new (scheduler: Scheduler, work: (state?: T) => void);\n * schedule(state?: T, delay: number = 0): Subscription;\n * }\n * ```\n */\nexport class Action extends Subscription {\n constructor(scheduler: Scheduler, work: (this: SchedulerAction, state?: T) => void) {\n super();\n }\n /**\n * Schedules this action on its parent {@link SchedulerLike} for execution. May be passed\n * some context object, `state`. May happen at some point in the future,\n * according to the `delay` parameter, if specified.\n * @param state Some contextual data that the `work` function uses when called by the\n * Scheduler.\n * @param delay Time to wait before executing the work, where the time unit is implicit\n * and defined by the Scheduler.\n * @return A subscription in order to be able to unsubscribe the scheduled work.\n */\n public schedule(state?: T, delay: number = 0): Subscription {\n return this;\n }\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetIntervalFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearIntervalFunction = (handle: TimerHandle) => void;\n\ninterface IntervalProvider {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n delegate:\n | {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n }\n | undefined;\n}\n\nexport const intervalProvider: IntervalProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setInterval(handler: () => void, timeout?: number, ...args) {\n const { delegate } = intervalProvider;\n if (delegate?.setInterval) {\n return delegate.setInterval(handler, timeout, ...args);\n }\n return setInterval(handler, timeout, ...args);\n },\n clearInterval(handle) {\n const { delegate } = intervalProvider;\n return (delegate?.clearInterval || clearInterval)(handle as any);\n },\n delegate: undefined,\n};\n", "import { Action } from './Action';\nimport { SchedulerAction } from '../types';\nimport { Subscription } from '../Subscription';\nimport { AsyncScheduler } from './AsyncScheduler';\nimport { intervalProvider } from './intervalProvider';\nimport { arrRemove } from '../util/arrRemove';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncAction extends Action {\n public id: TimerHandle | undefined;\n public state?: T;\n // @ts-ignore: Property has no initializer and is not definitely assigned\n public delay: number;\n protected pending: boolean = false;\n\n constructor(protected scheduler: AsyncScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (this.closed) {\n return this;\n }\n\n // Always replace the current state with the new state.\n this.state = state;\n\n const id = this.id;\n const scheduler = this.scheduler;\n\n //\n // Important implementation note:\n //\n // Actions only execute once by default, unless rescheduled from within the\n // scheduled callback. This allows us to implement single and repeat\n // actions via the same code path, without adding API surface area, as well\n // as mimic traditional recursion but across asynchronous boundaries.\n //\n // However, JS runtimes and timers distinguish between intervals achieved by\n // serial `setTimeout` calls vs. a single `setInterval` call. An interval of\n // serial `setTimeout` calls can be individually delayed, which delays\n // scheduling the next `setTimeout`, and so on. `setInterval` attempts to\n // guarantee the interval callback will be invoked more precisely to the\n // interval period, regardless of load.\n //\n // Therefore, we use `setInterval` to schedule single and repeat actions.\n // If the action reschedules itself with the same delay, the interval is not\n // canceled. If the action doesn't reschedule, or reschedules with a\n // different delay, the interval will be canceled after scheduled callback\n // execution.\n //\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, delay);\n }\n\n // Set the pending flag indicating that this action has been scheduled, or\n // has recursively rescheduled itself.\n this.pending = true;\n\n this.delay = delay;\n // If this action has already an async Id, don't request a new one.\n this.id = this.id ?? this.requestAsyncId(scheduler, this.id, delay);\n\n return this;\n }\n\n protected requestAsyncId(scheduler: AsyncScheduler, _id?: TimerHandle, delay: number = 0): TimerHandle {\n return intervalProvider.setInterval(scheduler.flush.bind(scheduler, this), delay);\n }\n\n protected recycleAsyncId(_scheduler: AsyncScheduler, id?: TimerHandle, delay: number | null = 0): TimerHandle | undefined {\n // If this action is rescheduled with the same delay time, don't clear the interval id.\n if (delay != null && this.delay === delay && this.pending === false) {\n return id;\n }\n // Otherwise, if the action's delay time is different from the current delay,\n // or the action has been rescheduled before it's executed, clear the interval id\n if (id != null) {\n intervalProvider.clearInterval(id);\n }\n\n return undefined;\n }\n\n /**\n * Immediately executes this action and the `work` it contains.\n */\n public execute(state: T, delay: number): any {\n if (this.closed) {\n return new Error('executing a cancelled action');\n }\n\n this.pending = false;\n const error = this._execute(state, delay);\n if (error) {\n return error;\n } else if (this.pending === false && this.id != null) {\n // Dequeue if the action didn't reschedule itself. Don't call\n // unsubscribe(), because the action could reschedule later.\n // For example:\n // ```\n // scheduler.schedule(function doWork(counter) {\n // /* ... I'm a busy worker bee ... */\n // var originalAction = this;\n // /* wait 100ms before rescheduling the action */\n // setTimeout(function () {\n // originalAction.schedule(counter + 1);\n // }, 100);\n // }, 1000);\n // ```\n this.id = this.recycleAsyncId(this.scheduler, this.id, null);\n }\n }\n\n protected _execute(state: T, _delay: number): any {\n let errored: boolean = false;\n let errorValue: any;\n try {\n this.work(state);\n } catch (e) {\n errored = true;\n // HACK: Since code elsewhere is relying on the \"truthiness\" of the\n // return here, we can't have it return \"\" or 0 or false.\n // TODO: Clean this up when we refactor schedulers mid-version-8 or so.\n errorValue = e ? e : new Error('Scheduled action threw falsy error');\n }\n if (errored) {\n this.unsubscribe();\n return errorValue;\n }\n }\n\n unsubscribe() {\n if (!this.closed) {\n const { id, scheduler } = this;\n const { actions } = scheduler;\n\n this.work = this.state = this.scheduler = null!;\n this.pending = false;\n\n arrRemove(actions, this);\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, null);\n }\n\n this.delay = null!;\n super.unsubscribe();\n }\n }\n}\n", "import { Action } from './scheduler/Action';\nimport { Subscription } from './Subscription';\nimport { SchedulerLike, SchedulerAction } from './types';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * An execution context and a data structure to order tasks and schedule their\n * execution. Provides a notion of (potentially virtual) time, through the\n * `now()` getter method.\n *\n * Each unit of work in a Scheduler is called an `Action`.\n *\n * ```ts\n * class Scheduler {\n * now(): number;\n * schedule(work, delay?, state?): Subscription;\n * }\n * ```\n *\n * @deprecated Scheduler is an internal implementation detail of RxJS, and\n * should not be used directly. Rather, create your own class and implement\n * {@link SchedulerLike}. Will be made internal in v8.\n */\nexport class Scheduler implements SchedulerLike {\n public static now: () => number = dateTimestampProvider.now;\n\n constructor(private schedulerActionCtor: typeof Action, now: () => number = Scheduler.now) {\n this.now = now;\n }\n\n /**\n * A getter method that returns a number representing the current time\n * (at the time this function was called) according to the scheduler's own\n * internal clock.\n * @return A number that represents the current time. May or may not\n * have a relation to wall-clock time. May or may not refer to a time unit\n * (e.g. milliseconds).\n */\n public now: () => number;\n\n /**\n * Schedules a function, `work`, for execution. May happen at some point in\n * the future, according to the `delay` parameter, if specified. May be passed\n * some context object, `state`, which will be passed to the `work` function.\n *\n * The given arguments will be processed an stored as an Action object in a\n * queue of actions.\n *\n * @param work A function representing a task, or some unit of work to be\n * executed by the Scheduler.\n * @param delay Time to wait before executing the work, where the time unit is\n * implicit and defined by the Scheduler itself.\n * @param state Some contextual data that the `work` function uses when called\n * by the Scheduler.\n * @return A subscription in order to be able to unsubscribe the scheduled work.\n */\n public schedule(work: (this: SchedulerAction, state?: T) => void, delay: number = 0, state?: T): Subscription {\n return new this.schedulerActionCtor(this, work).schedule(state, delay);\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Action } from './Action';\nimport { AsyncAction } from './AsyncAction';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncScheduler extends Scheduler {\n public actions: Array> = [];\n /**\n * A flag to indicate whether the Scheduler is currently executing a batch of\n * queued actions.\n * @internal\n */\n public _active: boolean = false;\n /**\n * An internal ID used to track the latest asynchronous task such as those\n * coming from `setTimeout`, `setInterval`, `requestAnimationFrame`, and\n * others.\n * @internal\n */\n public _scheduled: TimerHandle | undefined;\n\n constructor(SchedulerAction: typeof Action, now: () => number = Scheduler.now) {\n super(SchedulerAction, now);\n }\n\n public flush(action: AsyncAction): void {\n const { actions } = this;\n\n if (this._active) {\n actions.push(action);\n return;\n }\n\n let error: any;\n this._active = true;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions.shift()!)); // exhaust the scheduler queue\n\n this._active = false;\n\n if (error) {\n while ((action = actions.shift()!)) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\n/**\n *\n * Async Scheduler\n *\n * Schedule task as if you used setTimeout(task, duration)\n *\n * `async` scheduler schedules tasks asynchronously, by putting them on the JavaScript\n * event loop queue. It is best used to delay tasks in time or to schedule tasks repeating\n * in intervals.\n *\n * If you just want to \"defer\" task, that is to perform it right after currently\n * executing synchronous code ends (commonly achieved by `setTimeout(deferredTask, 0)`),\n * better choice will be the {@link asapScheduler} scheduler.\n *\n * ## Examples\n * Use async scheduler to delay task\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * const task = () => console.log('it works!');\n *\n * asyncScheduler.schedule(task, 2000);\n *\n * // After 2 seconds logs:\n * // \"it works!\"\n * ```\n *\n * Use async scheduler to repeat task in intervals\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * function task(state) {\n * console.log(state);\n * this.schedule(state + 1, 1000); // `this` references currently executing Action,\n * // which we reschedule with new state and delay\n * }\n *\n * asyncScheduler.schedule(task, 3000, 0);\n *\n * // Logs:\n * // 0 after 3s\n * // 1 after 4s\n * // 2 after 5s\n * // 3 after 6s\n * ```\n */\n\nexport const asyncScheduler = new AsyncScheduler(AsyncAction);\n\n/**\n * @deprecated Renamed to {@link asyncScheduler}. Will be removed in v8.\n */\nexport const async = asyncScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { Subscription } from '../Subscription';\nimport { QueueScheduler } from './QueueScheduler';\nimport { SchedulerAction } from '../types';\nimport { TimerHandle } from './timerHandle';\n\nexport class QueueAction extends AsyncAction {\n constructor(protected scheduler: QueueScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (delay > 0) {\n return super.schedule(state, delay);\n }\n this.delay = delay;\n this.state = state;\n this.scheduler.flush(this);\n return this;\n }\n\n public execute(state: T, delay: number): any {\n return delay > 0 || this.closed ? super.execute(state, delay) : this._execute(state, delay);\n }\n\n protected requestAsyncId(scheduler: QueueScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n\n if ((delay != null && delay > 0) || (delay == null && this.delay > 0)) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n\n // Otherwise flush the scheduler starting with this action.\n scheduler.flush(this);\n\n // HACK: In the past, this was returning `void`. However, `void` isn't a valid\n // `TimerHandle`, and generally the return value here isn't really used. So the\n // compromise is to return `0` which is both \"falsy\" and a valid `TimerHandle`,\n // as opposed to refactoring every other instanceo of `requestAsyncId`.\n return 0;\n }\n}\n", "import { AsyncScheduler } from './AsyncScheduler';\n\nexport class QueueScheduler extends AsyncScheduler {\n}\n", "import { QueueAction } from './QueueAction';\nimport { QueueScheduler } from './QueueScheduler';\n\n/**\n *\n * Queue Scheduler\n *\n * Put every next task on a queue, instead of executing it immediately\n *\n * `queue` scheduler, when used with delay, behaves the same as {@link asyncScheduler} scheduler.\n *\n * When used without delay, it schedules given task synchronously - executes it right when\n * it is scheduled. However when called recursively, that is when inside the scheduled task,\n * another task is scheduled with queue scheduler, instead of executing immediately as well,\n * that task will be put on a queue and wait for current one to finish.\n *\n * This means that when you execute task with `queue` scheduler, you are sure it will end\n * before any other task scheduled with that scheduler will start.\n *\n * ## Examples\n * Schedule recursively first, then do something\n * ```ts\n * import { queueScheduler } from 'rxjs';\n *\n * queueScheduler.schedule(() => {\n * queueScheduler.schedule(() => console.log('second')); // will not happen now, but will be put on a queue\n *\n * console.log('first');\n * });\n *\n * // Logs:\n * // \"first\"\n * // \"second\"\n * ```\n *\n * Reschedule itself recursively\n * ```ts\n * import { queueScheduler } from 'rxjs';\n *\n * queueScheduler.schedule(function(state) {\n * if (state !== 0) {\n * console.log('before', state);\n * this.schedule(state - 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * console.log('after', state);\n * }\n * }, 0, 3);\n *\n * // In scheduler that runs recursively, you would expect:\n * // \"before\", 3\n * // \"before\", 2\n * // \"before\", 1\n * // \"after\", 1\n * // \"after\", 2\n * // \"after\", 3\n *\n * // But with queue it logs:\n * // \"before\", 3\n * // \"after\", 3\n * // \"before\", 2\n * // \"after\", 2\n * // \"before\", 1\n * // \"after\", 1\n * ```\n */\n\nexport const queueScheduler = new QueueScheduler(QueueAction);\n\n/**\n * @deprecated Renamed to {@link queueScheduler}. Will be removed in v8.\n */\nexport const queue = queueScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\nimport { SchedulerAction } from '../types';\nimport { animationFrameProvider } from './animationFrameProvider';\nimport { TimerHandle } from './timerHandle';\n\nexport class AnimationFrameAction extends AsyncAction {\n constructor(protected scheduler: AnimationFrameScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n protected requestAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay is greater than 0, request as an async action.\n if (delay !== null && delay > 0) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n // Push the action to the end of the scheduler queue.\n scheduler.actions.push(this);\n // If an animation frame has already been requested, don't request another\n // one. If an animation frame hasn't been requested yet, request one. Return\n // the current animation frame request id.\n return scheduler._scheduled || (scheduler._scheduled = animationFrameProvider.requestAnimationFrame(() => scheduler.flush(undefined)));\n }\n\n protected recycleAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle | undefined {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n if (delay != null ? delay > 0 : this.delay > 0) {\n return super.recycleAsyncId(scheduler, id, delay);\n }\n // If the scheduler queue has no remaining actions with the same async id,\n // cancel the requested animation frame and set the scheduled flag to\n // undefined so the next AnimationFrameAction will request its own.\n const { actions } = scheduler;\n if (id != null && id === scheduler._scheduled && actions[actions.length - 1]?.id !== id) {\n animationFrameProvider.cancelAnimationFrame(id as number);\n scheduler._scheduled = undefined;\n }\n // Return undefined so the action knows to request a new async id if it's rescheduled.\n return undefined;\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\nexport class AnimationFrameScheduler extends AsyncScheduler {\n public flush(action?: AsyncAction): void {\n this._active = true;\n // The async id that effects a call to flush is stored in _scheduled.\n // Before executing an action, it's necessary to check the action's async\n // id to determine whether it's supposed to be executed in the current\n // flush.\n // Previous implementations of this method used a count to determine this,\n // but that was unsound, as actions that are unsubscribed - i.e. cancelled -\n // are removed from the actions array and that can shift actions that are\n // scheduled to be executed in a subsequent flush into positions at which\n // they are executed within the current flush.\n let flushId;\n if (action) {\n flushId = action.id;\n } else {\n flushId = this._scheduled;\n this._scheduled = undefined;\n }\n\n const { actions } = this;\n let error: any;\n action = action || actions.shift()!;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions[0]) && action.id === flushId && actions.shift());\n\n this._active = false;\n\n if (error) {\n while ((action = actions[0]) && action.id === flushId && actions.shift()) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AnimationFrameAction } from './AnimationFrameAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\n\n/**\n *\n * Animation Frame Scheduler\n *\n * Perform task when `window.requestAnimationFrame` would fire\n *\n * When `animationFrame` scheduler is used with delay, it will fall back to {@link asyncScheduler} scheduler\n * behaviour.\n *\n * Without delay, `animationFrame` scheduler can be used to create smooth browser animations.\n * It makes sure scheduled task will happen just before next browser content repaint,\n * thus performing animations as efficiently as possible.\n *\n * ## Example\n * Schedule div height animation\n * ```ts\n * // html:
\n * import { animationFrameScheduler } from 'rxjs';\n *\n * const div = document.querySelector('div');\n *\n * animationFrameScheduler.schedule(function(height) {\n * div.style.height = height + \"px\";\n *\n * this.schedule(height + 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * }, 0, 0);\n *\n * // You will see a div element growing in height\n * ```\n */\n\nexport const animationFrameScheduler = new AnimationFrameScheduler(AnimationFrameAction);\n\n/**\n * @deprecated Renamed to {@link animationFrameScheduler}. Will be removed in v8.\n */\nexport const animationFrame = animationFrameScheduler;\n", "import { Observable } from '../Observable';\nimport { SchedulerLike } from '../types';\n\n/**\n * A simple Observable that emits no items to the Observer and immediately\n * emits a complete notification.\n *\n * Just emits 'complete', and nothing else.\n *\n * ![](empty.png)\n *\n * A simple Observable that only emits the complete notification. It can be used\n * for composing with other Observables, such as in a {@link mergeMap}.\n *\n * ## Examples\n *\n * Log complete notification\n *\n * ```ts\n * import { EMPTY } from 'rxjs';\n *\n * EMPTY.subscribe({\n * next: () => console.log('Next'),\n * complete: () => console.log('Complete!')\n * });\n *\n * // Outputs\n * // Complete!\n * ```\n *\n * Emit the number 7, then complete\n *\n * ```ts\n * import { EMPTY, startWith } from 'rxjs';\n *\n * const result = EMPTY.pipe(startWith(7));\n * result.subscribe(x => console.log(x));\n *\n * // Outputs\n * // 7\n * ```\n *\n * Map and flatten only odd numbers to the sequence `'a'`, `'b'`, `'c'`\n *\n * ```ts\n * import { interval, mergeMap, of, EMPTY } from 'rxjs';\n *\n * const interval$ = interval(1000);\n * const result = interval$.pipe(\n * mergeMap(x => x % 2 === 1 ? of('a', 'b', 'c') : EMPTY),\n * );\n * result.subscribe(x => console.log(x));\n *\n * // Results in the following to the console:\n * // x is equal to the count on the interval, e.g. (0, 1, 2, 3, ...)\n * // x will occur every 1000ms\n * // if x % 2 is equal to 1, print a, b, c (each on its own)\n * // if x % 2 is not equal to 1, nothing will be output\n * ```\n *\n * @see {@link Observable}\n * @see {@link NEVER}\n * @see {@link of}\n * @see {@link throwError}\n */\nexport const EMPTY = new Observable((subscriber) => subscriber.complete());\n\n/**\n * @param scheduler A {@link SchedulerLike} to use for scheduling\n * the emission of the complete notification.\n * @deprecated Replaced with the {@link EMPTY} constant or {@link scheduled} (e.g. `scheduled([], scheduler)`). Will be removed in v8.\n */\nexport function empty(scheduler?: SchedulerLike) {\n return scheduler ? emptyScheduled(scheduler) : EMPTY;\n}\n\nfunction emptyScheduled(scheduler: SchedulerLike) {\n return new Observable((subscriber) => scheduler.schedule(() => subscriber.complete()));\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport function isScheduler(value: any): value is SchedulerLike {\n return value && isFunction(value.schedule);\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\nimport { isScheduler } from './isScheduler';\n\nfunction last(arr: T[]): T | undefined {\n return arr[arr.length - 1];\n}\n\nexport function popResultSelector(args: any[]): ((...args: unknown[]) => unknown) | undefined {\n return isFunction(last(args)) ? args.pop() : undefined;\n}\n\nexport function popScheduler(args: any[]): SchedulerLike | undefined {\n return isScheduler(last(args)) ? args.pop() : undefined;\n}\n\nexport function popNumber(args: any[], defaultValue: number): number {\n return typeof last(args) === 'number' ? args.pop()! : defaultValue;\n}\n", "export const isArrayLike = ((x: any): x is ArrayLike => x && typeof x.length === 'number' && typeof x !== 'function');", "import { isFunction } from \"./isFunction\";\n\n/**\n * Tests to see if the object is \"thennable\".\n * @param value the object to test\n */\nexport function isPromise(value: any): value is PromiseLike {\n return isFunction(value?.then);\n}\n", "import { InteropObservable } from '../types';\nimport { observable as Symbol_observable } from '../symbol/observable';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being Observable (but not necessary an Rx Observable) */\nexport function isInteropObservable(input: any): input is InteropObservable {\n return isFunction(input[Symbol_observable]);\n}\n", "import { isFunction } from './isFunction';\n\nexport function isAsyncIterable(obj: any): obj is AsyncIterable {\n return Symbol.asyncIterator && isFunction(obj?.[Symbol.asyncIterator]);\n}\n", "/**\n * Creates the TypeError to throw if an invalid object is passed to `from` or `scheduled`.\n * @param input The object that was passed.\n */\nexport function createInvalidObservableTypeError(input: any) {\n // TODO: We should create error codes that can be looked up, so this can be less verbose.\n return new TypeError(\n `You provided ${\n input !== null && typeof input === 'object' ? 'an invalid object' : `'${input}'`\n } where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.`\n );\n}\n", "export function getSymbolIterator(): symbol {\n if (typeof Symbol !== 'function' || !Symbol.iterator) {\n return '@@iterator' as any;\n }\n\n return Symbol.iterator;\n}\n\nexport const iterator = getSymbolIterator();\n", "import { iterator as Symbol_iterator } from '../symbol/iterator';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being an Iterable */\nexport function isIterable(input: any): input is Iterable {\n return isFunction(input?.[Symbol_iterator]);\n}\n", "import { ReadableStreamLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport async function* readableStreamLikeToAsyncGenerator(readableStream: ReadableStreamLike): AsyncGenerator {\n const reader = readableStream.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n return;\n }\n yield value!;\n }\n } finally {\n reader.releaseLock();\n }\n}\n\nexport function isReadableStreamLike(obj: any): obj is ReadableStreamLike {\n // We don't want to use instanceof checks because they would return\n // false for instances from another Realm, like an