diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7744325..f7f96c8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,8 +46,8 @@ pixi run -e mojo mojo run opengateway/mojo/main.mojo OpenGateway ships **two HTTP servers** that implement the same OpenAI-compatible contract: -1. **FastAPI server** (`opengateway/main.py`) — default, Python, asyncio. -2. **Mojo + flare server** (`opengateway/mojo/main.mojo`) — opt-in, Mojo, static binary. +1. **Mojo + flare server** (`opengateway/mojo/main.mojo`), default, Mojo, static binary. +2. **FastAPI server** (`opengateway/main.py`), opt-in dev path, Python, asyncio. Both delegate provider calls to the same Python adapters (`opengateway/providers/`). The Mojo server uses the PythonObject @@ -64,6 +64,41 @@ See [docs/architecture.md](docs/architecture.md) and [ADR-002](adr/002-mojo-api-surface.md) for the full layering rationale. +### Repository layout + +``` +opengateway/ +├── opengateway/ +│ ├── main.py # FastAPI server (opt-in dev path) +│ ├── auth.py # Virtual key + root key auth +│ ├── config.py # Settings via pydantic-settings (RouteRule, ROUTES_JSON) +│ ├── keys.py # API key generator (sk-og-{token}) +│ ├── router.py # Model-to-provider routing (Python) +│ ├── providers/ # Provider adapters (called by the bridge) +│ │ ├── base.py +│ │ ├── openai.py +│ │ └── anthropic.py # stub, falls back from OpenAI when configured +│ ├── mojo/ # Mojo server on flare, default deployment +│ │ ├── main.mojo # flare HTTP server + middleware stack +│ │ ├── router.mojo # Model to provider module routing +│ │ ├── test_router.mojo +│ │ └── __init__.mojo +│ └── mojo_bridge/ # Python side of the Mojo bridge +│ ├── auth.py +│ ├── chat.py +│ ├── stream.py +│ ├── db.py # Postgres virtual key store +│ ├── ratelimit.py # Redis sliding-window rate limiter +│ └── routing.py # ROUTES_JSON resolution + fallback orchestration +├── tests/ # pytest suite + mock_upstream.py +├── docs/ # architecture.md, configuration.md, release-process.md +├── adr/ # Architecture Decision Records +├── pyproject.toml # Python package config +├── pixi.toml # Mojo environment config +├── Dockerfile # Container build +└── docker-compose.yml # Local dev stack (Postgres + Redis + gateway) +``` + ## Pull Request Process 1. Ensure your code passes all tests and linting (`ruff`, `mypy`, diff --git a/README.md b/README.md index 9924a2e..c6b9d32 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@
-OpenGateway, the AI gateway that doesn't paywall the features +OpenGateway, the AI gateway that doesn't paywall the features **The fastest way to add SSO, audit logs, guardrails, and routing to any AI app.** OpenAI-compatible. Every feature free. MIT, forever. -[Docs](docs/architecture.md) · [Quick Start](#quick-start) · [Drop-in Replacement](#drop-in-replacement) · [Providers](#providers) · [ADRs](adr/) +[Docs](docs/architecture.md) · [Quick Start](#quick-start) · [Drop-in Replacement](#drop-in-replacement) · [Providers](#providers) · [Configuration](docs/configuration.md) · [ADRs](adr/) | | | | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | @@ -32,9 +32,9 @@ Every AI gateway on the market takes the same bet: **lock the good stuff behind LiteLLM charges for SSO and audit logs. Bifrost gates guardrails and clustering behind enterprise. **OpenGateway gives you everything in the OSS build**, funded by managed hosting and support, the same model Red Hat used with Backstage. -> "Bifrost is Go. OpenGateway is Python/Mojo, easier to customise." internal positioning line. +> "Bifrost is Go. OpenGateway is Python/Mojo, easier to customise." -And unlike every other Python AI gateway, OpenGateway ships a **second server on Mojo + [flare](https://github.com/ehsanmok/flare)** for when you want a single static binary at the edge. +And unlike every other Python AI gateway, OpenGateway ships a **second server on Mojo + [flare](https://github.com/ehsanmok/flare)**: one static binary for the edge. --- @@ -65,7 +65,7 @@ $ opengateway INFO: Uvicorn running on http://0.0.0.0:8080 ``` -Both servers implement the same `POST /v1/chat/completions` endpoint and share the same provider adapters. Production ships the Mojo binary; FastAPI stays in-tree as the Python-only dev path. See `docs/architecture.md` for the full story. +Both servers implement the same `POST /v1/chat/completions` endpoint and share the same provider adapters. Production ships the Mojo binary; FastAPI stays in-tree as the Python-only dev path. See [docs/architecture.md](docs/architecture.md) for the full story. ### Hit it @@ -83,54 +83,7 @@ $ curl -s -X POST http://localhost:8080/v1/chat/completions \ "Hello, Hola, Bonjour, Hallo, こんにちは" ``` ---- - -### Demo without an API key (mock upstream) - -No OpenAI key? The repo ships a mock SSE upstream so the full path -(gateway → provider → streaming client) runs offline: - -```bash -$ python tests/mock_upstream.py 9100 & -mock upstream on 127.0.0.1:9100 -``` - -Then set these in `.env` instead of a real key: - -```bash -OPENAI_API_KEY=sk-mock-local -OPENAI_BASE_URL=http://127.0.0.1:9100/v1 -``` - -Restart the gateway and hit `/v1/chat/completions` as above — the mock -replies `"Hello, streaming world!"`, or four SSE frames when -`"stream": true`. Works with both servers. - ---- - -### TLS termination (in-binary) - -Set `TLS_CERT_FILE` and `TLS_KEY_FILE` (PEM-encoded) to terminate TLS -inside the Mojo binary via flare's reactor-side TLS state machine — -no sidecar, no edge LB. When unset, the listener accepts cleartext on -the loopback address so an LB can terminate TLS in front. The -`TLS_PORT` env var is unused — the listener binds on `PORT`. - -```bash -$ TLS_CERT_FILE=./server.pem TLS_KEY_FILE=./server.key ./og-tls3 -opengateway (mojo): HTTPS on 127.0.0.1:8080 with 1 workers - -$ curl --cacert ./server.pem --resolve localhost:8080:127.0.0.1 \ - https://localhost:8080/health -{"status":"ok"} -``` - -> **Self-signed cert gotcha.** The in-binary TLS path requires the cert -> subject to match the hostname curl uses. Generate the cert with -> `CN=localhost` (not `CN=127.0.0.1`) and use `--resolve -> localhost:8080:127.0.0.1` so the cert verifies. Hitting -> `https://127.0.0.1:8080/health` directly will exit code 60 silently -> under `curl -s` and look like a hang. +No API key? Run the bundled mock upstream and exercise the full path offline ([docs/configuration.md](docs/configuration.md)). --- @@ -160,11 +113,10 @@ Every OpenAI SDK works without modification. Anthropic, Google GenAI, LiteLLM, a ### Core Infrastructure -- **OpenAI-compatible endpoint.** Same request shape, same response shape, same error format. Drop-in for every OpenAI client SDK. +- **OpenAI-compatible endpoint.** Same request shape, same response shape, same error format. - **Multi-provider support.** OpenAI, Anthropic, AWS Bedrock, Google Vertex, Azure, and more, behind a single API. - **Automatic fallbacks.** Seamless failover between providers and models with zero downtime. -- **Load balancing.** Intelligent request distribution across multiple API keys and providers. -- **Streaming (SSE).** Server-sent events work out of the box, same wire format as OpenAI. +- **Streaming (SSE).** Server-sent events out of the box, same wire format as OpenAI. ### Governance and Security @@ -172,22 +124,14 @@ Every OpenAI SDK works without modification. Anthropic, Google GenAI, LiteLLM, a - **Per-key budgets and rate limits.** Token-bucket rate limiting and USD budget caps per virtual key. - **Audit logs.** Structured events for every request, queryable by key, model, and time range. - **SSO / SAML.** OIDC and SAML authentication for admin interfaces and key management. -- **RBAC.** Team, organisation, and admin role hierarchy. -- **IP ACLs.** Restrict access to specific source IPs or CIDR ranges. -- **Custom branding.** Logo, colours, and per-tenant theming on the admin UI. +- **RBAC, IP ACLs, custom branding.** Team hierarchies, source-IP restrictions, per-tenant theming. -### Observability +### Observability and Developer Experience - **Native Prometheus metrics.** Request counts, latency histograms, error rates, token usage. -- **Distributed tracing.** OpenTelemetry-compatible export to Jaeger, Tempo, or Honeycomb. - **Structured request logging.** JSON logs with key, model, latency, tokens, and cost. - -### Developer Experience - - **Zero-config startup.** Single command, single binary, no database required to start. -- **Single static binary** (Mojo path) for serverless, edge, and Lambda deployments. -- **Conventional commits** drive automated versioning and changelog generation via release-please. -- **Standard formats everywhere.** OpenAPI for the spec, JSON for the config, dotenv for the env. +- **Standard formats everywhere.** OpenAPI for the spec, JSON for config, dotenv for env. --- @@ -206,137 +150,18 @@ Every OpenAI SDK works without modification. Anthropic, Google GenAI, LiteLLM, a --- -## Integrations - -OpenGateway is a drop-in replacement for any OpenAI-compatible client. Tested with: - -| SDK | Status | Notes | -|---|---|---| -| [openai-python](https://github.com/openai/openai-python) | works | Set `base_url` to the gateway URL | -| [openai-node](https://github.com/openai/openai-node) | works | Set `baseURL` to the gateway URL | -| [anthropic-sdk-python](https://github.com/anthropics/anthropic-sdk-python) | works | Via provider router | -| [Google GenAI](https://github.com/google-gemini/generative-ai-python) | works | Via provider router | -| [LiteLLM SDK](https://github.com/BerriAI/litellm) | works | Nested routing for migration | -| [LangChain](https://github.com/langchain-ai/langchain) | works | Use OpenAI-compatible endpoint | - ---- - -## Routing, Cost Caps, and Fallbacks - -Three layered controls for traffic shape. All opt-in: with no `ROUTES_JSON` set, the bridge behaves identically to a pre-routing-table install. - -### Routing table (`ROUTES_JSON`) - -Pin a model to a primary provider with optional fallback, plus pricing for the cost cap: - -```bash -ROUTES_JSON='[ - { - "model": "gpt-4o-mini", - "primary": "openai", - "input_price_per_1k": 0.00015, - "output_price_per_1k": 0.0006, - "max_output_tokens": 16384 - }, - { - "model": "gpt-4o", - "primary": "openai", - "fallback": "anthropic", - "input_price_per_1k": 0.0025, - "output_price_per_1k": 0.01, - "max_output_tokens": 4096 - } -]' -``` - -- `model`: exact match against the request body. -- `primary` / `fallback`: short provider name (`openai`, `anthropic`) or full module path (`opengateway.providers.openai`). -- Pricing fields are USD per 1k tokens. They drive the per-key `max_cost_usd` cap (below); if unset, the cap is not enforced. -- `max_output_tokens`: ceiling used when the request omits `max_tokens`. Caps the worst-case completion cost. - -When `ROUTES_JSON` is unset, the bridge falls through to the legacy prefix-based routing (`gpt-*` → OpenAI, `claude-*` → Anthropic, `bedrock/*` → Bedrock). Drift between the Python bridge and the Mojo router is guarded by `tests/test_mojo_bridge.py::test_routing_rules_match_mojo_router`. - -### Per-key USD cost cap - -Set `max_cost_usd` on a virtual key (column added to the `virtual_keys` table). The bridge estimates the upstream cost of each request before calling the provider: - -``` -estimate = prompt_chars / 4 × input_price + max_tokens × output_price -``` - -When `budget_used + estimate > max_cost_usd`, the request is rejected with `429 rate_limit_error` before any upstream call. Over-estimation is the right bias for a cap. The cap is independent from the token-denominated `max_budget` field (ADR-003 §166) — set both, set either, set neither. - -### Provider fallback - -When the primary provider returns a retryable failure (`5xx`, `429`, connection / timeout), the bridge retries the request against `fallback` once. Non-retryable failures (4xx other than 429) surface immediately — the request is broken, not the provider. - -Streaming falls back only before the first frame is queued. Mid-stream errors log a warning and emit the canonical `[DONE]` terminator; the client sees a clean stream end, not a silent provider swap mid-flight. - ---- - -## Configuration - -Everything is environment variables or `.env`: - -| Variable | Default | Description | -|---|---|---| -| `ROOT_KEY` | `sk-root-change-me` | Admin key with full access. **Replace before deploying.** | -| `OPENAI_API_KEY` | _(unset)_ | Upstream key for `gpt-*` and `openai/*`. | -| `ANTHROPIC_API_KEY` | _(unset)_ | Upstream key for `claude-*` and `anthropic/*`. | -| `ROUTES_JSON` | _(unset)_ | JSON array of `RouteRule` objects — see [Routing, Cost Caps, and Fallbacks](#routing-cost-caps-and-fallbacks). | -| `DATABASE_URL` | `postgresql://...` | Tenants, keys, audit logs. | -| `REDIS_URL` | `redis://...` | Rate limits and short-lived caches. | -| `HOST` | `0.0.0.0` | Bind address. | -| `PORT` | `8080` | Bind port. | -| `WORKERS` | `1` | uvicorn workers (Python only). | -| `DEBUG` | `false` | Reload on file changes. | -| `LOG_LEVEL` | `INFO` | `DEBUG` / `INFO` / `WARNING` / `ERROR`. | -| `REQUIRE_AUTH` | `true` | Reject requests without a valid `Authorization` header. | - -API keys follow [`sk-og-{token}`](adr/001-api-key-format.md), the same prefix shape as OpenAI, branded to OpenGateway. - ---- - ## Architecture +```mermaid +flowchart TD + client["OpenAI-compatible client"] --> api["HTTP API surface
flare on Mojo, default
or FastAPI, Python, opt-in"] + api --> bridge["PythonObject bridge
only in the Mojo path"] + bridge -->|"single sync function call
returns envelope dict"| logic["Python business logic
auth, validation, provider dispatch"] + logic --> adapters["Provider adapters
opengateway/providers/{openai,anthropic,bedrock,...}.py"] + adapters --> upstream["Upstream LLM API
OpenAI / Anthropic / Bedrock / ..."] ``` - OpenAI-compatible client - │ - ▼ - ┌────────────────────┐ - │ HTTP API surface │ ← FastAPI (Python, default) - │ │ OR flare (Mojo, opt-in) - └─────────┬──────────┘ - │ - ▼ - ┌────────────────────┐ - │ PythonObject │ ← only in the Mojo path - │ bridge │ - └─────────┬──────────┘ - │ - ═╪═ single sync function call, returns envelope - ═╪═ - │ - ▼ - ┌────────────────────┐ - │ Python business │ ← auth, validation, provider dispatch - │ logic │ - └─────────┬──────────┘ - │ - ▼ - ┌────────────────────┐ - │ Provider adapters │ ← opengateway/providers/{openai,anthropic,bedrock,...}.py - └─────────┬──────────┘ - │ - ▼ - ┌────────────────────┐ - │ Upstream LLM API │ ← OpenAI / Anthropic / Bedrock / ... - └────────────────────┘ -``` - -**Mojo on flare** is the default. Single static binary, sub-50 ms cold start, ~30 MB image, no `pip install` in your container. Hosts the entire HTTP surface — routing, middleware, SSE, request-id propagation, structured logging, panic safety, graceful drain — natively. -**FastAPI** stays in-tree as the Python-only dev path (`uv run opengateway`). Use it for local iteration on the bridge or when you need a debugger attached to the Python stack. It is no longer the production deployment target. +**Mojo on flare** is the default. Single static binary, sub-50 ms cold start, ~30 MB image, no `pip install` in your container. It hosts the entire HTTP surface natively: routing, middleware, SSE, request-id propagation, structured logging, panic safety, graceful drain. **FastAPI** stays in-tree as the Python-only dev path for local iteration on the bridge or when you need a debugger attached to the Python stack. Both servers share the same provider adapters, the same auth, the same config. The Mojo to PythonObject boundary is one synchronous function call (`bridge.handle_chat`) that returns an envelope dict so the Mojo handler never catches Python exceptions. @@ -344,50 +169,6 @@ Full layout in [docs/architecture.md](docs/architecture.md); the decision to mak --- -## Repository Structure - -``` -opengateway/ -├── opengateway/ -│ ├── main.py # FastAPI server (opt-in dev path) -│ ├── auth.py # Virtual key + root key auth -│ ├── config.py # Settings via pydantic-settings (RouteRule, ROUTES_JSON) -│ ├── keys.py # API key generator (sk-og-{token}) -│ ├── router.py # Model-to-provider routing (Python) -│ ├── providers/ # Provider adapters (called by the bridge) -│ │ ├── base.py -│ │ ├── openai.py -│ │ └── anthropic.py # stub — falls back from OpenAI when configured -│ ├── mojo/ # Mojo server on flare — default deployment -│ │ ├── main.mojo # flare HTTP server + middleware stack -│ │ ├── router.mojo # Model -> provider module routing -│ │ ├── test_router.mojo -│ │ └── __init__.mojo -│ └── mojo_bridge/ # Python side of the Mojo bridge -│ ├── auth.py -│ ├── chat.py -│ ├── stream.py -│ ├── db.py # Postgres virtual key store -│ ├── ratelimit.py # Redis sliding-window rate limiter -│ └── routing.py # ROUTES_JSON resolution + fallback orchestration -├── tests/ -│ ├── test_proxy.py # FastAPI server tests (still in-tree) -│ ├── test_mojo_bridge.py # Bridge tests + router drift guard -│ ├── test_cost_cap.py # per-key max_cost_usd enforcement -│ └── test_provider_fallback.py # retryable-failure fallback -├── docs/ # Documentation -│ ├── architecture.md -│ ├── release-process.md -│ └── assets/ # Banner image and other assets -├── adr/ # Architecture Decision Records -├── pyproject.toml # Python package config -├── pixi.toml # Mojo environment config -├── Dockerfile # Container build -└── docker-compose.yml # Local dev stack (Postgres + Redis + gateway) -``` - ---- - ## Philosophy A few principles we hold ourselves to. They're non-negotiable. @@ -396,54 +177,20 @@ A few principles we hold ourselves to. They're non-negotiable. 2. **OpenAI-compatible is the API contract.** Not "compatible-ish". Not "subset". The same request shape, the same response shape, the same error format. 3. **Boring tech where it matters.** FastAPI, Postgres, Redis, Pydantic. We don't get bonus points for picking weird. 4. **New tech where it pays off.** Mojo for the binary-deploy path. Conventional commits for release automation. Standard formats everywhere else. -5. **Tests in CI, not in promises.** 56 Python tests today, more every week. No `// TODO: test this later` in main. +5. **Tests in CI, not in promises.** No `// TODO: test this later` in main. 6. **The README is a contract.** If it doesn't run as written, the docs are wrong, not the code. --- -## Roadmap - -Shipped today: - -- [x] OpenAI-compatible `/v1/chat/completions` -- [x] Virtual keys with model allow-lists -- [x] Per-key token budgets (`max_budget`) and USD cost caps (`max_cost_usd`) -- [x] Routing table with primary + fallback providers (`ROUTES_JSON`) -- [x] Single-hop provider fallback on retryable failures -- [x] OpenAI provider adapter -- [x] Dual server: FastAPI + Mojo on flare -- [x] Streaming SSE in the Mojo server -- [x] Prometheus metrics endpoint at `/metrics` -- [x] Per-key Redis-backed rate limiting (sliding window) -- [x] PostgreSQL-backed virtual keys (in-memory fallback when `DATABASE_URL` unset) -- [x] release-please to PyPI publishing (calver `YYYY.M.PATCH`) -- [x] In-binary TLS termination via flare's reactor (`TLS_CERT_FILE`, `TLS_KEY_FILE`) -- [x] Drop-in replacement for openai-python, openai-node, and Anthropic SDKs - -Next up: - -- [ ] **Anthropic provider** adapter (currently a stub; routed via `fallback: anthropic`) -- [ ] **Bedrock pass-through** adapter -- [ ] **Guardrails**: PII detection, prompt injection, content moderation -- [ ] **Audit log**: structured events, queryable -- [ ] **SSO**: OIDC + SAML -- [ ] **Adaptive routing**: score-based provider selection -- [ ] **HTTP/3** over QUIC (blocked on upstream flare reactor integration) - -Long term: - -- [ ] Managed SaaS, the Phase 3 from the strategy note -- [ ] Enterprise support contracts, the Phase 4 from the strategy note - ---- - ## Documentation | Doc | What it covers | |---|---| | [docs/architecture.md](docs/architecture.md) | Runtime layout, dual-server design, Mojo to Python boundary | +| [docs/configuration.md](docs/configuration.md) | Env vars, routing table, cost caps, fallbacks, TLS, mock upstream | | [docs/release-process.md](docs/release-process.md) | release-please flow, conventional commits, PyPI trusted publisher | | [docs/mojo-python-ai-gateway.md](docs/mojo-python-ai-gateway.md) | Original design sketch (historical rationale) | +| [docs/assets/logo-explore](docs/assets/logo-explore/) | Brand assets: mark and lockup SVGs, banner source, logo animation | | [adr/001-api-key-format.md](adr/001-api-key-format.md) | The `sk-og-{token}` key format | | [adr/002-mojo-api-surface.md](adr/002-mojo-api-surface.md) | Why Mojo for the API surface | | [CONTRIBUTING.md](CONTRIBUTING.md) | Dev setup, commit conventions, PR process | @@ -455,27 +202,16 @@ Long term: We accept contributions under [DCO](./CONTRIBUTING.md). Commits follow [Conventional Commits](https://www.conventionalcommits.org/). `release-please` uses your commit messages to drive the version bump and the changelog, so prefix your commits with `feat:`, `fix:`, `docs:`, etc. ```bash -# Set up -git clone https://github.com/echohello-dev/opengateway.git -cd opengateway -uv pip install -e ".[dev]" - -# Run everything make test # pytest make lint # ruff + mypy -make format # ruff format make mojo-test # Mojo router tests (requires pixi) - -# Open a PR with a clear title and description ``` --- ## Project Status -**Alpha (0.x).** The core proxy works end-to-end with the OpenAI provider. Anthropic and Bedrock adapters are routed but not yet implemented. Virtual keys and budgets are scaffolded. DB-backed persistence is the next milestone. Expect breaking changes before 1.0. - -Watch [releases](https://github.com/echohello-dev/opengateway/releases) for tagged versions, and check the [open issues](https://github.com/echohello-dev/opengateway/issues) for the current roadmap. +OpenGateway is alpha (0.x). The core proxy works end-to-end with the OpenAI provider; Anthropic and Bedrock adapters are routed but not yet implemented, and expect breaking changes before 1.0. Watch [releases](https://github.com/echohello-dev/opengateway/releases) for tagged versions, and check the [open issues](https://github.com/echohello-dev/opengateway/issues) for the current picture. --- diff --git a/docs/architecture.md b/docs/architecture.md index 1fcfc98..fce3679 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -10,38 +10,14 @@ them. ## High-level request flow -``` - ┌─────────────────────┐ - OpenAI-compatible client │ POST /v1/chat/... │ - (curl, openai-python, │ Bearer │ - └─────────┬───────────┘ - │ - ┌─────────────────────┴─────────────────────┐ - │ │ - ┌───────▼────────┐ ┌───────▼────────┐ - │ FastAPI app │ │ flare server │ - │ (opengateway │ │ (opengateway │ - │ .main) │ │ .mojo.main) │ - │ │ │ │ - │ Python, │ │ Mojo, static │ - │ uvicorn, │ │ binary or │ - │ asyncio │ │ pixi run │ - └───────┬────────┘ └───────┬────────┘ - │ │ - │ ┌────────────────────────────────┐ │ - └─────►│ opengateway.providers.* │◄──┘ - │ (Python adapters, async) │ - │ - openai.py (existing) │ - │ - anthropic.py (planned) │ - │ - bedrock.py (planned) │ - └────────────────┬───────────────┘ - │ - ┌─────────▼──────────┐ - │ Upstream LLM API │ - │ (OpenAI / Anthrop │ - │ ic / Bedrock...) │ - └────────────────────┘ +```mermaid +flowchart TD + client["OpenAI-compatible client
curl, openai-python, SDK, internal app"] + client -->|"POST /v1/chat/completions
Bearer sk-og-token"| fastapi["FastAPI app
opengateway.main
Python, uvicorn, asyncio"] + client -->|"POST /v1/chat/completions
Bearer sk-og-token"| flare["flare server
opengateway.mojo.main
Mojo, static binary or pixi run"] + fastapi --> providers["opengateway.providers.*
Python adapters, async
openai.py shipped
anthropic.py, bedrock.py planned"] + flare --> providers + providers --> upstream["Upstream LLM API
OpenAI / Anthropic / Bedrock"] ``` Both servers implement the same endpoint contract, share the same @@ -109,25 +85,18 @@ Python (`bridge_module.handle_chat(payload, auth_header, provider_module)`). It returns an envelope dict `{"status": , "body": }` so the Mojo handler never has to catch Python exceptions itself. -``` -Mojo handler Python bridge -───────────── ────────────── -chat_completions(req) handle_chat(body, auth, module) - │ │ - │ req.text() │ authenticate_authorization() - │ req.headers.get("authorization") │ _validate_request(body) - │ │ _enforce_model_access(...) - │ │ _enforce_budget(...) - │ │ asyncio.run(_run_completion(...)) - │ │ └─► provider.chat(...) - │ │ - │ json_loads(body_text) │ - │ bridge.handle_chat(payload, ...) │ - │ │ - │ ◄──────────── envelope dict ────────│ - │ - ▼ -ok_json(body_json) +```mermaid +sequenceDiagram + participant M as Mojo handler + participant P as Python bridge + M->>M: req.text() and headers.get("authorization") + M->>P: handle_chat(payload, auth_header, provider_module) + Note over P: authenticate_authorization() + Note over P: _validate_request(body) + Note over P: _enforce_model_access(...) and _enforce_budget(...) + Note over P: asyncio.run(_run_completion(...)) calls provider.chat(...) + P-->>M: envelope dict with status and body + M->>M: json_loads(body_text), ok_json(body_json) ``` The Mojo layer never calls `httpx`, `asyncpg`, or any async Python @@ -185,46 +154,12 @@ two servers. ## Deployment shapes -``` -┌────────────────────────────┐ -│ Local dev (Python default) │ -│ │ -│ uv run opengateway │ -│ (uses opengateway.main │ -│ FastAPI app on :8080) │ -└────────────────────────────┘ - -┌────────────────────────────┐ -│ Edge / serverless │ -│ │ -│ pixi run -e mojo mojo │ -│ build main.mojo -O3 │ -│ ./main │ -│ (static binary, no venv, │ -│ 15–80 MB) │ -└────────────────────────────┘ - -┌────────────────────────────┐ -│ Docker (default) │ -│ │ -│ docker compose up │ -│ (FastAPI + Postgres + │ -│ Redis via Compose) │ -└────────────────────────────┘ - -┌────────────────────────────┐ -│ Docker (Mojo variant) │ -│ │ -│ pixi run -e mojo mojo │ -│ build main.mojo │ -│ docker build -f │ -│ Dockerfile.mojo . │ -│ (multi-stage: │ -│ modular image → static │ -│ binary → distroless │ -│ final image, ~30 MB) │ -└────────────────────────────┘ -``` +| Shape | Command | Notes | +|---|---|---| +| **Local dev (Python default)** | `uv run opengateway` | FastAPI app on :8080 | +| **Edge / serverless** | `pixi run -e mojo mojo build main.mojo -O3` then `./main` | Static binary, no venv, 15-80 MB | +| **Docker (default)** | `docker compose up` | FastAPI + Postgres + Redis via Compose | +| **Docker (Mojo variant)** | `pixi run -e mojo mojo build main.mojo` then `docker build -f Dockerfile.mojo .` | Multi-stage: modular image to static binary to distroless, ~30 MB | See `docs/release-process.md` for the full release flow and `docs/mojo-python-ai-gateway.md` for the design rationale. diff --git a/docs/assets/banner.gif b/docs/assets/banner.gif new file mode 100644 index 0000000..d45d63a Binary files /dev/null and b/docs/assets/banner.gif differ diff --git a/docs/assets/banner.png b/docs/assets/banner.png index 292d318..0d52ad0 100644 Binary files a/docs/assets/banner.png and b/docs/assets/banner.png differ diff --git a/docs/assets/logo-explore/banner.anim.svg b/docs/assets/logo-explore/banner.anim.svg new file mode 100644 index 0000000..e178c57 --- /dev/null +++ b/docs/assets/logo-explore/banner.anim.svg @@ -0,0 +1 @@ +OpenGateway \ No newline at end of file diff --git a/docs/assets/logo-explore/banner.svg b/docs/assets/logo-explore/banner.svg new file mode 100644 index 0000000..d695f65 --- /dev/null +++ b/docs/assets/logo-explore/banner.svg @@ -0,0 +1 @@ +OpenGateway \ No newline at end of file diff --git a/docs/assets/logo-explore/logo.dark.svg b/docs/assets/logo-explore/logo.dark.svg new file mode 100644 index 0000000..1a5e3a3 --- /dev/null +++ b/docs/assets/logo-explore/logo.dark.svg @@ -0,0 +1 @@ +OpenGateway \ No newline at end of file diff --git a/docs/assets/logo-explore/logo.gif b/docs/assets/logo-explore/logo.gif new file mode 100644 index 0000000..2baddfc Binary files /dev/null and b/docs/assets/logo-explore/logo.gif differ diff --git a/docs/assets/logo-explore/logo.light.svg b/docs/assets/logo-explore/logo.light.svg new file mode 100644 index 0000000..d0f8ba8 --- /dev/null +++ b/docs/assets/logo-explore/logo.light.svg @@ -0,0 +1 @@ +OpenGateway \ No newline at end of file diff --git a/docs/assets/logo-explore/logo.py b/docs/assets/logo-explore/logo.py new file mode 100644 index 0000000..64fdc0f --- /dev/null +++ b/docs/assets/logo-explore/logo.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +"""OpenGateway logo assets: the round-4 foot-plain mark, the Outfit SemiBold lockup, +and the slim README banner (static + animated). +Run: python3 logo.py (needs motion/wordmarks.json from wordmark.py + and motion/svg/m-connect.dark.svg from motion.py) +Writes mark.{dark,light}.svg, logo.{dark,light}.svg, banner.svg and +banner.anim.svg next to itself. + magick banner.svg ../banner.png # slim static banner +The animated banner GIF is captured from banner.anim.svg in a browser +(CSS animations do not rasterise) and encoded with ffmpeg. +""" +import json, os, re + +HERE = os.path.dirname(os.path.abspath(__file__)) +WM = json.load(open(os.path.join(HERE, "motion", "wordmarks.json"))) +CONNECT = open(os.path.join(HERE, "motion", "svg", "m-connect.dark.svg")).read() + +# round-4 foot-plain mark (motion.py) +ARCH = "M6.769 26.321V14.910a9.231 9.231 0 0 1 18.462 0V16.575" +SW = 4.358 +DX, DY, DR = 25.231, 24.974, 3.526 +GAP = 8.5 +FACE = "outfit" # Outfit SemiBold - the current brand setting (outfit.py) + +THEMES = { + "dark": {"c": "#EDEBE6", "o": "#FF5A1F", "bg": "#0B0B0C"}, + "light": {"c": "#151517", "o": "#CC3A06", "bg": "#F5F5F4"}, +} +BW, BH = 1792, 360 # slim banner, one logo, no vertical padding to burn + +def mark_body(th): + return (f'' + f'') + +def mark_svg(theme): + th = THEMES[theme] + return ('OpenGateway' + + mark_body(th) + '') + +def lockup_geom(): + w = WM[FACE] + tx = 27.41 + GAP - w["minx"] + x0, x1 = 2.5, 27.41 + GAP + (w["maxx"] - w["minx"]) + 2.1 + y0, y1 = 1.4, max(w["maxy"], 28.5) + 2.1 + return w, tx, x0, y0, x1 - x0, y1 - y0 + +def logo_svg(theme): + th = THEMES[theme] + w, tx, x, y, vw, vh = lockup_geom() + letters = "".join(f'' for l in w["letters"] if l["d"]) + h = 256 + return (f'' + f'OpenGateway' + f'{mark_body(th)}' + f'{letters}') + +def _banner_frame(): + """Shared layout: dark grid, lockup centred on the slim canvas.""" + th = THEMES["dark"] + w, tx, x, y, vw, vh = lockup_geom() + s = min((BW - 260) / vw, (BH - 130) / vh) + px, py = (BW - vw * s) / 2, (BH - vh * s) / 2 + letters = "".join(f'' for l in w["letters"] if l["d"]) + plus = ('' + '') + wrap_open = (f'' + f'OpenGateway' + f'{plus}' + f'' + f'' + f'') + wrap_close = '' + tail = f'{letters}' + return wrap_open, tail, wrap_close + +def banner_svg(): + """Static banner: the settled foot-plain lockup, presentation attributes only + so any rasteriser (magick) renders it correctly.""" + wrap_open, tail, wrap_close = _banner_frame() + return wrap_open + mark_body(THEMES["dark"]) + tail + wrap_close + +def banner_anim_svg(): + """Animated banner: the Connect sequence plays on the mark (key clicks down/up, + the ring unlocks into the gate, the leg latches closed). CSS lives inside the + SVG, so it must be rasterised in a browser. The keyframes and mark markup are + extracted from motion.py's own output to keep one animation source.""" + style = re.search(r'', CONNECT, re.S).group(1) + mark = re.search(r'.*', CONNECT, re.S).group(0) + mark = mark[:-len('')] + wrap_open, tail, wrap_close = _banner_frame() + return wrap_open.replace('', f'') + mark + tail + wrap_close + +if __name__ == "__main__": + for theme in THEMES: + for name, svg in (("mark", mark_svg(theme)), ("logo", logo_svg(theme))): + path = os.path.join(HERE, f"{name}.{theme}.svg") + open(path, "w").write(svg) + print("wrote", path) + for name, svg in (("banner", banner_svg()), ("banner.anim", banner_anim_svg())): + path = os.path.join(HERE, f"{name}.svg") + open(path, "w").write(svg) + print("wrote", path) + print('rasterise: magick banner.svg ../banner.png (banner.anim.svg -> browser capture -> ../banner.gif)') diff --git a/docs/assets/logo-explore/mark.dark.svg b/docs/assets/logo-explore/mark.dark.svg new file mode 100644 index 0000000..e4e9fc6 --- /dev/null +++ b/docs/assets/logo-explore/mark.dark.svg @@ -0,0 +1 @@ +OpenGateway \ No newline at end of file diff --git a/docs/assets/logo-explore/mark.light.svg b/docs/assets/logo-explore/mark.light.svg new file mode 100644 index 0000000..1aca2a7 --- /dev/null +++ b/docs/assets/logo-explore/mark.light.svg @@ -0,0 +1 @@ +OpenGateway \ No newline at end of file diff --git a/docs/assets/logo-explore/motion/audio/l-emerge.wav b/docs/assets/logo-explore/motion/audio/l-emerge.wav new file mode 100644 index 0000000..fccac56 Binary files /dev/null and b/docs/assets/logo-explore/motion/audio/l-emerge.wav differ diff --git a/docs/assets/logo-explore/motion/audio/l-open.wav b/docs/assets/logo-explore/motion/audio/l-open.wav new file mode 100644 index 0000000..928d132 Binary files /dev/null and b/docs/assets/logo-explore/motion/audio/l-open.wav differ diff --git a/docs/assets/logo-explore/motion/audio/l-reveal.wav b/docs/assets/logo-explore/motion/audio/l-reveal.wav new file mode 100644 index 0000000..c98b52b Binary files /dev/null and b/docs/assets/logo-explore/motion/audio/l-reveal.wav differ diff --git a/docs/assets/logo-explore/motion/audio/l-trace.wav b/docs/assets/logo-explore/motion/audio/l-trace.wav new file mode 100644 index 0000000..f77f67e Binary files /dev/null and b/docs/assets/logo-explore/motion/audio/l-trace.wav differ diff --git a/docs/assets/logo-explore/motion/audio/l-wave.wav b/docs/assets/logo-explore/motion/audio/l-wave.wav new file mode 100644 index 0000000..908a0ab Binary files /dev/null and b/docs/assets/logo-explore/motion/audio/l-wave.wav differ diff --git a/docs/assets/logo-explore/motion/audio/m-connect.wav b/docs/assets/logo-explore/motion/audio/m-connect.wav new file mode 100644 index 0000000..ab6f42e Binary files /dev/null and b/docs/assets/logo-explore/motion/audio/m-connect.wav differ diff --git a/docs/assets/logo-explore/motion/audio/m-draw.wav b/docs/assets/logo-explore/motion/audio/m-draw.wav new file mode 100644 index 0000000..8bb8ce8 Binary files /dev/null and b/docs/assets/logo-explore/motion/audio/m-draw.wav differ diff --git a/docs/assets/logo-explore/motion/audio/m-knock.wav b/docs/assets/logo-explore/motion/audio/m-knock.wav new file mode 100644 index 0000000..b6a13e8 Binary files /dev/null and b/docs/assets/logo-explore/motion/audio/m-knock.wav differ diff --git a/docs/assets/logo-explore/motion/audio/m-latch.wav b/docs/assets/logo-explore/motion/audio/m-latch.wav new file mode 100644 index 0000000..29b1025 Binary files /dev/null and b/docs/assets/logo-explore/motion/audio/m-latch.wav differ diff --git a/docs/assets/logo-explore/motion/audio/m-morph.wav b/docs/assets/logo-explore/motion/audio/m-morph.wav new file mode 100644 index 0000000..e18cb03 Binary files /dev/null and b/docs/assets/logo-explore/motion/audio/m-morph.wav differ diff --git a/docs/assets/logo-explore/motion/audio/m-pulse.wav b/docs/assets/logo-explore/motion/audio/m-pulse.wav new file mode 100644 index 0000000..122b452 Binary files /dev/null and b/docs/assets/logo-explore/motion/audio/m-pulse.wav differ diff --git a/docs/assets/logo-explore/motion/audio/m-roll.wav b/docs/assets/logo-explore/motion/audio/m-roll.wav new file mode 100644 index 0000000..9ba0fc5 Binary files /dev/null and b/docs/assets/logo-explore/motion/audio/m-roll.wav differ diff --git a/docs/assets/logo-explore/motion/audio/m-trace.wav b/docs/assets/logo-explore/motion/audio/m-trace.wav new file mode 100644 index 0000000..f77f67e Binary files /dev/null and b/docs/assets/logo-explore/motion/audio/m-trace.wav differ diff --git a/docs/assets/logo-explore/motion/index.html b/docs/assets/logo-explore/motion/index.html new file mode 100644 index 0000000..82bc4ee --- /dev/null +++ b/docs/assets/logo-explore/motion/index.html @@ -0,0 +1,134 @@ + + + + + +OpenGateway logo motion + + + + + +
+

OpenGateway logo motion

+

In the product the logo stays still. It is on screen all day, so it only moves on a first-run splash, a loading screen, a docs or release hero, and when a connection changes state. Sound is off until you enable it, plays once per moment, and is never attached to a loop. Everything here respects prefers-reduced-motion by showing the static mark.

+
+ + + +
+ + + Sound is synthesised in your browser. Press Replay on a card to hear it. Muted below 1x speed. +
+
+
+

Logo mark

Morph

DelightRare

Splash and docs hero. A dot, a ring drawn around it, the ring opens into the arch, and the dot passes through the opening. Plays once.

Sound. Pop, a rising draw, a falling unfold, a tick at the turn and a landing note.

Plays once, 1.9 s

Draw

DelightRare

Splash, onboarding, docs hero. Plays once.

Sound. Sweep while the arch draws, pluck when the dot lands.

Plays once, 1.3 s

Drop and roll

ExplanationRare

Docs hero, empty states. The request enters the gate and settles at its exit. Plays once.

Sound. Two thumps, a short roll, a soft settle.

Plays once, 1.6 s

Pulse

State: liveAmbient

Next to a live request tail or a connected badge only. Static everywhere else.

Sound. One ping on first play. Do not loop sound.

Loops every 2.4 s

Trace

State: loadingAmbient

Loading screen or long startup. Replace with the static mark when done.

Sound. Soft sweep and a ping on first play. Do not loop sound.

Loops every 1.6 s

Knock

State: retryEvent

When a request retries or falls back. Plays once per event.

Sound. Three knocks. Keep off by default, it signals a failure.

Loops every 3.2 s

Latch

State: connectedEvent

Connection status. The leg latches the gap closed when linked and opens on disconnect.

Sound. Latch click on link, lighter release on disconnect.

Loops every 3.6 s

Connect

EventEvent

When a key or provider connects. The dot is the key: it clicks down and up in the lock, the ring opens into the gate, the key exits, and the leg latches closed. Plays once.

Sound. A pop as the key appears, a knock and click as it presses down, a lighter click on release, a falling sweep as the gate opens, a landing note, then a latch click and thump as the leg closes home, and a closing fifth.

Plays once, 3.3 s
+

Lockup

Morph and open

DelightRare

The lead concept. The mark morphs, then the lockup slides to centre as the name unfurls from the gate. Plays once.

Sound. The morph sounds, an air swell as it slides, a soft arpeggio across the letters and a closing open fifth.

Plays once, 2.6 s

Reveal

DelightRare

Splash, release video, README banner. Plays once.

Sound. Sweep, pop, a soft air swell and an open fifth.

Plays once, 1.9 s

Wave

DelightAmbient

Marketing hero on hover. A colour wave runs across the letters, then the dot ripples.

Sound. Pentatonic blip per letter, then a ping. Hover only.

Loops every 3.6 s

Emerge

ExplanationRare

Splash. The dot rolls out and the name slides out of the gate behind it. Plays once.

Sound. The roll sounds, then an air swell and a closing fifth.

Plays once, 2.2 s

Trace lockup

State: loadingAmbient

Full-screen loading with the name visible.

Sound. As the mark trace. First play only.

Loops every 1.6 s
+
+ + + diff --git a/docs/assets/logo-explore/motion/motion.py b/docs/assets/logo-explore/motion/motion.py new file mode 100644 index 0000000..8ceabae --- /dev/null +++ b/docs/assets/logo-explore/motion/motion.py @@ -0,0 +1,258 @@ +#!/usr/bin/env python3 +"""OpenGateway logo motion. Generates standalone animated SVGs (dark and light, two wordmark faces) and a self-contained +preview page with synthesised sound (Web Audio, no audio files). + +Mark geometry is the golden foot-plain mark (see ../gen.py round 4): stroke 4.358 on a 32 unit box, plain dot, no outline. +Run: python3 motion.py (needs wordmarks.json from wordmark.py) + +Motion rules used (Emil Kowalski): ease-out for entering, ease-in-out for on-screen movement, linear for constant motion, +transform/opacity only (plus stroke-dashoffset for the draw-on), nothing scales from 0, reduced motion shows the static mark. +Exception: the drop in 'roll' uses ease-in for gravity because it is a physical event in a brand moment, not UI feedback. +""" +import json, os + +HERE = os.path.dirname(os.path.abspath(__file__)) +WM = json.load(open(os.path.join(HERE, "wordmarks.json"))) + +EO = "cubic-bezier(0.23,1,0.32,1)" # strong ease-out +EIO = "cubic-bezier(0.77,0,0.175,1)" # strong ease-in-out +EBACK = "cubic-bezier(0.34,1.56,0.64,1)" # easeOutBack, for the dot pop only +EIQ = "cubic-bezier(0.55,0.085,0.68,0.53)" # easeInQuad, gravity only + +ARCH = "M6.769 26.321V14.910a9.231 9.231 0 0 1 18.462 0V16.575" +SW = 4.358 +DX, DY, DR = 25.231, 24.974, 3.526 +LEG = "M25.231 16.575V22.4" +# ring -> arch morph. Both paths share one command structure (M C A C) so the browser can interpolate them. +RING_D = "M16 24.141C10.902 24.141 6.769 20.008 6.769 14.91A9.231 9.231 0 0 1 25.231 14.91C25.231 20.008 21.098 24.141 16 24.141" +ARCH_M = "M6.769 26.321C6.769 22.5 6.769 18.7 6.769 14.91A9.231 9.231 0 0 1 25.231 14.91C25.231 15.5 25.231 16 25.231 16.575" +THEMES = {"dark": {"c": "#EDEBE6", "o": "#FF5A1F", "bg": "#0B0B0C"}, "light": {"c": "#151517", "o": "#CC3A06", "bg": "#F5F5F4"}} +E5, G5, A5, B5, D6, E6 = 659.25, 783.99, 880.0, 987.77, 1174.66, 1318.51 +PENTA = [E5, G5, A5, B5, D6, E6, D6, B5, A5, G5, E5] + +BASE = (".§-arch{fill:none;stroke:var(--c);stroke-width:%s;stroke-linecap:round;stroke-linejoin:round}" + ".§-dot{fill:var(--o)}") % SW + +# ---- mark animations: css, svg parts, cycle ms, loops, sound schedule -------------------------------------------- +def sub(css): + return css.replace("EIQ", EIQ).replace("EIO", EIO).replace("EBACK", EBACK).replace("EO", EO).replace("SWV", str(SW)).replace("RINGD", RING_D).replace("ARCHD", ARCH_M) + +def mark_svg(pre="", mid=""): + return (f'{pre}{mid}' + f'') + +M = {} + +M["morph"] = dict( + css=sub(BASE + ".§-arch{stroke-dasharray:1 3;animation:§-ring 650ms EIO 150ms backwards,§-open 600ms EIO 900ms backwards}" + ".§-dot{transform-box:fill-box;transform-origin:center;animation:§-pop 320ms EBACK backwards}" + ".§-dotg{animation:§-go 1850ms linear backwards}" + "@keyframes §-ring{from{stroke-dashoffset:1.06}to{stroke-dashoffset:0}}" + '@keyframes §-open{from{d:path("RINGD")}to{d:path("ARCHD")}}' + "@keyframes §-pop{from{opacity:0;transform:scale(.6)}to{opacity:1;transform:scale(1)}}" + "@keyframes §-go{0%{transform:translate(-9.231px,-10.064px)}67.57%{transform:translate(-9.231px,-10.064px);animation-timing-function:EIO}" + "83.78%{transform:translate(-9.231px,0);animation-timing-function:EO}100%{transform:translate(0,0)}}"), + svg=(f'' + f''), + cycle=1900, loop=False, + audio=[dict(t=0, c="pop", f=E5, g=.40), dict(t=150, c="sweep", d=650, f0=250, f1=900, g=.07), + dict(t=900, c="sweep", d=600, f0=1400, f1=500, g=.06), dict(t=1550, c="tick", f=G5, g=.08), + dict(t=1830, c="pop", f=B5, g=.32)]) +M["draw"] = dict( + css=sub(BASE + ".§-arch{stroke-dasharray:1 3;animation:§-draw 900ms EIO 100ms backwards}" + ".§-dot{transform-box:fill-box;transform-origin:center;animation:§-pop 420ms EBACK 860ms backwards}" + "@keyframes §-draw{from{stroke-dashoffset:1.06}to{stroke-dashoffset:0}}" + "@keyframes §-pop{from{opacity:0;transform:scale(.6)}to{opacity:1;transform:scale(1)}}"), + svg=mark_svg(), cycle=1300, loop=False, + audio=[dict(t=100, c="sweep", d=900, f0=300, f1=1400, g=.10), dict(t=900, c="pop", f=E5, g=.55)]) +M["roll"] = dict( + css=sub(BASE + ".§-dot{animation:§-fade 140ms EO 100ms backwards,§-roll 1500ms linear 100ms backwards}" + "@keyframes §-fade{from{opacity:0}to{opacity:1}}" + "@keyframes §-roll{0%{transform:translate(-9.231px,-12.974px);animation-timing-function:EIQ}" + "30%{transform:translate(-9.231px,0);animation-timing-function:EO}" + "42%{transform:translate(-9.231px,-3.2px);animation-timing-function:EIQ}" + "52%{transform:translate(-9.231px,0);animation-timing-function:EO}" + "100%{transform:translate(0,0)}}"), + svg=mark_svg(), cycle=1600, loop=False, + audio=[dict(t=550, c="thump", g=.5), dict(t=880, c="thump", g=.28, f=170), dict(t=900, c="rumble", d=650, g=.10), dict(t=1560, c="pop", f=E5, g=.32)]) +M["pulse"] = dict( + css=sub(BASE + ".§-halo{fill:var(--o);opacity:0;transform-box:fill-box;transform-origin:center;animation:§-halo 2400ms EO infinite}" + "@keyframes §-halo{0%{opacity:.4;transform:scale(1)}70%{opacity:0;transform:scale(1.85)}100%{opacity:0;transform:scale(1.85)}}"), + svg=mark_svg(pre=f''), cycle=2400, loop=True, + audio=[dict(t=0, c="ping", f=B5, g=.20)]) +M["trace"] = dict( + css=sub(BASE + ".§-trace{fill:none;stroke:var(--o);stroke-width:SWV;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:.22 3;opacity:0;animation:§-trace 1600ms linear infinite}" + ".§-dot{transform-box:fill-box;transform-origin:center;animation:§-beat 1600ms EO infinite}" + "@keyframes §-trace{0%{opacity:1;stroke-dashoffset:.28}75%{opacity:1;stroke-dashoffset:-1.06}75.1%,100%{opacity:0;stroke-dashoffset:-1.06}}" + "@keyframes §-beat{0%,72%{transform:scale(1)}80%{transform:scale(1.16)}100%{transform:scale(1)}}"), + svg=mark_svg(mid=f''), cycle=1600, loop=True, + audio=[dict(t=0, c="sweep", d=1200, f0=500, f1=2200, g=.06), dict(t=1200, c="ping", f=E6, g=.10)]) +M["knock"] = dict( + css=sub(BASE + ".§-dot{animation:§-knock 3200ms linear infinite}.§-arch{animation:§-thud 3200ms linear infinite}" + "@keyframes §-knock{0%{transform:translateY(0);animation-timing-function:EIQ}" + "5%{transform:translateY(-2.5px);animation-timing-function:EO}" + "10.3%{transform:translateY(0);animation-timing-function:EIQ}" + "16.25%{transform:translateY(-2.5px);animation-timing-function:EO}" + "21.25%{transform:translateY(0);animation-timing-function:EIQ}" + "25.6%{transform:translateY(-1.2px);animation-timing-function:EO}" + "28.75%,100%{transform:translateY(0)}}" + "@keyframes §-thud{0%{transform:translateY(0)}5%{transform:translateY(.45px)}8%{transform:translateY(0)}" + "16.25%{transform:translateY(.45px)}19%{transform:translateY(0)}25.6%{transform:translateY(.25px)}27.5%,100%{transform:translateY(0)}}"), + svg=mark_svg(), cycle=3200, loop=True, + audio=[dict(t=160, c="knock", g=.5), dict(t=520, c="knock", g=.42), dict(t=820, c="knock", g=.25)]) +M["latch"] = dict( + css=sub(BASE + ".§-leg{fill:none;stroke:var(--c);stroke-width:SWV;stroke-linecap:round;stroke-dasharray:1 3;stroke-dashoffset:1.06;animation:§-leg 3600ms linear infinite}" + "@keyframes §-leg{0%{stroke-dashoffset:1.06;animation-timing-function:EO}12%{stroke-dashoffset:0}" + "62%{stroke-dashoffset:0;animation-timing-function:EIO}76%,100%{stroke-dashoffset:1.06}}"), + svg=mark_svg(mid=f''), cycle=3600, loop=True, + audio=[dict(t=430, c="click", g=.35), dict(t=430, c="thump", g=.22, f=120), dict(t=2230, c="click", g=.18), dict(t=2740, c="tick", f=B5, g=.07)]) +# the leg is drawn before the dot, so move the dot after it: mark_svg already orders arch, mid, dot +M["connect"] = dict( + css=sub(BASE + ".§-arch{stroke-dasharray:1 3;animation:§-draw 500ms EIO backwards,§-open 600ms EIO 1200ms backwards,§-thud 2300ms linear backwards}" + ".§-mark{animation:§-thud2 3300ms linear backwards}" + ".§-leg{fill:none;stroke:var(--c);stroke-width:SWV;stroke-linecap:round;stroke-dasharray:1 3;stroke-dashoffset:1.06;animation:§-latch 450ms EO 2350ms both}" + ".§-dot{transform-box:fill-box;transform-origin:center;animation:§-pop 220ms EBACK 400ms backwards,§-press 2300ms linear backwards}" + ".§-dotg{animation:§-go 2300ms linear backwards}" + "@keyframes §-draw{from{stroke-dashoffset:1.06}to{stroke-dashoffset:0}}" + '@keyframes §-open{from{d:path("RINGD")}to{d:path("ARCHD")}}' + "@keyframes §-pop{from{opacity:0;transform:scale(.6)}to{opacity:1;transform:scale(1)}}" + "@keyframes §-thud{0%,36.7%{transform:translateY(0)}37.4%{transform:translateY(.5px)}38.5%,100%{transform:translateY(0)}}" + "@keyframes §-thud2{0%,84.3%{transform:translateY(0)}84.8%{transform:translateY(.6px)}85.6%,100%{transform:translateY(0)}}" + "@keyframes §-press{0%,30.4%{transform:scale(1)}37.4%{transform:scale(1.12,.78);animation-timing-function:EBACK}47.8%{transform:scale(1)}100%{transform:scale(1)}}" + "@keyframes §-go{0%,30.4%{transform:translate(-9.231px,-10.064px)}37.4%{transform:translate(-9.231px,-6.4px);animation-timing-function:EBACK}47.8%{transform:translate(-9.231px,-10.064px)}" + "63%{transform:translate(-9.231px,-10.064px);animation-timing-function:EIO}89.1%{transform:translate(-9.231px,0);animation-timing-function:EO}100%{transform:translate(0,0)}}" + "@keyframes §-latch{from{stroke-dashoffset:1.06}to{stroke-dashoffset:0}}"), + svg=(f'' + f''), + cycle=3300, loop=False, + audio=[dict(t=430, c="pop", f=E5, g=.30), + dict(t=860, c="knock", g=.55), dict(t=860, c="click", g=.35), dict(t=860, c="thump", g=.25, f=140), + dict(t=1060, c="click", g=.28), dict(t=1080, c="tick", f=G5, g=.08), + dict(t=1200, c="sweep", d=600, f0=1400, f1=500, g=.07), + dict(t=1780, c="tick", f=B5, g=.08), + dict(t=2100, c="pop", f=E5, g=.38), + dict(t=2800, c="click", g=.40), dict(t=2800, c="thump", g=.30, f=120), + dict(t=2980, c="pluck", f=B5, g=.22), dict(t=3080, c="pluck", f=E5, g=.18)]) + +# ---- lockups ----------------------------------------------------------------------------------------------------- +GAP = 8.5 +LETTER_IN = ".§-l{fill:var(--c);animation:§-in 420ms EO calc(980ms + var(--i)*45ms) backwards}@keyframes §-in{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}" +L = {} + +L["open"] = dict(base="morph", wrap=True, cycle=2600, loop=False, + extra=".§-all{animation:§-slide 800ms EIO 1500ms backwards}" + "@keyframes §-slide{from{transform:translateX(SLIDEXpx)}to{transform:translateX(0)}}" + ".§-l{fill:var(--c);animation:§-out 420ms EO calc(1750ms + var(--i)*35ms) backwards}" + "@keyframes §-out{from{opacity:0;transform:translateX(-12px)}to{opacity:1;transform:translateX(0)}}", + audio=M["morph"]["audio"] + [dict(t=1500, c="air", d=800, g=.04)] + + [dict(t=1750 + i * 35 + 120, c="tick", f=PENTA[i], g=.05) for i in range(11)] + + [dict(t=2350, c="pluck", f=E5, g=.20), dict(t=2500, c="pluck", f=B5, g=.20)]) +L["reveal"] = dict(base="draw", extra=LETTER_IN, cycle=1900, loop=False, + audio=M["draw"]["audio"] + [dict(t=1000, c="air", d=700, g=.04), dict(t=1250, c="pluck", f=B5, g=.26)]) +L["wave"] = dict(base=None, mark="pulse2", + extra=".§-l{fill:var(--c);animation:§-wave 3600ms ease-in-out infinite;animation-delay:calc(var(--i)*70ms)}" + "@keyframes §-wave{0%{fill:var(--c)}7%{fill:var(--o)}20%,100%{fill:var(--c)}}" + ".§-halo{fill:var(--o);opacity:0;transform-box:fill-box;transform-origin:center;animation:§-halo 3600ms EO infinite}" + "@keyframes §-halo{0%,28%{opacity:0;transform:scale(1)}30%{opacity:.4;transform:scale(1)}56%{opacity:0;transform:scale(1.85)}100%{opacity:0;transform:scale(1.85)}}", + cycle=3600, loop=True, + audio=[dict(t=i * 70 + 252, c="tick", f=PENTA[i], g=.10) for i in range(11)] + [dict(t=1100, c="ping", f=B5, g=.14)]) +L["emerge"] = dict(base="roll", extra=".§-l{fill:var(--c);animation:§-out 420ms EO calc(1300ms + var(--i)*40ms) backwards}@keyframes §-out{from{opacity:0;transform:translateX(-10px)}to{opacity:1;transform:translateX(0)}}", + cycle=2200, loop=False, + audio=M["roll"]["audio"] + [dict(t=1300, c="air", d=600, g=.04), dict(t=1850, c="pluck", f=B5, g=.2)]) +L["trace"] = dict(base="trace", extra=".§-l{fill:var(--c)}", cycle=1600, loop=True, audio=M["trace"]["audio"]) + +def lockup_geometry(face): + w = WM[face] + tx = 27.41 + GAP - w["minx"] + x0, x1 = 2.5, 27.41 + GAP + (w["maxx"] - w["minx"]) + 2.1 + y0, y1 = 1.4, max(w["maxy"], 28.5) + 2.1 + return tx, (x0, y0, x1 - x0, y1 - y0) + +def letters_svg(face): + return "".join(f'' for i, l in enumerate(WM[face]["letters"]) if l["d"]) + +def variant(kind, name, face="instrument"): + """returns (css, body_svg, viewBox) for a variant with class prefix already substituted""" + p = ("m" if kind == "mark" else "l") + "-" + name + if kind == "mark": + d = M[name]; css, body, vb = d["css"], d["svg"], "0 0 32 32" + else: + d = L[name] + mk = M[d["base"]] if d["base"] else dict(css=M["pulse"]["css"].split(".§-halo")[0], svg=M["pulse"]["svg"]) + css = mk["css"] + sub(d["extra"]) + body_mark = mk["svg"] + tx, vbx = lockup_geometry(face) + body = body_mark + f'{letters_svg(face)}' + if d.get("wrap"): + body = f'{body}' + css = css.replace("SLIDEX", f"{vbx[0] + vbx[2] / 2 - 16:.2f}") + vb = " ".join(f"{v:.2f}" for v in vbx) + return css.replace("§", p), body.replace("§", p), vb + +META = { + ("mark", "morph"): dict(title="Morph", purpose="Delight", tier="Rare", use="Splash and docs hero. A dot, a ring drawn around it, the ring opens into the arch, and the dot passes through the opening. Plays once.", sound="Pop, a rising draw, a falling unfold, a tick at the turn and a landing note."), + ("lockup", "open"): dict(title="Morph and open", purpose="Delight", tier="Rare", use="The lead concept. The mark morphs, then the lockup slides to centre as the name unfurls from the gate. Plays once.", sound="The morph sounds, an air swell as it slides, a soft arpeggio across the letters and a closing open fifth."), + ("mark", "draw"): dict(title="Draw", purpose="Delight", tier="Rare", use="Splash, onboarding, docs hero. Plays once.", sound="Sweep while the arch draws, pluck when the dot lands."), + ("mark", "roll"): dict(title="Drop and roll", purpose="Explanation", tier="Rare", use="Docs hero, empty states. The request enters the gate and settles at its exit. Plays once.", sound="Two thumps, a short roll, a soft settle."), + ("mark", "pulse"): dict(title="Pulse", purpose="State: live", tier="Ambient", use="Next to a live request tail or a connected badge only. Static everywhere else.", sound="One ping on first play. Do not loop sound."), + ("mark", "trace"): dict(title="Trace", purpose="State: loading", tier="Ambient", use="Loading screen or long startup. Replace with the static mark when done.", sound="Soft sweep and a ping on first play. Do not loop sound."), + ("mark", "knock"): dict(title="Knock", purpose="State: retry", tier="Event", use="When a request retries or falls back. Plays once per event.", sound="Three knocks. Keep off by default, it signals a failure."), + ("mark", "latch"): dict(title="Latch", purpose="State: connected", tier="Event", use="Connection status. The leg latches the gap closed when linked and opens on disconnect.", sound="Latch click on link, lighter release on disconnect."), + ("mark", "connect"): dict(title="Connect", purpose="Event", tier="Event", use="When a key or provider connects. The dot is the key: it clicks down and up in the lock, the ring opens into the gate, the key exits, and the leg latches closed. Plays once.", sound="A pop as the key appears, a knock and click as it presses down, a lighter click on release, a falling sweep as the gate opens, a landing note, then a latch click and thump as the leg closes home, and a closing fifth."), + ("lockup", "reveal"):dict(title="Reveal", purpose="Delight", tier="Rare", use="Splash, release video, README banner. Plays once.", sound="Sweep, pop, a soft air swell and an open fifth."), + ("lockup", "wave"): dict(title="Wave", purpose="Delight", tier="Ambient", use="Marketing hero on hover. A colour wave runs across the letters, then the dot ripples.", sound="Pentatonic blip per letter, then a ping. Hover only."), + ("lockup", "emerge"):dict(title="Emerge", purpose="Explanation", tier="Rare", use="Splash. The dot rolls out and the name slides out of the gate behind it. Plays once.", sound="The roll sounds, then an air swell and a closing fifth."), + ("lockup", "trace"): dict(title="Trace lockup", purpose="State: loading", tier="Ambient", use="Full-screen loading with the name visible.", sound="As the mark trace. First play only."), +} + +STANDALONE_RM = "@media (prefers-reduced-motion:reduce){*{animation:none!important}}" +def standalone(kind, name, theme, face): + css, body, vb = variant(kind, name, face) + t = THEMES[theme]; w, h = [float(v) for v in vb.split()[2:]] + scale = 8 if kind == "mark" else 4 + return (f'' + f'{body}\n') + +def main(): + os.makedirs(os.path.join(HERE, "svg"), exist_ok=True) + n = 0 + for (kind, name) in META: + faces = list(WM) if kind == "lockup" else ["instrument"] + for face in faces: + for theme in ("dark", "light"): + fn = f"{kind[0]}-{name}" + (f".{face}" if kind == "lockup" else "") + f".{theme}.svg" + open(os.path.join(HERE, "svg", fn), "w").write(standalone(kind, name, theme, face)); n += 1 + # preview page + css_all, cards, sched, cycle, loops = [], [], {}, {}, {} + for (kind, name), m in META.items(): + vid = f"{kind[0]}-{name}" + d = M[name] if kind == "mark" else L[name] + css, _, _ = variant(kind, name) + css_all.append(css) + sched[vid] = d["audio"]; cycle[vid] = d["cycle"]; loops[vid] = d["loop"] + def tile(theme): + if kind == "mark": + _, body, vb = variant(kind, name) + return f'
' + inner = "" + for face in WM: + _, body, vb = variant(kind, name, face) + inner += f'' + return f'
{inner}
' + cards.append( + f'

{m["title"]}

{m["purpose"]}{m["tier"]}
' + f'
{tile("dark")}{tile("light")}
' + f'

{m["use"]}

Sound. {m["sound"]}

' + f'
{"Loops every %.1f s" % (d["cycle"]/1000) if d["loop"] else "Plays once, %.1f s" % (d["cycle"]/1000)}
') + tpl = open(os.path.join(HERE, "preview.template.html")).read() + face_css = "".join(f'body[data-face={f}]:not(.stage) .svgl:not(.f-{f}){{display:none}}' for f in WM) + face_btns = "".join(f'' for f in WM) + html = (tpl.replace("/*@CSS@*/", face_css + "".join(css_all)).replace("", face_btns) + .replace("", "".join(c for c in cards if 'data-id="m-' in c)) + .replace("", "".join(c for c in cards if 'data-id="l-' in c)) + .replace("/*@SCHED@*/{}", json.dumps(sched)).replace("/*@CYCLE@*/{}", json.dumps(cycle)).replace("/*@LOOPS@*/{}", json.dumps(loops))) + open(os.path.join(HERE, "index.html"), "w").write(html) + print("wrote", n, "svgs and index.html") + +if __name__ == "__main__": + main() diff --git a/docs/assets/logo-explore/motion/outfit.py b/docs/assets/logo-explore/motion/outfit.py new file mode 100644 index 0000000..5425445 --- /dev/null +++ b/docs/assets/logo-explore/motion/outfit.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python3 +"""Outfit deep-dive: weights, geometric neighbours and Outfit-as-wordmark pairings, side by side. +Run: python3 outfit.py (needs wordmarks.json + wordmarks-extra.json from wordmark.py) +Writes outfit.html next to itself.""" +import json, os + +HERE = os.path.dirname(os.path.abspath(__file__)) +WM = json.load(open(os.path.join(HERE, "wordmarks.json"))) +WX = json.load(open(os.path.join(HERE, "wordmarks-extra.json"))) +ALL = {**WM, **WX} + +ARCH = "M6.769 26.321V14.910a9.231 9.231 0 0 1 18.462 0V16.575" +DX, DY, DR = 25.231, 24.974, 3.526 +GAP = 8.5 + +WEIGHTS = ["outfit500", "outfit", "outfit700", "outfit800"] +WEIGHT_NOTE = { + "outfit500": "Medium. Lighter and more elegant; the arch mark may out-weigh the word.", + "outfit": "SemiBold. The current brand setting - balanced next to the 4.358 stroke.", + "outfit700": "Bold. Stronger lockup, better at small sizes and on dark tiles.", + "outfit800": "ExtraBold. Display territory; the letters start competing with the mark.", +} +NEIGH = ["outfit", "urbanist", "figtree", "jost", "onest", "hanken", "poppins"] +NEIGH_NOTE = { + "outfit": "Outfit - the anchor. Circular geometric, high x-height, open counters.", + "urbanist": "Urbanist - slightly softer curves, a touch more humanist warmth.", + "figtree": "Figtree - friendlier terminals, the closest all-rounder to Outfit.", + "jost": "Jost - Futura lineage, narrower, more geometric severity.", + "onest": "Onest - Outfit-like geometry with more even texture.", + "hanken": "Hanken Grotesk - compact and grotesque rather than geometric.", + "poppins": "Poppins - the classic geometric comparison; wider and rounder.", +} +FAM = {"outfit": "Outfit", "instrument": "Instrument Sans", "inter": "Inter", "plexsans": "IBM Plex Sans", + "publicsans": "Public Sans", "sora": "Sora", "plexmono": "IBM Plex Mono", "jetbrains": "JetBrains Mono", + "spacemono": "Space Mono"} +GF = ("family=Outfit:wght@400;500;600;700;800&family=Instrument+Sans:wght@400;500;600&family=Inter:wght@400;500;600&" + "family=IBM+Plex+Sans:wght@400;500;600&family=Public+Sans:wght@400;500;600&family=Sora:wght@400;500;600&" + "family=IBM+Plex+Mono:wght@400;500&family=JetBrains+Mono:wght@400;500&family=Space+Mono:wght@400;700&display=swap") + +# Outfit as the wordmark/heading face: (body, mono, tag, note) +PAIRS = [ + ("instrument", "plexmono", "Recommended", + "Outfit stays the voice of the brand, Instrument Sans keeps the UI quiet, Plex Mono fits API docs."), + ("inter", "jetbrains", "Dev-standard", + "The familiar developer stack under an Outfit wordmark. Inter disappears, JetBrains feels like home."), + ("plexsans", "plexmono", "Family cohesion", + "One superfamily below the wordmark - even texture, zero clashing, slightly corporate."), + ("publicsans", "spacemono", "Open-source stack", + "Public Sans and Space Mono read civic and maker-ish; a fully open-source type stack."), + ("sora", "plexmono", "Soft-futurist", + "Sora body under Outfit headings - two geometrics, subtle contrast in the tails."), +] + +def letters_svg(face, cls): + return "".join(f'' for l in ALL[face]["letters"] if l["d"]) + +def specimen_svg(face, cap_px=36): + w = ALL[face]; sc = cap_px / w["cap"] + bw, bh = w["maxx"] - w["minx"], w["maxy"] - w["miny"] + return (f'{letters_svg(face, "spec")}') + +def lockup_svg(face, theme, h=52): + w = ALL[face] + tx = 27.41 + GAP - w["minx"] + x0, x1 = 2.5, 27.41 + GAP + (w["maxx"] - w["minx"]) + 2.1 + y0, y1 = 1.4, max(w["maxy"], 28.5) + 2.1 + body = (f'' + f'{letters_svg(face, "mk-l")}') + return (f'{body}') + +base = ALL["outfit"]["advance"] +weights = "\n".join(f"""
+
{ALL[f]["label"]}{round(ALL[f]["advance"]/base*100)}% width

{WEIGHT_NOTE[f]}

+
{specimen_svg(f)}
+
{lockup_svg(f, "dark")}{lockup_svg(f, "light")}
+
""" for f in WEIGHTS) + +neigh = "\n".join(f"""
+
{ALL[f]["label"]}{round(ALL[f]["advance"]/base*100)}% width

{NEIGH_NOTE[f]}

+
{specimen_svg(f)}
+
""" for f in NEIGH) + +def pair_card(i, body, mono, tag, note): + hb, mb = FAM[body], FAM[mono] + return f"""
+
#{i} Outfit + {hb} + {mb}{tag}
+
+
{lockup_svg("outfit", "dark")}{lockup_svg("outfit", "light")}
+
+

Ship the gateway.

+

OpenGateway adds SSO, audit logs, guardrails and routing to any AI app. OpenAI-compatible, every feature free, MIT forever.

+
$ pip install opengateway && og serve --port 8080
+ +
+
+

{note}

+
""" + +pairs = "\n".join(pair_card(i + 1, *p) for i, p in enumerate(PAIRS)) + +small = "\n".join(f"""
{ALL[f]["label"]}
+
+ OpenGatewayOpenGatewayOpenGateway + Routing, guardrails, audit logs. OpenAI-compatible.
""" for f in WEIGHTS) + +HTML = f""" + + + + +OpenGateway - Outfit deep dive + + + + +
+

Outfit deep dive

+ Weights, geometric neighbours and Outfit-as-wordmark pairings. Same copy everywhere. + + +
+
+

Outfit weights

+

Wordmark outlined at matched cap height, lockup with the foot-plain mark beside it.

+{weights} + +

Outfit neighbourhood

+

Geometric sans relatives at SemiBold, so Outfit's own quirks are easy to spot. Widths are against Outfit SemiBold.

+{neigh} + +

Outfit as the wordmark

+

Five systems under the same Outfit heading - side by side, identical copy.

+
+{pairs} +
+ +

Small sizes

+

Where the logo actually lives - nav, footer, body.

+{small} +
+ + +""" +open(os.path.join(HERE, "outfit.html"), "w").write(HTML) +print("wrote outfit.html", len(HTML), "bytes") diff --git a/docs/assets/logo-explore/motion/pairing.html b/docs/assets/logo-explore/motion/pairing.html new file mode 100644 index 0000000..b2c2c31 --- /dev/null +++ b/docs/assets/logo-explore/motion/pairing.html @@ -0,0 +1,179 @@ + + + + + +OpenGateway - font pairings + + + + +
+

OpenGateway - font pairings

+ Wordmark face + body face + mono face. Same copy in every board. 9 pairings, side by side. + + +
+
+
+
+
#1 Space Grotesk + Instrument Sans + IBM Plex Mono + Recommended
+
+
+
+

Ship the gateway.

+

OpenGateway adds SSO, audit logs, guardrails and routing to any AI app. OpenAI-compatible, every feature free, MIT forever.

+
$ pip install opengateway && og serve --port 8080
+ +
+
+

The balanced system. Space Grotesk quirks carry the personality, Instrument Sans keeps the UI calm, Plex Mono fits API docs.

+
+
+
#2 Space Grotesk + Inter + JetBrains Mono + Dev-standard
+
+
+
+

Ship the gateway.

+

OpenGateway adds SSO, audit logs, guardrails and routing to any AI app. OpenAI-compatible, every feature free, MIT forever.

+
$ pip install opengateway && og serve --port 8080
+ +
+
+

The same wordmark against the most familiar dev trio. Inter disappears in the UI, JetBrains Mono feels like the IDE.

+
+
+
#3 Syne + Familjen Grotesk + Space Mono + Expressive editorial
+
+
+
+

Ship the gateway.

+

OpenGateway adds SSO, audit logs, guardrails and routing to any AI app. OpenAI-compatible, every feature free, MIT forever.

+
$ pip install opengateway && og serve --port 8080
+ +
+
+

Loudest voice on the page. Syne headlines, Familjen body copy, Space Mono as the maker-y accent. Artsy, opinionated.

+
+
+
#4 Martian Mono + Outfit + Martian Mono + Terminal identity
+
+
+
+

Ship the gateway.

+

OpenGateway adds SSO, audit logs, guardrails and routing to any AI app. OpenAI-compatible, every feature free, MIT forever.

+
$ pip install opengateway && og serve --port 8080
+ +
+
+

One voice from CLI to docs: mono everywhere it counts, Outfit softens the UI so it does not read as a terminal emulator.

+
+
+
#5 Unbounded + Sora + IBM Plex Mono + Geometric futurist
+
+
+
+

Ship the gateway.

+

OpenGateway adds SSO, audit logs, guardrails and routing to any AI app. OpenAI-compatible, every feature free, MIT forever.

+
$ pip install opengateway && og serve --port 8080
+ +
+
+

Wide display wordmark with a cool, even body. Long lockup - give it room on the marketing site.

+
+
+
#6 Familjen Grotesk + Public Sans + IBM Plex Mono + Compact industrious
+
+
+
+

Ship the gateway.

+

OpenGateway adds SSO, audit logs, guardrails and routing to any AI app. OpenAI-compatible, every feature free, MIT forever.

+
$ pip install opengateway && og serve --port 8080
+ +
+
+

Tightest lockup of the set (93% width) with two workhorse grotesques. Quiet, dense, product-like.

+
+
+
#7 Sora + Instrument Sans + JetBrains Mono + Quiet tech
+
+
+
+

Ship the gateway.

+

OpenGateway adds SSO, audit logs, guardrails and routing to any AI app. OpenAI-compatible, every feature free, MIT forever.

+
$ pip install opengateway && og serve --port 8080
+ +
+
+

Even-tempered everywhere. Nothing shouts - the mark does all the branding.

+
+
+
#8 Parkinsans + Outfit + Space Mono + Friendly gateway
+
+
+
+

Ship the gateway.

+

OpenGateway adds SSO, audit logs, guardrails and routing to any AI app. OpenAI-compatible, every feature free, MIT forever.

+
$ pip install opengateway && og serve --port 8080
+ +
+
+

Softest pairing. Rounded wordmark plus Outfit reads approachable, good for onboarding-heavy products.

+
+
+
#9 Dela Gothic One + Outfit + IBM Plex Mono + Statement
+
+
+
+

Ship the gateway.

+

OpenGateway adds SSO, audit logs, guardrails and routing to any AI app. OpenAI-compatible, every feature free, MIT forever.

+
$ pip install opengateway && og serve --port 8080
+ +
+
+

Max-volume wordmark against a quiet UI. The contrast is the idea - use the lockup sparingly, at size.

+
+
+
+ + diff --git a/docs/assets/logo-explore/motion/pairing.py b/docs/assets/logo-explore/motion/pairing.py new file mode 100644 index 0000000..1c48e9e --- /dev/null +++ b/docs/assets/logo-explore/motion/pairing.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +"""OpenGateway font-pairing exploration. Each pairing is a mini brand board shown side by side: +outlined wordmark + foot-plain lockup (from wordmarks.json) next to a live type stack - +heading in the wordmark face, body in the companion face, code in the mono face. +Same copy in every board, so only the type differs. +Run: python3 pairing.py (needs wordmarks.json from wordmark.py) +Writes pairing.html next to itself.""" +import json, os + +HERE = os.path.dirname(os.path.abspath(__file__)) +WM = json.load(open(os.path.join(HERE, "wordmarks.json"))) + +ARCH = "M6.769 26.321V14.910a9.231 9.231 0 0 1 18.462 0V16.575" +DX, DY, DR = 25.231, 24.974, 3.526 +GAP = 8.5 + +FAM = {"instrument": "Instrument Sans", "inter": "Inter", "publicsans": "Public Sans", "outfit": "Outfit", + "familjen": "Familjen Grotesk", "sora": "Sora", "space": "Space Grotesk", "syne": "Syne", + "martian": "Martian Mono", "unbounded": "Unbounded", "parkinsans": "Parkinsans", "dela": "Dela Gothic One", + "ibmplexmono": "IBM Plex Mono", "jetbrains": "JetBrains Mono", "spacemono": "Space Mono"} +GF = ("family=Space+Grotesk:wght@400;500;600;700&family=Instrument+Sans:wght@400;500;600&" + "family=Inter:wght@400;500;600&family=IBM+Plex+Sans:wght@400;500;600&family=Public+Sans:wght@400;500;600&" + "family=Outfit:wght@400;500;600&family=Familjen+Grotesk:wght@400;500;600;700&family=Sora:wght@400;500;600&" + "family=Syne:wght@400;500;600;700&family=Martian+Mono:wght@400;500;600&family=Unbounded:wght@400;500;600&" + "family=Parkinsans:wght@400;500;600&family=Dela+Gothic+One&family=IBM+Plex+Mono:wght@400;500&" + "family=JetBrains+Mono:wght@400;500&family=Space+Mono:wght@400;700&display=swap") + +# (wordmark, body, mono, tag, note) - same copy everywhere so the type is the only variable +PAIRS = [ + ("space", "instrument", "ibmplexmono", "Recommended", + "The balanced system. Space Grotesk quirks carry the personality, Instrument Sans keeps the UI calm, Plex Mono fits API docs."), + ("space", "inter", "jetbrains", "Dev-standard", + "The same wordmark against the most familiar dev trio. Inter disappears in the UI, JetBrains Mono feels like the IDE."), + ("syne", "familjen", "spacemono", "Expressive editorial", + "Loudest voice on the page. Syne headlines, Familjen body copy, Space Mono as the maker-y accent. Artsy, opinionated."), + ("martian", "outfit", "martian", "Terminal identity", + "One voice from CLI to docs: mono everywhere it counts, Outfit softens the UI so it does not read as a terminal emulator."), + ("unbounded", "sora", "ibmplexmono", "Geometric futurist", + "Wide display wordmark with a cool, even body. Long lockup - give it room on the marketing site."), + ("familjen", "publicsans", "ibmplexmono", "Compact industrious", + "Tightest lockup of the set (93% width) with two workhorse grotesques. Quiet, dense, product-like."), + ("sora", "instrument", "jetbrains", "Quiet tech", + "Even-tempered everywhere. Nothing shouts - the mark does all the branding."), + ("parkinsans", "outfit", "spacemono", "Friendly gateway", + "Softest pairing. Rounded wordmark plus Outfit reads approachable, good for onboarding-heavy products."), + ("dela", "outfit", "ibmplexmono", "Statement", + "Max-volume wordmark against a quiet UI. The contrast is the idea - use the lockup sparingly, at size."), +] + +def letters_svg(face, cls): + return "".join(f'' for l in WM[face]["letters"] if l["d"]) + +def lockup_svg(face, theme, h=52): + w = WM[face] + tx = 27.41 + GAP - w["minx"] + x0, x1 = 2.5, 27.41 + GAP + (w["maxx"] - w["minx"]) + 2.1 + y0, y1 = 1.4, max(w["maxy"], 28.5) + 2.1 + body = (f'' + f'{letters_svg(face, "mk-l")}') + return (f'{body}') + +def card(i, wm, body, mono, tag, note): + hb, mb, wb = FAM[body], FAM[mono], FAM[wm] + return f"""
+
#{i} {wb} + {hb} + {mb} + {tag}
+
+
{lockup_svg(wm, "dark")}{lockup_svg(wm, "light")}
+
+

Ship the gateway.

+

OpenGateway adds SSO, audit logs, guardrails and routing to any AI app. OpenAI-compatible, every feature free, MIT forever.

+
$ pip install opengateway && og serve --port 8080
+ +
+
+

{note}

+
""" + +cards = "\n".join(card(i + 1, *p) for i, p in enumerate(PAIRS)) + +HTML = f""" + + + + +OpenGateway - font pairings + + + + +
+

OpenGateway - font pairings

+ Wordmark face + body face + mono face. Same copy in every board. 9 pairings, side by side. + + +
+
+
+{cards} +
+
+ + +""" +open(os.path.join(HERE, "pairing.html"), "w").write(HTML) +print("wrote pairing.html", len(HTML), "bytes,", len(PAIRS), "pairings") diff --git a/docs/assets/logo-explore/motion/preview.template.html b/docs/assets/logo-explore/motion/preview.template.html new file mode 100644 index 0000000..8820740 --- /dev/null +++ b/docs/assets/logo-explore/motion/preview.template.html @@ -0,0 +1,134 @@ + + + + + +OpenGateway logo motion + + + + + +
+

OpenGateway logo motion

+

In the product the logo stays still. It is on screen all day, so it only moves on a first-run splash, a loading screen, a docs or release hero, and when a connection changes state. Sound is off until you enable it, plays once per moment, and is never attached to a loop. Everything here respects prefers-reduced-motion by showing the static mark.

+
+ + + +
+ + + Sound is synthesised in your browser. Press Replay on a card to hear it. Muted below 1x speed. +
+
+
+

Logo mark

+

Lockup

+
+ + + diff --git a/docs/assets/logo-explore/motion/svg/l-emerge.dela.dark.svg b/docs/assets/logo-explore/motion/svg/l-emerge.dela.dark.svg new file mode 100644 index 0000000..69ec7df --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-emerge.dela.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-emerge.dela.light.svg b/docs/assets/logo-explore/motion/svg/l-emerge.dela.light.svg new file mode 100644 index 0000000..2fdac1f --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-emerge.dela.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-emerge.familjen.dark.svg b/docs/assets/logo-explore/motion/svg/l-emerge.familjen.dark.svg new file mode 100644 index 0000000..a451d6a --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-emerge.familjen.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-emerge.familjen.light.svg b/docs/assets/logo-explore/motion/svg/l-emerge.familjen.light.svg new file mode 100644 index 0000000..a357c06 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-emerge.familjen.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-emerge.instrument.dark.svg b/docs/assets/logo-explore/motion/svg/l-emerge.instrument.dark.svg new file mode 100644 index 0000000..bf35633 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-emerge.instrument.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-emerge.instrument.light.svg b/docs/assets/logo-explore/motion/svg/l-emerge.instrument.light.svg new file mode 100644 index 0000000..9f4da89 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-emerge.instrument.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-emerge.martian.dark.svg b/docs/assets/logo-explore/motion/svg/l-emerge.martian.dark.svg new file mode 100644 index 0000000..2bf6b54 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-emerge.martian.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-emerge.martian.light.svg b/docs/assets/logo-explore/motion/svg/l-emerge.martian.light.svg new file mode 100644 index 0000000..29126e7 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-emerge.martian.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-emerge.outfit.dark.svg b/docs/assets/logo-explore/motion/svg/l-emerge.outfit.dark.svg new file mode 100644 index 0000000..80dea71 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-emerge.outfit.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-emerge.outfit.light.svg b/docs/assets/logo-explore/motion/svg/l-emerge.outfit.light.svg new file mode 100644 index 0000000..e93c032 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-emerge.outfit.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-emerge.parkinsans.dark.svg b/docs/assets/logo-explore/motion/svg/l-emerge.parkinsans.dark.svg new file mode 100644 index 0000000..dc1b9f9 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-emerge.parkinsans.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-emerge.parkinsans.light.svg b/docs/assets/logo-explore/motion/svg/l-emerge.parkinsans.light.svg new file mode 100644 index 0000000..4ba1918 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-emerge.parkinsans.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-emerge.sora.dark.svg b/docs/assets/logo-explore/motion/svg/l-emerge.sora.dark.svg new file mode 100644 index 0000000..a360584 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-emerge.sora.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-emerge.sora.light.svg b/docs/assets/logo-explore/motion/svg/l-emerge.sora.light.svg new file mode 100644 index 0000000..e08df2e --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-emerge.sora.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-emerge.space.dark.svg b/docs/assets/logo-explore/motion/svg/l-emerge.space.dark.svg new file mode 100644 index 0000000..aaef199 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-emerge.space.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-emerge.space.light.svg b/docs/assets/logo-explore/motion/svg/l-emerge.space.light.svg new file mode 100644 index 0000000..864045a --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-emerge.space.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-emerge.syne.dark.svg b/docs/assets/logo-explore/motion/svg/l-emerge.syne.dark.svg new file mode 100644 index 0000000..7f4c350 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-emerge.syne.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-emerge.syne.light.svg b/docs/assets/logo-explore/motion/svg/l-emerge.syne.light.svg new file mode 100644 index 0000000..d69c4a5 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-emerge.syne.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-emerge.unbounded.dark.svg b/docs/assets/logo-explore/motion/svg/l-emerge.unbounded.dark.svg new file mode 100644 index 0000000..c43ca20 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-emerge.unbounded.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-emerge.unbounded.light.svg b/docs/assets/logo-explore/motion/svg/l-emerge.unbounded.light.svg new file mode 100644 index 0000000..2313c9e --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-emerge.unbounded.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-open.dela.dark.svg b/docs/assets/logo-explore/motion/svg/l-open.dela.dark.svg new file mode 100644 index 0000000..59665a9 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-open.dela.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-open.dela.light.svg b/docs/assets/logo-explore/motion/svg/l-open.dela.light.svg new file mode 100644 index 0000000..f875003 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-open.dela.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-open.familjen.dark.svg b/docs/assets/logo-explore/motion/svg/l-open.familjen.dark.svg new file mode 100644 index 0000000..bcac8af --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-open.familjen.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-open.familjen.light.svg b/docs/assets/logo-explore/motion/svg/l-open.familjen.light.svg new file mode 100644 index 0000000..46605f0 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-open.familjen.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-open.instrument.dark.svg b/docs/assets/logo-explore/motion/svg/l-open.instrument.dark.svg new file mode 100644 index 0000000..dded956 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-open.instrument.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-open.instrument.light.svg b/docs/assets/logo-explore/motion/svg/l-open.instrument.light.svg new file mode 100644 index 0000000..5d38e74 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-open.instrument.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-open.martian.dark.svg b/docs/assets/logo-explore/motion/svg/l-open.martian.dark.svg new file mode 100644 index 0000000..343d33c --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-open.martian.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-open.martian.light.svg b/docs/assets/logo-explore/motion/svg/l-open.martian.light.svg new file mode 100644 index 0000000..ee500e0 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-open.martian.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-open.outfit.dark.svg b/docs/assets/logo-explore/motion/svg/l-open.outfit.dark.svg new file mode 100644 index 0000000..79658c8 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-open.outfit.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-open.outfit.light.svg b/docs/assets/logo-explore/motion/svg/l-open.outfit.light.svg new file mode 100644 index 0000000..9401336 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-open.outfit.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-open.parkinsans.dark.svg b/docs/assets/logo-explore/motion/svg/l-open.parkinsans.dark.svg new file mode 100644 index 0000000..eb03c79 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-open.parkinsans.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-open.parkinsans.light.svg b/docs/assets/logo-explore/motion/svg/l-open.parkinsans.light.svg new file mode 100644 index 0000000..103b268 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-open.parkinsans.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-open.sora.dark.svg b/docs/assets/logo-explore/motion/svg/l-open.sora.dark.svg new file mode 100644 index 0000000..96d1a05 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-open.sora.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-open.sora.light.svg b/docs/assets/logo-explore/motion/svg/l-open.sora.light.svg new file mode 100644 index 0000000..2683442 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-open.sora.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-open.space.dark.svg b/docs/assets/logo-explore/motion/svg/l-open.space.dark.svg new file mode 100644 index 0000000..531376f --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-open.space.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-open.space.light.svg b/docs/assets/logo-explore/motion/svg/l-open.space.light.svg new file mode 100644 index 0000000..8337fb4 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-open.space.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-open.syne.dark.svg b/docs/assets/logo-explore/motion/svg/l-open.syne.dark.svg new file mode 100644 index 0000000..0d02344 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-open.syne.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-open.syne.light.svg b/docs/assets/logo-explore/motion/svg/l-open.syne.light.svg new file mode 100644 index 0000000..5fde233 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-open.syne.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-open.unbounded.dark.svg b/docs/assets/logo-explore/motion/svg/l-open.unbounded.dark.svg new file mode 100644 index 0000000..bad1ccc --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-open.unbounded.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-open.unbounded.light.svg b/docs/assets/logo-explore/motion/svg/l-open.unbounded.light.svg new file mode 100644 index 0000000..970340d --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-open.unbounded.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-reveal.dela.dark.svg b/docs/assets/logo-explore/motion/svg/l-reveal.dela.dark.svg new file mode 100644 index 0000000..3cbf059 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-reveal.dela.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-reveal.dela.light.svg b/docs/assets/logo-explore/motion/svg/l-reveal.dela.light.svg new file mode 100644 index 0000000..80bd826 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-reveal.dela.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-reveal.familjen.dark.svg b/docs/assets/logo-explore/motion/svg/l-reveal.familjen.dark.svg new file mode 100644 index 0000000..02dbadb --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-reveal.familjen.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-reveal.familjen.light.svg b/docs/assets/logo-explore/motion/svg/l-reveal.familjen.light.svg new file mode 100644 index 0000000..87f5e7e --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-reveal.familjen.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-reveal.instrument.dark.svg b/docs/assets/logo-explore/motion/svg/l-reveal.instrument.dark.svg new file mode 100644 index 0000000..a707215 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-reveal.instrument.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-reveal.instrument.light.svg b/docs/assets/logo-explore/motion/svg/l-reveal.instrument.light.svg new file mode 100644 index 0000000..5f6619c --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-reveal.instrument.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-reveal.martian.dark.svg b/docs/assets/logo-explore/motion/svg/l-reveal.martian.dark.svg new file mode 100644 index 0000000..3e5e41b --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-reveal.martian.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-reveal.martian.light.svg b/docs/assets/logo-explore/motion/svg/l-reveal.martian.light.svg new file mode 100644 index 0000000..d56e490 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-reveal.martian.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-reveal.outfit.dark.svg b/docs/assets/logo-explore/motion/svg/l-reveal.outfit.dark.svg new file mode 100644 index 0000000..4446d77 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-reveal.outfit.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-reveal.outfit.light.svg b/docs/assets/logo-explore/motion/svg/l-reveal.outfit.light.svg new file mode 100644 index 0000000..1963d84 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-reveal.outfit.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-reveal.parkinsans.dark.svg b/docs/assets/logo-explore/motion/svg/l-reveal.parkinsans.dark.svg new file mode 100644 index 0000000..20383c9 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-reveal.parkinsans.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-reveal.parkinsans.light.svg b/docs/assets/logo-explore/motion/svg/l-reveal.parkinsans.light.svg new file mode 100644 index 0000000..055f745 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-reveal.parkinsans.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-reveal.sora.dark.svg b/docs/assets/logo-explore/motion/svg/l-reveal.sora.dark.svg new file mode 100644 index 0000000..19a04cc --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-reveal.sora.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-reveal.sora.light.svg b/docs/assets/logo-explore/motion/svg/l-reveal.sora.light.svg new file mode 100644 index 0000000..e189135 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-reveal.sora.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-reveal.space.dark.svg b/docs/assets/logo-explore/motion/svg/l-reveal.space.dark.svg new file mode 100644 index 0000000..d7e6bb6 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-reveal.space.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-reveal.space.light.svg b/docs/assets/logo-explore/motion/svg/l-reveal.space.light.svg new file mode 100644 index 0000000..d1b59b9 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-reveal.space.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-reveal.syne.dark.svg b/docs/assets/logo-explore/motion/svg/l-reveal.syne.dark.svg new file mode 100644 index 0000000..8672d1b --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-reveal.syne.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-reveal.syne.light.svg b/docs/assets/logo-explore/motion/svg/l-reveal.syne.light.svg new file mode 100644 index 0000000..b23d2a4 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-reveal.syne.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-reveal.unbounded.dark.svg b/docs/assets/logo-explore/motion/svg/l-reveal.unbounded.dark.svg new file mode 100644 index 0000000..eeff105 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-reveal.unbounded.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-reveal.unbounded.light.svg b/docs/assets/logo-explore/motion/svg/l-reveal.unbounded.light.svg new file mode 100644 index 0000000..c04d1c4 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-reveal.unbounded.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-trace.dela.dark.svg b/docs/assets/logo-explore/motion/svg/l-trace.dela.dark.svg new file mode 100644 index 0000000..0e2f4eb --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-trace.dela.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-trace.dela.light.svg b/docs/assets/logo-explore/motion/svg/l-trace.dela.light.svg new file mode 100644 index 0000000..3d6cc5a --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-trace.dela.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-trace.familjen.dark.svg b/docs/assets/logo-explore/motion/svg/l-trace.familjen.dark.svg new file mode 100644 index 0000000..afa27b6 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-trace.familjen.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-trace.familjen.light.svg b/docs/assets/logo-explore/motion/svg/l-trace.familjen.light.svg new file mode 100644 index 0000000..2cc02e2 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-trace.familjen.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-trace.instrument.dark.svg b/docs/assets/logo-explore/motion/svg/l-trace.instrument.dark.svg new file mode 100644 index 0000000..c2f9ac8 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-trace.instrument.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-trace.instrument.light.svg b/docs/assets/logo-explore/motion/svg/l-trace.instrument.light.svg new file mode 100644 index 0000000..16768fe --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-trace.instrument.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-trace.martian.dark.svg b/docs/assets/logo-explore/motion/svg/l-trace.martian.dark.svg new file mode 100644 index 0000000..3113ca3 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-trace.martian.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-trace.martian.light.svg b/docs/assets/logo-explore/motion/svg/l-trace.martian.light.svg new file mode 100644 index 0000000..cc88a58 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-trace.martian.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-trace.outfit.dark.svg b/docs/assets/logo-explore/motion/svg/l-trace.outfit.dark.svg new file mode 100644 index 0000000..21a2ef6 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-trace.outfit.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-trace.outfit.light.svg b/docs/assets/logo-explore/motion/svg/l-trace.outfit.light.svg new file mode 100644 index 0000000..ded3fcd --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-trace.outfit.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-trace.parkinsans.dark.svg b/docs/assets/logo-explore/motion/svg/l-trace.parkinsans.dark.svg new file mode 100644 index 0000000..a064ce4 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-trace.parkinsans.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-trace.parkinsans.light.svg b/docs/assets/logo-explore/motion/svg/l-trace.parkinsans.light.svg new file mode 100644 index 0000000..1a54305 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-trace.parkinsans.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-trace.sora.dark.svg b/docs/assets/logo-explore/motion/svg/l-trace.sora.dark.svg new file mode 100644 index 0000000..2d80281 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-trace.sora.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-trace.sora.light.svg b/docs/assets/logo-explore/motion/svg/l-trace.sora.light.svg new file mode 100644 index 0000000..deed5dd --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-trace.sora.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-trace.space.dark.svg b/docs/assets/logo-explore/motion/svg/l-trace.space.dark.svg new file mode 100644 index 0000000..c488830 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-trace.space.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-trace.space.light.svg b/docs/assets/logo-explore/motion/svg/l-trace.space.light.svg new file mode 100644 index 0000000..222833d --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-trace.space.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-trace.syne.dark.svg b/docs/assets/logo-explore/motion/svg/l-trace.syne.dark.svg new file mode 100644 index 0000000..2d4d1ab --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-trace.syne.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-trace.syne.light.svg b/docs/assets/logo-explore/motion/svg/l-trace.syne.light.svg new file mode 100644 index 0000000..8e8945a --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-trace.syne.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-trace.unbounded.dark.svg b/docs/assets/logo-explore/motion/svg/l-trace.unbounded.dark.svg new file mode 100644 index 0000000..1c7190b --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-trace.unbounded.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-trace.unbounded.light.svg b/docs/assets/logo-explore/motion/svg/l-trace.unbounded.light.svg new file mode 100644 index 0000000..084969d --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-trace.unbounded.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-wave.dela.dark.svg b/docs/assets/logo-explore/motion/svg/l-wave.dela.dark.svg new file mode 100644 index 0000000..545fdcd --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-wave.dela.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-wave.dela.light.svg b/docs/assets/logo-explore/motion/svg/l-wave.dela.light.svg new file mode 100644 index 0000000..b01f62c --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-wave.dela.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-wave.familjen.dark.svg b/docs/assets/logo-explore/motion/svg/l-wave.familjen.dark.svg new file mode 100644 index 0000000..6dbfb2c --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-wave.familjen.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-wave.familjen.light.svg b/docs/assets/logo-explore/motion/svg/l-wave.familjen.light.svg new file mode 100644 index 0000000..5906b36 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-wave.familjen.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-wave.instrument.dark.svg b/docs/assets/logo-explore/motion/svg/l-wave.instrument.dark.svg new file mode 100644 index 0000000..fc7c750 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-wave.instrument.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-wave.instrument.light.svg b/docs/assets/logo-explore/motion/svg/l-wave.instrument.light.svg new file mode 100644 index 0000000..ebac001 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-wave.instrument.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-wave.martian.dark.svg b/docs/assets/logo-explore/motion/svg/l-wave.martian.dark.svg new file mode 100644 index 0000000..8f17e71 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-wave.martian.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-wave.martian.light.svg b/docs/assets/logo-explore/motion/svg/l-wave.martian.light.svg new file mode 100644 index 0000000..140a5bb --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-wave.martian.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-wave.outfit.dark.svg b/docs/assets/logo-explore/motion/svg/l-wave.outfit.dark.svg new file mode 100644 index 0000000..cfe6543 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-wave.outfit.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-wave.outfit.light.svg b/docs/assets/logo-explore/motion/svg/l-wave.outfit.light.svg new file mode 100644 index 0000000..f4cac42 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-wave.outfit.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-wave.parkinsans.dark.svg b/docs/assets/logo-explore/motion/svg/l-wave.parkinsans.dark.svg new file mode 100644 index 0000000..64d1ae5 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-wave.parkinsans.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-wave.parkinsans.light.svg b/docs/assets/logo-explore/motion/svg/l-wave.parkinsans.light.svg new file mode 100644 index 0000000..ee916c2 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-wave.parkinsans.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-wave.sora.dark.svg b/docs/assets/logo-explore/motion/svg/l-wave.sora.dark.svg new file mode 100644 index 0000000..aca8683 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-wave.sora.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-wave.sora.light.svg b/docs/assets/logo-explore/motion/svg/l-wave.sora.light.svg new file mode 100644 index 0000000..5faa0cc --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-wave.sora.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-wave.space.dark.svg b/docs/assets/logo-explore/motion/svg/l-wave.space.dark.svg new file mode 100644 index 0000000..8470833 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-wave.space.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-wave.space.light.svg b/docs/assets/logo-explore/motion/svg/l-wave.space.light.svg new file mode 100644 index 0000000..94c5083 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-wave.space.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-wave.syne.dark.svg b/docs/assets/logo-explore/motion/svg/l-wave.syne.dark.svg new file mode 100644 index 0000000..fe81df5 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-wave.syne.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-wave.syne.light.svg b/docs/assets/logo-explore/motion/svg/l-wave.syne.light.svg new file mode 100644 index 0000000..ce2f063 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-wave.syne.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-wave.unbounded.dark.svg b/docs/assets/logo-explore/motion/svg/l-wave.unbounded.dark.svg new file mode 100644 index 0000000..2048eab --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-wave.unbounded.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/l-wave.unbounded.light.svg b/docs/assets/logo-explore/motion/svg/l-wave.unbounded.light.svg new file mode 100644 index 0000000..3380930 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/l-wave.unbounded.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/m-connect.dark.svg b/docs/assets/logo-explore/motion/svg/m-connect.dark.svg new file mode 100644 index 0000000..6db7a53 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/m-connect.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/m-connect.light.svg b/docs/assets/logo-explore/motion/svg/m-connect.light.svg new file mode 100644 index 0000000..a2ff771 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/m-connect.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/m-draw.dark.svg b/docs/assets/logo-explore/motion/svg/m-draw.dark.svg new file mode 100644 index 0000000..2ee1157 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/m-draw.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/m-draw.light.svg b/docs/assets/logo-explore/motion/svg/m-draw.light.svg new file mode 100644 index 0000000..0223d99 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/m-draw.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/m-knock.dark.svg b/docs/assets/logo-explore/motion/svg/m-knock.dark.svg new file mode 100644 index 0000000..c3aaaa0 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/m-knock.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/m-knock.light.svg b/docs/assets/logo-explore/motion/svg/m-knock.light.svg new file mode 100644 index 0000000..b29b8a2 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/m-knock.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/m-latch.dark.svg b/docs/assets/logo-explore/motion/svg/m-latch.dark.svg new file mode 100644 index 0000000..54426aa --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/m-latch.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/m-latch.light.svg b/docs/assets/logo-explore/motion/svg/m-latch.light.svg new file mode 100644 index 0000000..86affdc --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/m-latch.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/m-morph.dark.svg b/docs/assets/logo-explore/motion/svg/m-morph.dark.svg new file mode 100644 index 0000000..61a4749 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/m-morph.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/m-morph.light.svg b/docs/assets/logo-explore/motion/svg/m-morph.light.svg new file mode 100644 index 0000000..2843b06 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/m-morph.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/m-pulse.dark.svg b/docs/assets/logo-explore/motion/svg/m-pulse.dark.svg new file mode 100644 index 0000000..566f113 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/m-pulse.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/m-pulse.light.svg b/docs/assets/logo-explore/motion/svg/m-pulse.light.svg new file mode 100644 index 0000000..3f9ea83 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/m-pulse.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/m-roll.dark.svg b/docs/assets/logo-explore/motion/svg/m-roll.dark.svg new file mode 100644 index 0000000..e4d0e83 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/m-roll.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/m-roll.light.svg b/docs/assets/logo-explore/motion/svg/m-roll.light.svg new file mode 100644 index 0000000..2cbf04a --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/m-roll.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/m-trace.dark.svg b/docs/assets/logo-explore/motion/svg/m-trace.dark.svg new file mode 100644 index 0000000..1b81931 --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/m-trace.dark.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/svg/m-trace.light.svg b/docs/assets/logo-explore/motion/svg/m-trace.light.svg new file mode 100644 index 0000000..b12e3eb --- /dev/null +++ b/docs/assets/logo-explore/motion/svg/m-trace.light.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/logo-explore/motion/typeface.html b/docs/assets/logo-explore/motion/typeface.html new file mode 100644 index 0000000..e7dd44a --- /dev/null +++ b/docs/assets/logo-explore/motion/typeface.html @@ -0,0 +1,171 @@ + + + + + +OpenGateway - typeface exploration + + + + +
+

OpenGateway - typeface exploration

+ 10 faces, SemiBold 600 where variable. Wordmarks outlined at matched cap height (40px). + + +
+
+

Wordmark specimens

+

Same cap height, so width and letterform differences read directly. Percentage is wordmark width against the Instrument Sans control.

+
+
Instrument Sans SemiBold100% width

Control. Neutral grotesque, the current Figma lockup face.

+
+
+
+
Outfit SemiBold99% width

Control. Even geometric sans.

+
+
+
+
Space Grotesk SemiBold100% width

Proportional sibling of Space Mono. Flat-spur G, mechanical a and y. Unique but usable.

+
+
+
+
Syne SemiBold112% width

Wide angular display. Distinctive a, G and w. The most expressive of the set.

+
+
+
+
Familjen Grotesk SemiBold93% width

Tight Scandinavian grotesque with quirky terminals. Shortest wordmark.

+
+
+
+
Unbounded SemiBold136% width

Wide geometric display. Bold rounded presence, widest lockup.

+
+
+
+
Sora SemiBold110% width

Futuristic tech geometric. Calm and even, cool tone.

+
+
+
+
Parkinsans SemiBold111% width

Rounded and friendly. Distinctive a and G, softest voice.

+
+
+
+
Martian Mono SemiBold99% width

Monospace wildcard. Instantly reads as CLI and gateway tooling. wdth 75 keeps it compact.

+
+
+
+
Dela Gothic One125% width

Ultra-black display statement. Heaviest and loudest, hard to pair quietly.

+
+
+ +

Lockups with the mark

+

Round-4 foot-plain arch, mark 1.15x the font size, 2% tracking. Dark and light tiles.

+
+
+

Instrument Sans SemiBold

+
+
+
+

Outfit SemiBold

+
+
+
+

Space Grotesk SemiBold

+
+
+
+

Syne SemiBold

+
+
+
+

Familjen Grotesk SemiBold

+
+
+
+

Unbounded SemiBold

+
+
+
+

Sora SemiBold

+
+
+
+

Parkinsans SemiBold

+
+
+
+

Martian Mono SemiBold

+
+
+
+

Dela Gothic One

+
+
+
+ +

Small sizes

+

Live text where the logo actually lives - nav, footer, body.

+
Instrument Sans SemiBold
+
OpenGatewayOpenGatewayOpenGateway + Routing, guardrails, audit logs. OpenAI-compatible.
+
Outfit SemiBold
+
OpenGatewayOpenGatewayOpenGateway + Routing, guardrails, audit logs. OpenAI-compatible.
+
Space Grotesk SemiBold
+
OpenGatewayOpenGatewayOpenGateway + Routing, guardrails, audit logs. OpenAI-compatible.
+
Syne SemiBold
+
OpenGatewayOpenGatewayOpenGateway + Routing, guardrails, audit logs. OpenAI-compatible.
+
Familjen Grotesk SemiBold
+
OpenGatewayOpenGatewayOpenGateway + Routing, guardrails, audit logs. OpenAI-compatible.
+
Unbounded SemiBold
+
OpenGatewayOpenGatewayOpenGateway + Routing, guardrails, audit logs. OpenAI-compatible.
+
Sora SemiBold
+
OpenGatewayOpenGatewayOpenGateway + Routing, guardrails, audit logs. OpenAI-compatible.
+
Parkinsans SemiBold
+
OpenGatewayOpenGatewayOpenGateway + Routing, guardrails, audit logs. OpenAI-compatible.
+
Martian Mono SemiBold
+
OpenGatewayOpenGatewayOpenGateway + Routing, guardrails, audit logs. OpenAI-compatible.
+
Dela Gothic One
+
OpenGatewayOpenGatewayOpenGateway + Routing, guardrails, audit logs. OpenAI-compatible.
+
+ + diff --git a/docs/assets/logo-explore/motion/typeface.py b/docs/assets/logo-explore/motion/typeface.py new file mode 100644 index 0000000..146e21d --- /dev/null +++ b/docs/assets/logo-explore/motion/typeface.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +"""OpenGateway typeface exploration page. Every face in wordmarks.json as a wordmark specimen +(matched cap height), a lockup with the round-4 foot-plain mark (dark and light), and a +small-size legibility strip in live Google Fonts text. +Run: python3 typeface.py (needs wordmarks.json from wordmark.py) +Writes typeface.html next to itself.""" +import json, os + +HERE = os.path.dirname(os.path.abspath(__file__)) +WM = json.load(open(os.path.join(HERE, "wordmarks.json"))) + +ARCH = "M6.769 26.321V14.910a9.231 9.231 0 0 1 18.462 0V16.575" # round-4 foot-plain mark (motion.py) +SW = 4.358 +DX, DY, DR = 25.231, 24.974, 3.526 +GAP = 8.5 +ORDER = ["instrument", "outfit", "space", "syne", "familjen", "unbounded", "sora", "parkinsans", "martian", "dela"] +NOTES = { + "instrument": "Control. Neutral grotesque, the current Figma lockup face.", + "outfit": "Control. Even geometric sans.", + "space": "Proportional sibling of Space Mono. Flat-spur G, mechanical a and y. Unique but usable.", + "syne": "Wide angular display. Distinctive a, G and w. The most expressive of the set.", + "familjen": "Tight Scandinavian grotesque with quirky terminals. Shortest wordmark.", + "unbounded": "Wide geometric display. Bold rounded presence, widest lockup.", + "sora": "Futuristic tech geometric. Calm and even, cool tone.", + "parkinsans": "Rounded and friendly. Distinctive a and G, softest voice.", + "martian": "Monospace wildcard. Instantly reads as CLI and gateway tooling. wdth 75 keeps it compact.", + "dela": "Ultra-black display statement. Heaviest and loudest, hard to pair quietly.", +} +FAMILY = {"instrument": "Instrument Sans", "outfit": "Outfit", "space": "Space Grotesk", "syne": "Syne", + "familjen": "Familjen Grotesk", "unbounded": "Unbounded", "sora": "Sora", + "parkinsans": "Parkinsans", "martian": "Martian Mono", "dela": "Dela Gothic One"} +GF = ("family=Instrument+Sans:wght@600&family=Outfit:wght@600&family=Space+Grotesk:wght@600&" + "family=Syne:wght@600&family=Familjen+Grotesk:wght@600&family=Unbounded:wght@600&" + "family=Sora:wght@600&family=Parkinsans:wght@600&family=Martian+Mono:wght@600&" + "family=Dela+Gothic+One&display=swap") + +def letters_svg(face, cls): + return "".join(f'' for l in WM[face]["letters"] if l["d"]) + +def specimen_svg(face, cap_px=40): + w = WM[face]; sc = cap_px / w["cap"] + bw, bh = w["maxx"] - w["minx"], w["maxy"] - w["miny"] + return (f'{letters_svg(face, "spec")}') + +def lockup_parts(face): + w = WM[face] + tx = 27.41 + GAP - w["minx"] + x0, x1 = 2.5, 27.41 + GAP + (w["maxx"] - w["minx"]) + 2.1 + y0, y1 = 1.4, max(w["maxy"], 28.5) + 2.1 + body = (f'' + f'{letters_svg(face, "mk-l")}') + return body, (x0, y0, x1 - x0, y1 - y0) + +def lockup_svg(face, theme, h=64): + body, (x, y, w, hh) = lockup_parts(face) + return (f'{body}') + +def rows(): + base = WM["instrument"]["advance"] + out = [] + for f in ORDER: + w = WM[f] + pct = round(w["advance"] / base * 100) + out.append(f"""
+
{w["label"]}{pct}% width

{NOTES[f]}

+
{specimen_svg(f)}
+
""") + return "\n".join(out) + +def lockups(): + out = [] + for f in ORDER: + out.append(f"""
+

{WM[f]["label"]}

+
{lockup_svg(f, "dark")}{lockup_svg(f, "light")}
+
""") + return "\n".join(out) + +def sizes(): + out = [] + for f in ORDER: + fam = FAMILY[f] + txt = "".join(f'OpenGateway' for s in (20, 16, 13)) + out.append(f"""
{WM[f]["label"]}
+
{txt} + Routing, guardrails, audit logs. OpenAI-compatible.
""") + return "\n".join(out) + +HTML = f""" + + + + +OpenGateway - typeface exploration + + + + +
+

OpenGateway - typeface exploration

+ 10 faces, SemiBold 600 where variable. Wordmarks outlined at matched cap height (40px). + + +
+
+

Wordmark specimens

+

Same cap height, so width and letterform differences read directly. Percentage is wordmark width against the Instrument Sans control.

+{rows()} + +

Lockups with the mark

+

Round-4 foot-plain arch, mark 1.15x the font size, 2% tracking. Dark and light tiles.

+
+{lockups()} +
+ +

Small sizes

+

Live text where the logo actually lives - nav, footer, body.

+{sizes()} +
+ + +""" +open(os.path.join(HERE, "typeface.html"), "w").write(HTML) +print("wrote typeface.html", len(HTML), "bytes,", len(ORDER), "faces") diff --git a/docs/assets/logo-explore/motion/video/l-open.mp4 b/docs/assets/logo-explore/motion/video/l-open.mp4 new file mode 100644 index 0000000..b76642d Binary files /dev/null and b/docs/assets/logo-explore/motion/video/l-open.mp4 differ diff --git a/docs/assets/logo-explore/motion/video/m-connect-sheet.png b/docs/assets/logo-explore/motion/video/m-connect-sheet.png new file mode 100644 index 0000000..7bdae04 Binary files /dev/null and b/docs/assets/logo-explore/motion/video/m-connect-sheet.png differ diff --git a/docs/assets/logo-explore/motion/video/m-connect.gif b/docs/assets/logo-explore/motion/video/m-connect.gif new file mode 100644 index 0000000..550c15d Binary files /dev/null and b/docs/assets/logo-explore/motion/video/m-connect.gif differ diff --git a/docs/assets/logo-explore/motion/video/m-connect.mp4 b/docs/assets/logo-explore/motion/video/m-connect.mp4 new file mode 100644 index 0000000..42ae606 Binary files /dev/null and b/docs/assets/logo-explore/motion/video/m-connect.mp4 differ diff --git a/docs/assets/logo-explore/motion/video/m-morph.mp4 b/docs/assets/logo-explore/motion/video/m-morph.mp4 new file mode 100644 index 0000000..b62005d Binary files /dev/null and b/docs/assets/logo-explore/motion/video/m-morph.mp4 differ diff --git a/docs/assets/logo-explore/motion/wordmark.py b/docs/assets/logo-explore/motion/wordmark.py new file mode 100644 index 0000000..d87f8a8 --- /dev/null +++ b/docs/assets/logo-explore/motion/wordmark.py @@ -0,0 +1,66 @@ +"""Shape 'OpenGateway' with real kerning and write per-letter SVG paths (32-unit mark scale) to wordmarks.json. +Run: uv run --with fonttools --with uharfbuzz python wordmark.py +Fonts are SIL OFL, fetched from github.com/google/fonts.""" +import subprocess, json, io +import uharfbuzz as hb +from fontTools.ttLib import TTFont +from fontTools.varLib import instancer +from fontTools.pens.svgPathPen import SVGPathPen +from fontTools.pens.transformPen import TransformPen +from fontTools.pens.boundsPen import BoundsPen + +TEXT = "OpenGateway" +FACES = { + "instrument": ("Instrument Sans SemiBold", "InstrumentSans[wdth,wght].ttf", "instrumentsans", {"wght": 600, "wdth": 100}), + "outfit": ("Outfit SemiBold", "Outfit[wght].ttf", "outfit", {"wght": 600}), + "space": ("Space Grotesk SemiBold", "SpaceGrotesk[wght].ttf", "spacegrotesk", {"wght": 600}), + "syne": ("Syne SemiBold", "Syne[wght].ttf", "syne", {"wght": 600}), + "familjen": ("Familjen Grotesk SemiBold", "FamiljenGrotesk[wght].ttf", "familjengrotesk", {"wght": 600}), + "unbounded": ("Unbounded SemiBold", "Unbounded[wght].ttf", "unbounded", {"wght": 600}), + "sora": ("Sora SemiBold", "Sora[wght].ttf", "sora", {"wght": 600}), + "parkinsans": ("Parkinsans SemiBold", "Parkinsans[wght].ttf", "parkinsans", {"wght": 600}), + "martian": ("Martian Mono SemiBold", "MartianMono[wdth,wght].ttf", "martianmono", {"wght": 600, "wdth": 75}), + "dela": ("Dela Gothic One", "DelaGothicOne-Regular.ttf", "delagothicone", {}), +} +EXTRA = { # exploration faces (Outfit deep-dive), written to wordmarks-extra.json + "outfit500": ("Outfit Medium", "Outfit[wght].ttf", "outfit", {"wght": 500}), + "outfit700": ("Outfit Bold", "Outfit[wght].ttf", "outfit", {"wght": 700}), + "outfit800": ("Outfit ExtraBold", "Outfit[wght].ttf", "outfit", {"wght": 800}), + "urbanist": ("Urbanist SemiBold", "Urbanist[wght].ttf", "urbanist", {"wght": 600}), + "figtree": ("Figtree SemiBold", "Figtree[wght].ttf", "figtree", {"wght": 600}), + "jost": ("Jost SemiBold", "Jost[wght].ttf", "jost", {"wght": 600}), + "onest": ("Onest SemiBold", "Onest[wght].ttf", "onest", {"wght": 600}), + "hanken": ("Hanken Grotesk SemiBold", "HankenGrotesk[wght].ttf", "hankengrotesk", {"wght": 600}), + "poppins": ("Poppins SemiBold", "Poppins-SemiBold.ttf", "poppins", {}), +} +FS = 32 / 1.15 # font size in mark units, so the mark is 1.15x the font size (as in the Figma lockups) +TRACK = -0.02 # -2% letter spacing +out = {} +for key, (label, fname, folder, axes) in {**FACES, **EXTRA}.items(): + raw = subprocess.run(["gh", "api", f"repos/google/fonts/contents/ofl/{folder}/{fname}", "-H", "Accept: application/vnd.github.raw"], capture_output=True).stdout + font = TTFont(io.BytesIO(raw)) + if axes and "fvar" in font: + font = instancer.instantiateVariableFont(font, axes) + buf = io.BytesIO(); font.save(buf); data = buf.getvalue() + upem = font["head"].unitsPerEm; s = FS / upem + face = hb.Face(data); hbfont = hb.Font(face) + b = hb.Buffer(); b.add_str(TEXT); b.guess_segment_properties(); hb.shape(hbfont, b, {"kern": True, "liga": False}) + gs = font.getGlyphSet(); order = font.getGlyphOrder() + cap = getattr(font["OS/2"], "sCapHeight", 0.7 * upem) * s + baseline = 16 + cap / 2 # centre the cap height on the mark centre line + x = 0.0; letters = [] + for i, (info, pos) in enumerate(zip(b.glyph_infos, b.glyph_positions)): + name = order[info.codepoint] + pen = SVGPathPen(gs, ntos=lambda v: f"{v:.2f}".rstrip("0").rstrip(".")) + gx = x + pos.x_offset * s + gs[name].draw(TransformPen(pen, (s, 0, 0, -s, gx, baseline))) + bp = BoundsPen(gs); gs[name].draw(TransformPen(bp, (s, 0, 0, -s, gx, baseline))) + letters.append({"ch": TEXT[i], "d": pen.getCommands(), "x": round(gx, 2), "bounds": [round(v, 2) for v in bp.bounds] if bp.bounds else None}) + x += pos.x_advance * s + TRACK * FS + x -= TRACK * FS + bs = [l["bounds"] for l in letters if l["bounds"]] + out[key] = {"label": label, "fs": round(FS, 2), "baseline": round(baseline, 2), "advance": round(x, 2), "cap": round(cap, 2), + "minx": min(v[0] for v in bs), "maxx": max(v[2] for v in bs), "miny": min(v[1] for v in bs), "maxy": max(v[3] for v in bs), "letters": letters} + print(key, label, "width", round(x, 1), "y range", round(out[key]["miny"], 1), round(out[key]["maxy"], 1), "baseline", round(baseline, 1)) +json.dump({k: out[k] for k in FACES}, open("wordmarks.json", "w")) +json.dump({k: out[k] for k in EXTRA}, open("wordmarks-extra.json", "w")) diff --git a/docs/assets/logo-explore/motion/wordmarks-extra.json b/docs/assets/logo-explore/motion/wordmarks-extra.json new file mode 100644 index 0000000..3a48d61 --- /dev/null +++ b/docs/assets/logo-explore/motion/wordmarks-extra.json @@ -0,0 +1 @@ +{"outfit500": {"label": "Outfit Medium", "fs": 27.83, "baseline": 25.74, "advance": 176.5, "cap": 19.48, "minx": 1.11, "maxx": 176.19, "miny": 5.98, "maxy": 31.3, "letters": [{"ch": "O", "d": "M11.07 26.02Q8.99 26.02 7.18 25.24Q5.37 24.46 4.01 23.08Q2.64 21.7 1.88 19.88Q1.11 18.06 1.11 15.97Q1.11 13.89 1.88 12.08Q2.64 10.27 3.99 8.89Q5.34 7.51 7.14 6.75Q8.93 5.98 11.02 5.98Q13.11 5.98 14.9 6.75Q16.7 7.51 18.06 8.89Q19.42 10.27 20.19 12.09Q20.95 13.91 20.95 16Q20.95 18.09 20.19 19.91Q19.42 21.73 18.07 23.1Q16.72 24.46 14.93 25.24Q13.13 26.02 11.07 26.02ZM11.02 22.96Q12.99 22.96 14.48 22.07Q15.97 21.18 16.82 19.6Q17.67 18.03 17.67 15.97Q17.67 14.44 17.18 13.18Q16.7 11.91 15.81 10.98Q14.91 10.05 13.7 9.54Q12.49 9.04 11.02 9.04Q9.07 9.04 7.58 9.92Q6.09 10.8 5.25 12.35Q4.4 13.91 4.4 15.97Q4.4 17.5 4.88 18.8Q5.37 20.09 6.25 21.01Q7.12 21.93 8.35 22.44Q9.57 22.96 11.02 22.96Z", "x": 0.0, "bounds": [1.11, 5.98, 20.95, 26.02]}, {"ch": "p", "d": "M30.39 26.02Q28.86 26.02 27.66 25.36Q26.46 24.71 25.78 23.57Q25.1 22.43 25.1 20.98V17.14Q25.1 15.69 25.79 14.55Q26.49 13.41 27.69 12.76Q28.88 12.1 30.39 12.1Q32.19 12.1 33.66 13.04Q35.12 13.97 35.97 15.54Q36.81 17.11 36.81 19.09Q36.81 21.04 35.97 22.61Q35.12 24.18 33.66 25.1Q32.19 26.02 30.39 26.02ZM23.23 31.3V12.38H26.3V15.97L25.77 19.23L26.3 22.46V31.3ZM29.89 23.12Q31 23.12 31.85 22.61Q32.7 22.09 33.18 21.18Q33.67 20.26 33.67 19.06Q33.67 17.86 33.18 16.95Q32.7 16.03 31.85 15.51Q31 15 29.89 15Q28.77 15 27.91 15.51Q27.05 16.03 26.57 16.95Q26.1 17.86 26.1 19.06Q26.1 20.26 26.57 21.18Q27.05 22.09 27.91 22.61Q28.77 23.12 29.89 23.12Z", "x": 21.54, "bounds": [23.23, 12.1, 36.81, 31.3]}, {"ch": "e", "d": "M45.02 26.02Q43.02 26.02 41.42 25.11Q39.82 24.21 38.89 22.62Q37.95 21.04 37.95 19.06Q37.95 17.09 38.87 15.51Q39.79 13.94 41.36 13.02Q42.94 12.1 44.86 12.1Q46.72 12.1 48.15 12.97Q49.59 13.83 50.41 15.33Q51.23 16.83 51.23 18.75Q51.23 19.09 51.19 19.44Q51.14 19.78 51.06 20.2H40.1V17.7H49.47L48.33 18.7Q48.28 17.47 47.86 16.64Q47.44 15.81 46.68 15.36Q45.91 14.91 44.8 14.91Q43.63 14.91 42.77 15.42Q41.91 15.92 41.43 16.82Q40.96 17.73 40.96 18.98Q40.96 20.23 41.46 21.18Q41.96 22.12 42.88 22.64Q43.8 23.15 44.99 23.15Q46.02 23.15 46.9 22.79Q47.78 22.43 48.39 21.73L50.34 23.71Q49.36 24.85 47.97 25.43Q46.58 26.02 45.02 26.02Z", "x": 37.12, "bounds": [37.95, 12.1, 51.23, 26.02]}, {"ch": "n", "d": "M62.27 25.74V18.03Q62.27 16.7 61.43 15.83Q60.58 14.97 59.25 14.97Q58.34 14.97 57.65 15.36Q56.96 15.75 56.57 16.45Q56.18 17.14 56.18 18.03L54.98 17.36Q54.98 15.83 55.65 14.65Q56.32 13.47 57.5 12.79Q58.68 12.1 60.17 12.1Q61.66 12.1 62.83 12.87Q64 13.63 64.67 14.85Q65.34 16.06 65.34 17.36V25.74ZM53.12 25.74V12.38H56.18V25.74Z", "x": 51.42, "bounds": [53.12, 12.1, 65.34, 25.74]}, {"ch": "G", "d": "M77.11 26.02Q75.1 26.02 73.32 25.24Q71.54 24.46 70.21 23.08Q68.87 21.7 68.1 19.88Q67.34 18.06 67.34 15.97Q67.34 13.89 68.1 12.08Q68.87 10.27 70.22 8.89Q71.57 7.51 73.39 6.75Q75.21 5.98 77.33 5.98Q79.64 5.98 81.61 6.87Q83.59 7.76 84.93 9.41L82.75 11.58Q81.89 10.35 80.45 9.7Q79 9.04 77.33 9.04Q75.33 9.04 73.81 9.92Q72.29 10.8 71.46 12.35Q70.62 13.91 70.62 15.97Q70.62 18.03 71.47 19.6Q72.32 21.18 73.77 22.07Q75.21 22.96 77.05 22.96Q79 22.96 80.4 22.25Q81.81 21.54 82.56 20.13Q83.31 18.73 83.31 16.67L85.31 18.11H76.99V15.19H86.62V15.64Q86.62 19.12 85.38 21.43Q84.15 23.74 82 24.88Q79.86 26.02 77.11 26.02Z", "x": 66.2, "bounds": [67.34, 5.98, 86.62, 26.02]}, {"ch": "a", "d": "M94.41 26.02Q92.61 26.02 91.14 25.1Q89.68 24.18 88.83 22.61Q87.99 21.04 87.99 19.09Q87.99 17.11 88.83 15.54Q89.68 13.97 91.14 13.04Q92.61 12.1 94.41 12.1Q95.94 12.1 97.13 12.76Q98.31 13.41 99.01 14.55Q99.7 15.69 99.7 17.14V20.98Q99.7 22.43 99.02 23.57Q98.34 24.71 97.14 25.36Q95.94 26.02 94.41 26.02ZM94.91 23.12Q96.61 23.12 97.66 21.98Q98.7 20.84 98.7 19.06Q98.7 17.86 98.23 16.95Q97.75 16.03 96.9 15.51Q96.06 15 94.91 15Q93.8 15 92.95 15.51Q92.1 16.03 91.62 16.95Q91.13 17.86 91.13 19.06Q91.13 20.26 91.62 21.18Q92.1 22.09 92.95 22.61Q93.8 23.12 94.91 23.12ZM98.5 25.74V22.15L99.03 18.89L98.5 15.67V12.38H101.57V25.74Z", "x": 87.12, "bounds": [87.99, 12.1, 101.57, 26.02]}, {"ch": "t", "d": "M106.43 25.74V6.82H109.5V25.74ZM103.23 15.17V12.38H112.7V15.17Z", "x": 102.71, "bounds": [103.23, 6.82, 112.7, 25.74]}, {"ch": "e", "d": "M120.18 26.02Q118.18 26.02 116.58 25.11Q114.98 24.21 114.05 22.62Q113.11 21.04 113.11 19.06Q113.11 17.09 114.03 15.51Q114.95 13.94 116.52 13.02Q118.09 12.1 120.01 12.1Q121.88 12.1 123.31 12.97Q124.74 13.83 125.57 15.33Q126.39 16.83 126.39 18.75Q126.39 19.09 126.34 19.44Q126.3 19.78 126.22 20.2H115.26V17.7H124.63L123.49 18.7Q123.44 17.47 123.02 16.64Q122.6 15.81 121.84 15.36Q121.07 14.91 119.96 14.91Q118.79 14.91 117.93 15.42Q117.06 15.92 116.59 16.82Q116.12 17.73 116.12 18.98Q116.12 20.23 116.62 21.18Q117.12 22.12 118.04 22.64Q118.96 23.15 120.15 23.15Q121.18 23.15 122.06 22.79Q122.94 22.43 123.55 21.73L125.5 23.71Q124.52 24.85 123.13 25.43Q121.74 26.02 120.18 26.02Z", "x": 112.28, "bounds": [113.11, 12.1, 126.39, 26.02]}, {"ch": "w", "d": "M132.01 25.74 126.64 12.38H129.81L133.43 22.09L132.48 22.12L136.01 12.38H137.79L141.33 22.12L140.38 22.09L144 12.38H147.17L141.8 25.74H140.02L136.4 16.5H137.41L133.79 25.74Z", "x": 126.41, "bounds": [126.64, 12.38, 147.17, 25.74]}, {"ch": "a", "d": "M153.79 26.02Q151.99 26.02 150.53 25.1Q149.06 24.18 148.22 22.61Q147.37 21.04 147.37 19.09Q147.37 17.11 148.22 15.54Q149.06 13.97 150.53 13.04Q151.99 12.1 153.79 12.1Q155.33 12.1 156.51 12.76Q157.69 13.41 158.39 14.55Q159.08 15.69 159.08 17.14V20.98Q159.08 22.43 158.4 23.57Q157.72 24.71 156.52 25.36Q155.33 26.02 153.79 26.02ZM154.3 23.12Q155.99 23.12 157.04 21.98Q158.08 20.84 158.08 19.06Q158.08 17.86 157.61 16.95Q157.13 16.03 156.29 15.51Q155.44 15 154.3 15Q153.18 15 152.33 15.51Q151.49 16.03 151 16.95Q150.51 17.86 150.51 19.06Q150.51 20.26 151 21.18Q151.49 22.09 152.33 22.61Q153.18 23.12 154.3 23.12ZM157.89 25.74V22.15L158.41 18.89L157.89 15.67V12.38H160.95V25.74Z", "x": 146.5, "bounds": [147.37, 12.1, 160.95, 26.02]}, {"ch": "y", "d": "M168.1 25.88 162.39 12.38H165.76L169.66 22.29H168.68L172.8 12.38H176.19L169.99 25.88ZM164.12 31.3 168.26 22.76 169.99 25.88 167.51 31.3Z", "x": 162.09, "bounds": [162.39, 12.38, 176.19, 31.3]}]}, "outfit700": {"label": "Outfit Bold", "fs": 27.83, "baseline": 25.82, "advance": 181.87, "cap": 19.65, "minx": 0.88, "maxx": 181.75, "miny": 5.84, "maxy": 31.53, "letters": [{"ch": "O", "d": "M11.26 26.16Q9.04 26.16 7.16 25.38Q5.27 24.6 3.86 23.21Q2.45 21.83 1.66 19.97Q0.88 18.11 0.88 15.97Q0.88 13.82 1.66 11.98Q2.43 10.14 3.83 8.76Q5.23 7.37 7.11 6.61Q8.99 5.84 11.2 5.84Q13.41 5.84 15.29 6.61Q17.17 7.37 18.57 8.76Q19.98 10.14 20.76 11.99Q21.54 13.84 21.54 16Q21.54 18.14 20.76 19.99Q19.98 21.84 18.58 23.23Q17.18 24.61 15.31 25.38Q13.44 26.16 11.26 26.16ZM11.2 22.18Q12.97 22.18 14.28 21.4Q15.6 20.62 16.33 19.22Q17.06 17.82 17.06 15.97Q17.06 14.59 16.64 13.47Q16.22 12.34 15.44 11.52Q14.66 10.7 13.59 10.26Q12.52 9.82 11.2 9.82Q9.45 9.82 8.13 10.59Q6.82 11.35 6.09 12.74Q5.36 14.12 5.36 15.97Q5.36 17.36 5.77 18.51Q6.19 19.66 6.96 20.47Q7.74 21.29 8.81 21.73Q9.89 22.18 11.2 22.18Z", "x": 0.0, "bounds": [0.88, 5.84, 21.54, 26.16]}, {"ch": "p", "d": "M31.17 26.1Q29.79 26.1 28.65 25.57Q27.51 25.04 26.79 24.11Q26.07 23.18 25.96 21.98V16.28Q26.07 15.08 26.8 14.11Q27.52 13.15 28.66 12.58Q29.8 12.02 31.17 12.02Q33.09 12.02 34.57 12.94Q36.06 13.86 36.91 15.45Q37.76 17.04 37.76 19.07Q37.76 21.09 36.91 22.68Q36.06 24.26 34.57 25.18Q33.09 26.1 31.17 26.1ZM23.37 31.53V12.3H27.63V15.77L26.92 19.06L27.56 22.34V31.53ZM30.39 22.26Q31.3 22.26 31.98 21.85Q32.67 21.44 33.06 20.72Q33.45 19.99 33.45 19.06Q33.45 18.11 33.06 17.4Q32.67 16.68 31.99 16.27Q31.3 15.86 30.41 15.86Q29.52 15.86 28.83 16.27Q28.15 16.67 27.76 17.39Q27.38 18.11 27.38 19.06Q27.38 19.99 27.76 20.72Q28.13 21.44 28.82 21.85Q29.51 22.26 30.39 22.26Z", "x": 21.87, "bounds": [23.37, 12.02, 37.76, 31.53]}, {"ch": "e", "d": "M46.04 26.13Q43.84 26.13 42.14 25.22Q40.43 24.32 39.46 22.71Q38.48 21.11 38.48 19.06Q38.48 17.03 39.44 15.44Q40.39 13.84 42.04 12.92Q43.69 11.99 45.73 11.99Q47.74 11.99 49.27 12.86Q50.81 13.73 51.69 15.26Q52.56 16.79 52.56 18.75Q52.56 19.13 52.52 19.53Q52.48 19.94 52.37 20.45L40.71 20.49V17.57L50.56 17.53L48.72 18.77Q48.7 17.6 48.35 16.83Q48.01 16.06 47.37 15.65Q46.72 15.25 45.76 15.25Q44.76 15.25 44.02 15.71Q43.28 16.17 42.89 17.01Q42.49 17.85 42.49 19.03Q42.49 20.23 42.91 21.09Q43.34 21.94 44.13 22.39Q44.93 22.85 46.02 22.85Q47.03 22.85 47.83 22.5Q48.64 22.16 49.25 21.47L51.59 23.81Q50.59 24.96 49.17 25.54Q47.75 26.13 46.04 26.13Z", "x": 37.87, "bounds": [38.48, 11.99, 52.56, 26.13]}, {"ch": "n", "d": "M63 25.82V18.13Q63 17.06 62.34 16.41Q61.69 15.76 60.68 15.76Q59.99 15.76 59.46 16.06Q58.92 16.35 58.62 16.88Q58.32 17.42 58.32 18.13L56.67 17.32Q56.67 15.72 57.37 14.53Q58.07 13.34 59.31 12.68Q60.55 12.02 62.11 12.02Q63.6 12.02 64.76 12.73Q65.92 13.44 66.59 14.61Q67.26 15.78 67.26 17.17V25.82ZM54.07 25.82V12.3H58.32V25.82Z", "x": 52.56, "bounds": [54.07, 12.02, 67.26, 25.82]}, {"ch": "G", "d": "M79.08 26.13Q76.93 26.13 75.05 25.36Q73.18 24.58 71.78 23.2Q70.39 21.82 69.6 19.97Q68.81 18.13 68.81 15.99Q68.81 13.83 69.62 11.99Q70.43 10.16 71.86 8.78Q73.29 7.4 75.21 6.64Q77.12 5.87 79.32 5.87Q81.85 5.87 83.93 6.79Q86.01 7.71 87.36 9.34L84.4 12.3Q83.45 11.09 82.17 10.47Q80.89 9.85 79.28 9.85Q77.54 9.85 76.18 10.62Q74.82 11.38 74.05 12.76Q73.28 14.14 73.28 15.97Q73.28 17.81 74.03 19.2Q74.78 20.59 76.09 21.37Q77.4 22.15 79.1 22.15Q80.82 22.15 82.04 21.5Q83.26 20.86 83.9 19.61Q84.55 18.37 84.55 16.6L87.46 18.53L78.69 18.39V14.71H89V15.35Q89 18.87 87.74 21.27Q86.47 23.68 84.24 24.9Q82 26.13 79.08 26.13Z", "x": 67.92, "bounds": [68.81, 5.87, 89.0, 26.13]}, {"ch": "a", "d": "M96.36 26.1Q94.5 26.1 93.03 25.18Q91.56 24.26 90.72 22.68Q89.88 21.09 89.88 19.07Q89.88 17.04 90.72 15.45Q91.56 13.86 93.03 12.94Q94.5 12.02 96.36 12.02Q97.73 12.02 98.82 12.55Q99.92 13.08 100.61 14.01Q101.3 14.94 101.4 16.14V21.98Q101.3 23.18 100.62 24.11Q99.94 25.04 98.83 25.57Q97.73 26.1 96.36 26.1ZM97.22 22.26Q98.59 22.26 99.42 21.36Q100.26 20.47 100.26 19.06Q100.26 18.11 99.88 17.4Q99.51 16.68 98.82 16.27Q98.14 15.86 97.24 15.86Q96.35 15.86 95.67 16.27Q94.98 16.68 94.59 17.41Q94.19 18.13 94.19 19.06Q94.19 19.99 94.58 20.72Q94.97 21.44 95.66 21.85Q96.35 22.26 97.22 22.26ZM100.08 25.82V22.19L100.72 18.89L100.08 15.62V12.3H104.26V25.82Z", "x": 89.21, "bounds": [89.88, 12.02, 104.26, 26.1]}, {"ch": "t", "d": "M108.63 25.82V6.71H112.89V25.82ZM105.57 15.92V12.3H115.95V15.92Z", "x": 105.21, "bounds": [105.57, 6.71, 115.95, 25.82]}, {"ch": "e", "d": "M123.65 26.13Q121.45 26.13 119.74 25.22Q118.04 24.32 117.06 22.71Q116.09 21.11 116.09 19.06Q116.09 17.03 117.04 15.44Q118 13.84 119.65 12.92Q121.29 11.99 123.34 11.99Q125.34 11.99 126.88 12.86Q128.42 13.73 129.29 15.26Q130.17 16.79 130.17 18.75Q130.17 19.13 130.13 19.53Q130.09 19.94 129.98 20.45L118.32 20.49V17.57L128.17 17.53L126.33 18.77Q126.3 17.6 125.96 16.83Q125.62 16.06 124.97 15.65Q124.33 15.25 123.37 15.25Q122.37 15.25 121.63 15.71Q120.89 16.17 120.49 17.01Q120.1 17.85 120.1 19.03Q120.1 20.23 120.52 21.09Q120.95 21.94 121.74 22.39Q122.53 22.85 123.63 22.85Q124.63 22.85 125.44 22.5Q126.25 22.16 126.86 21.47L129.2 23.81Q128.19 24.96 126.78 25.54Q125.36 26.13 123.65 26.13Z", "x": 115.48, "bounds": [116.09, 11.99, 130.17, 26.13]}, {"ch": "w", "d": "M134.8 25.82 130.13 12.3H134.3L137.21 22.14L135.97 22.16L139.17 12.3H142.64L145.85 22.16L144.6 22.14L147.53 12.3H151.71L147.03 25.82H143.54L140.4 16.47H141.45L138.27 25.82Z", "x": 130.0, "bounds": [130.13, 12.3, 151.71, 25.82]}, {"ch": "a", "d": "M158.16 26.1Q156.3 26.1 154.83 25.18Q153.36 24.26 152.52 22.68Q151.68 21.09 151.68 19.07Q151.68 17.04 152.52 15.45Q153.36 13.86 154.83 12.94Q156.3 12.02 158.16 12.02Q159.53 12.02 160.63 12.55Q161.73 13.08 162.41 14.01Q163.1 14.94 163.2 16.14V21.98Q163.1 23.18 162.42 24.11Q161.74 25.04 160.63 25.57Q159.53 26.1 158.16 26.1ZM159.03 22.26Q160.39 22.26 161.22 21.36Q162.06 20.47 162.06 19.06Q162.06 18.11 161.68 17.4Q161.31 16.68 160.63 16.27Q159.94 15.86 159.04 15.86Q158.15 15.86 157.47 16.27Q156.79 16.68 156.39 17.41Q155.99 18.13 155.99 19.06Q155.99 19.99 156.38 20.72Q156.77 21.44 157.46 21.85Q158.15 22.26 159.03 22.26ZM161.88 25.82V22.19L162.52 18.89L161.88 15.62V12.3H166.07V25.82Z", "x": 151.01, "bounds": [151.68, 12.02, 166.07, 26.1]}, {"ch": "y", "d": "M172.67 25.89 167.12 12.3H171.7L175.23 23.08H173.55L177.17 12.3H181.75L175.96 25.89ZM169.2 31.53 173.26 22.76 175.96 25.89 173.57 31.53Z", "x": 167.01, "bounds": [167.12, 12.3, 181.75, 31.53]}]}, "outfit800": {"label": "Outfit ExtraBold", "fs": 27.83, "baseline": 25.86, "advance": 184.51, "cap": 19.73, "minx": 0.76, "maxx": 184.51, "miny": 5.77, "maxy": 31.64, "letters": [{"ch": "O", "d": "M11.35 26.23Q9.07 26.23 7.15 25.45Q5.22 24.67 3.79 23.28Q2.35 21.89 1.55 20.02Q0.76 18.14 0.76 15.97Q0.76 13.78 1.54 11.93Q2.33 10.08 3.75 8.69Q5.18 7.3 7.1 6.54Q9.02 5.77 11.29 5.77Q13.57 5.77 15.49 6.54Q17.41 7.3 18.83 8.69Q20.26 10.08 21.04 11.94Q21.83 13.81 21.83 16Q21.83 18.17 21.04 20.03Q20.26 21.9 18.83 23.29Q17.41 24.69 15.5 25.46Q13.59 26.23 11.35 26.23ZM11.29 21.79Q12.95 21.79 14.18 21.06Q15.41 20.34 16.08 19.03Q16.75 17.72 16.75 15.97Q16.75 14.67 16.37 13.61Q15.99 12.56 15.26 11.79Q14.54 11.03 13.54 10.62Q12.54 10.21 11.29 10.21Q9.63 10.21 8.41 10.92Q7.18 11.63 6.51 12.93Q5.84 14.23 5.84 15.97Q5.84 17.29 6.22 18.37Q6.6 19.44 7.32 20.21Q8.04 20.97 9.05 21.38Q10.05 21.79 11.29 21.79Z", "x": 0.0, "bounds": [0.76, 5.77, 21.83, 26.23]}, {"ch": "p", "d": "M31.55 26.14Q30.25 26.14 29.14 25.68Q28.03 25.21 27.29 24.38Q26.56 23.55 26.39 22.48V15.85Q26.56 14.78 27.3 13.9Q28.03 13.02 29.15 12.5Q30.26 11.98 31.55 11.98Q33.53 11.98 35.03 12.89Q36.54 13.8 37.38 15.41Q38.23 17.01 38.23 19.07Q38.23 21.12 37.38 22.71Q36.54 24.31 35.03 25.22Q33.53 26.14 31.55 26.14ZM23.44 31.64V12.26H28.3V15.67L27.5 18.98L28.19 22.29V31.64ZM30.65 21.83Q31.44 21.83 32.05 21.47Q32.65 21.11 32.99 20.49Q33.34 19.86 33.34 19.05Q33.34 18.24 32.99 17.62Q32.65 17.01 32.05 16.65Q31.45 16.29 30.68 16.29Q29.9 16.29 29.3 16.64Q28.7 17 28.36 17.62Q28.02 18.24 28.02 19.06Q28.02 19.86 28.35 20.49Q28.67 21.11 29.28 21.47Q29.88 21.83 30.65 21.83Z", "x": 22.04, "bounds": [23.44, 11.98, 38.23, 31.64]}, {"ch": "e", "d": "M46.56 26.18Q44.26 26.18 42.51 25.28Q40.75 24.38 39.76 22.76Q38.76 21.14 38.76 19.06Q38.76 17 39.73 15.4Q40.7 13.79 42.39 12.87Q44.08 11.94 46.18 11.94Q48.26 11.94 49.85 12.81Q51.44 13.68 52.34 15.23Q53.25 16.77 53.25 18.75Q53.25 19.15 53.2 19.58Q53.16 20.01 53.04 20.58L41.03 20.64V17.51L51.12 17.45L48.93 18.8Q48.92 17.66 48.62 16.92Q48.31 16.18 47.73 15.8Q47.14 15.42 46.25 15.42Q45.34 15.42 44.66 15.85Q43.99 16.29 43.63 17.1Q43.27 17.91 43.27 19.06Q43.27 20.23 43.66 21.04Q44.04 21.85 44.77 22.27Q45.5 22.69 46.55 22.69Q47.54 22.69 48.31 22.36Q49.09 22.03 49.7 21.34L52.23 23.85Q51.21 25.02 49.78 25.6Q48.35 26.18 46.56 26.18Z", "x": 38.26, "bounds": [38.76, 11.94, 53.25, 26.18]}, {"ch": "n", "d": "M63.35 25.86V18.18Q63.35 17.24 62.79 16.7Q62.23 16.16 61.38 16.16Q60.8 16.16 60.34 16.4Q59.89 16.65 59.63 17.1Q59.38 17.56 59.38 18.18L57.5 17.3Q57.5 15.67 58.22 14.47Q58.94 13.28 60.2 12.63Q61.47 11.98 63.07 11.98Q64.55 11.98 65.71 12.66Q66.87 13.34 67.53 14.49Q68.2 15.64 68.2 17.07V25.86ZM54.53 25.86V12.26H59.38V25.86Z", "x": 53.12, "bounds": [54.53, 11.98, 68.2, 25.86]}, {"ch": "G", "d": "M80.07 26.18Q77.84 26.18 75.92 25.42Q74 24.65 72.57 23.26Q71.14 21.87 70.35 20.02Q69.55 18.16 69.55 15.99Q69.55 13.8 70.38 11.95Q71.21 10.1 72.68 8.72Q74.16 7.35 76.11 6.58Q78.07 5.82 80.31 5.82Q82.96 5.82 85.09 6.75Q87.22 7.68 88.58 9.3L85.22 12.66Q84.23 11.46 83.03 10.86Q81.84 10.25 80.25 10.25Q78.64 10.25 77.37 10.96Q76.09 11.67 75.35 12.96Q74.61 14.25 74.61 15.97Q74.61 17.7 75.31 19Q76.01 20.3 77.25 21.02Q78.49 21.75 80.12 21.75Q81.73 21.75 82.86 21.13Q83.98 20.51 84.57 19.35Q85.17 18.18 85.17 16.56L88.53 18.74L79.54 18.53V14.46H90.19V15.2Q90.19 18.74 88.91 21.2Q87.63 23.65 85.35 24.92Q83.07 26.18 80.07 26.18Z", "x": 68.79, "bounds": [69.55, 5.82, 90.19, 26.18]}, {"ch": "a", "d": "M97.32 26.14Q95.43 26.14 93.96 25.22Q92.49 24.31 91.65 22.71Q90.81 21.12 90.81 19.07Q90.81 17.01 91.65 15.41Q92.49 13.8 93.96 12.89Q95.43 11.98 97.32 11.98Q98.6 11.98 99.66 12.45Q100.72 12.91 101.4 13.74Q102.09 14.57 102.23 15.64V22.48Q102.09 23.55 101.41 24.38Q100.72 25.21 99.66 25.68Q98.6 26.14 97.32 26.14ZM98.37 21.83Q99.56 21.83 100.29 21.05Q101.02 20.28 101.02 19.06Q101.02 18.24 100.7 17.62Q100.37 17.01 99.77 16.65Q99.17 16.29 98.39 16.29Q97.61 16.29 97.01 16.65Q96.41 17.01 96.06 17.63Q95.71 18.26 95.71 19.06Q95.71 19.86 96.05 20.49Q96.39 21.11 97 21.47Q97.61 21.83 98.37 21.83ZM100.85 25.86V22.21L101.54 18.89L100.85 15.6V12.26H105.6V25.86Z", "x": 90.24, "bounds": [90.81, 11.98, 105.6, 26.14]}, {"ch": "t", "d": "M109.73 25.86V6.65H114.59V25.86ZM106.74 16.29V12.26H117.58V16.29Z", "x": 106.46, "bounds": [106.74, 6.65, 117.58, 25.86]}, {"ch": "e", "d": "M125.39 26.18Q123.1 26.18 121.34 25.28Q119.58 24.38 118.59 22.76Q117.59 21.14 117.59 19.06Q117.59 17 118.56 15.4Q119.53 13.79 121.22 12.87Q122.91 11.94 125.02 11.94Q127.09 11.94 128.68 12.81Q130.27 13.68 131.17 15.23Q132.08 16.77 132.08 18.75Q132.08 19.15 132.03 19.58Q131.99 20.01 131.87 20.58L119.86 20.64V17.51L129.95 17.45L127.76 18.8Q127.75 17.66 127.45 16.92Q127.14 16.18 126.56 15.8Q125.97 15.42 125.09 15.42Q124.17 15.42 123.49 15.85Q122.82 16.29 122.46 17.1Q122.1 17.91 122.1 19.06Q122.1 20.23 122.49 21.04Q122.87 21.85 123.6 22.27Q124.33 22.69 125.38 22.69Q126.37 22.69 127.14 22.36Q127.92 22.03 128.53 21.34L131.06 23.85Q130.05 25.02 128.61 25.6Q127.18 26.18 125.39 26.18Z", "x": 117.09, "bounds": [117.59, 11.94, 132.08, 26.18]}, {"ch": "w", "d": "M136.19 25.86 131.86 12.26H136.54L139.09 22.16L137.7 22.18L140.74 12.26H145.04L148.1 22.18L146.69 22.16L149.29 12.26H153.96L149.63 25.86H145.29L142.38 16.46H143.46L140.49 25.86Z", "x": 131.78, "bounds": [131.86, 12.26, 153.96, 25.86]}, {"ch": "a", "d": "M160.32 26.14Q158.43 26.14 156.96 25.22Q155.49 24.31 154.65 22.71Q153.81 21.12 153.81 19.07Q153.81 17.01 154.65 15.41Q155.49 13.8 156.96 12.89Q158.43 11.98 160.32 11.98Q161.6 11.98 162.66 12.45Q163.71 12.91 164.4 13.74Q165.09 14.57 165.23 15.64V22.48Q165.09 23.55 164.4 24.38Q163.72 25.21 162.66 25.68Q161.6 26.14 160.32 26.14ZM161.36 21.83Q162.56 21.83 163.29 21.05Q164.02 20.28 164.02 19.06Q164.02 18.24 163.69 17.62Q163.37 17.01 162.77 16.65Q162.17 16.29 161.38 16.29Q160.61 16.29 160.01 16.65Q159.41 17.01 159.06 17.63Q158.71 18.26 158.71 19.06Q158.71 19.86 159.05 20.49Q159.39 21.11 160 21.47Q160.61 21.83 161.36 21.83ZM163.85 25.86V22.21L164.54 18.89L163.85 15.6V12.26H168.6V25.86Z", "x": 153.24, "bounds": [153.81, 11.98, 168.6, 26.14]}, {"ch": "y", "d": "M174.95 25.9 169.47 12.26H174.66L178.01 23.48H175.97L179.34 12.26H184.51L178.93 25.9ZM171.72 31.64 175.74 22.76 178.93 25.9 176.58 31.64Z", "x": 169.46, "bounds": [169.47, 12.26, 184.51, 31.64]}]}, "urbanist": {"label": "Urbanist SemiBold", "fs": 27.83, "baseline": 25.74, "advance": 178.7, "cap": 19.48, "minx": 1.11, "maxx": 177.58, "miny": 5.9, "maxy": 32.7, "letters": [{"ch": "O", "d": "M10.71 26.1Q8.74 26.1 7 25.31Q5.26 24.53 3.94 23.14Q2.62 21.75 1.86 19.92Q1.11 18.09 1.11 16Q1.11 13.91 1.86 12.08Q2.62 10.25 3.94 8.86Q5.26 7.47 7 6.69Q8.74 5.9 10.71 5.9Q12.7 5.9 14.44 6.69Q16.18 7.47 17.5 8.86Q18.82 10.25 19.57 12.08Q20.31 13.91 20.31 16Q20.31 18.09 19.57 19.92Q18.82 21.75 17.5 23.14Q16.18 24.53 14.44 25.31Q12.7 26.1 10.71 26.1ZM10.71 23.21Q12.11 23.21 13.32 22.64Q14.53 22.07 15.46 21.07Q16.38 20.08 16.9 18.78Q17.42 17.47 17.42 16Q17.42 14.52 16.89 13.21Q16.37 11.91 15.45 10.92Q14.52 9.93 13.31 9.36Q12.1 8.79 10.71 8.79Q9.32 8.79 8.11 9.36Q6.89 9.93 5.97 10.93Q5.05 11.92 4.53 13.22Q4.01 14.53 4.01 16Q4.01 17.49 4.53 18.79Q5.06 20.09 5.98 21.09Q6.9 22.08 8.12 22.64Q9.33 23.21 10.71 23.21Z", "x": 0.0, "bounds": [1.11, 5.9, 20.31, 26.1]}, {"ch": "p", "d": "M25.43 32.7H22.54V11.83H25.43V13.88Q26.13 12.8 27.21 12.13Q28.3 11.46 29.77 11.46Q31.29 11.46 32.62 12.03Q33.95 12.61 34.95 13.61Q35.95 14.62 36.52 15.94Q37.09 17.27 37.09 18.78Q37.09 20.3 36.52 21.63Q35.95 22.97 34.95 23.98Q33.95 24.99 32.62 25.56Q31.29 26.13 29.77 26.13Q28.3 26.13 27.21 25.46Q26.13 24.78 25.43 23.72ZM29.74 14.26Q28.54 14.26 27.62 14.87Q26.7 15.48 26.19 16.5Q25.67 17.53 25.67 18.78Q25.67 20.03 26.18 21.06Q26.69 22.09 27.61 22.7Q28.53 23.32 29.74 23.32Q30.94 23.32 31.94 22.71Q32.94 22.11 33.54 21.08Q34.14 20.05 34.14 18.78Q34.14 17.54 33.55 16.51Q32.96 15.49 31.96 14.87Q30.96 14.26 29.74 14.26Z", "x": 20.87, "bounds": [22.54, 11.46, 37.09, 32.7]}, {"ch": "e", "d": "M44.88 26.1Q42.96 26.1 41.38 25.11Q39.81 24.13 38.87 22.46Q37.93 20.79 37.93 18.78Q37.93 17.25 38.47 15.92Q39.01 14.59 39.97 13.59Q40.92 12.58 42.18 12.01Q43.45 11.44 44.88 11.44Q46.48 11.44 47.82 12.1Q49.15 12.76 50.1 13.92Q51.04 15.08 51.49 16.59Q51.94 18.1 51.75 19.79H41.03Q41.21 20.76 41.73 21.53Q42.26 22.3 43.07 22.74Q43.88 23.19 44.88 23.21Q45.94 23.21 46.81 22.68Q47.68 22.14 48.23 21.22L51.18 21.9Q50.34 23.75 48.65 24.93Q46.97 26.1 44.88 26.1ZM40.93 17.59H48.84Q48.7 16.59 48.13 15.77Q47.57 14.96 46.73 14.47Q45.88 13.99 44.88 13.99Q43.89 13.99 43.05 14.47Q42.22 14.94 41.66 15.76Q41.11 16.57 40.93 17.59Z", "x": 37.09, "bounds": [37.93, 11.44, 51.81, 26.1]}, {"ch": "n", "d": "M66.04 17.01V25.74H63.14V17.73Q63.14 16.8 62.69 16.03Q62.23 15.27 61.47 14.82Q60.71 14.36 59.78 14.36Q58.85 14.36 58.08 14.82Q57.32 15.27 56.86 16.03Q56.41 16.8 56.41 17.73V25.74H53.52L53.51 11.83H56.4L56.41 13.3Q57.13 12.44 58.19 11.94Q59.25 11.44 60.47 11.44Q62.01 11.44 63.27 12.19Q64.54 12.94 65.29 14.2Q66.04 15.47 66.04 17.01Z", "x": 51.84, "bounds": [53.51, 11.44, 66.04, 25.74]}, {"ch": "G", "d": "M77.28 26.1Q75.26 26.1 73.51 25.31Q71.77 24.52 70.47 23.12Q69.16 21.73 68.43 19.89Q67.7 18.06 67.7 15.98Q67.7 13.8 68.48 11.97Q69.26 10.13 70.61 8.77Q71.96 7.41 73.69 6.67Q75.43 5.92 77.33 5.92Q79.48 5.92 81.47 6.92Q83.46 7.92 84.93 9.93L82.35 11.32Q81.35 10.07 80.02 9.43Q78.69 8.79 77.3 8.79Q75.95 8.79 74.74 9.34Q73.53 9.89 72.6 10.87Q71.67 11.86 71.13 13.17Q70.59 14.49 70.59 16.03Q70.59 17.52 71.12 18.82Q71.64 20.13 72.57 21.11Q73.49 22.09 74.71 22.65Q75.93 23.21 77.32 23.21Q78.34 23.21 79.29 22.87Q80.23 22.54 81.05 21.92Q81.86 21.3 82.48 20.44Q83.09 19.57 83.45 18.52H79.31V15.96H86.85Q86.87 18.05 86.13 19.89Q85.39 21.72 84.07 23.12Q82.76 24.52 81.02 25.31Q79.28 26.1 77.28 26.1Z", "x": 66.59, "bounds": [67.7, 5.92, 86.85, 26.1]}, {"ch": "a", "d": "M99.56 11.83H102.45V25.74H99.55L99.43 23.72Q98.82 24.79 97.78 25.44Q96.74 26.1 95.32 26.1Q93.78 26.1 92.44 25.52Q91.09 24.94 90.07 23.92Q89.05 22.89 88.48 21.55Q87.9 20.2 87.9 18.66Q87.9 17.16 88.46 15.85Q89.01 14.54 90 13.55Q90.99 12.56 92.29 12Q93.6 11.44 95.09 11.44Q96.59 11.44 97.76 12.12Q98.92 12.81 99.68 13.89ZM95.25 23.31Q96.48 23.31 97.39 22.7Q98.31 22.09 98.82 21.06Q99.33 20.03 99.33 18.78Q99.33 17.51 98.82 16.49Q98.3 15.46 97.38 14.85Q96.47 14.25 95.25 14.25Q94.04 14.25 93.04 14.86Q92.04 15.47 91.45 16.5Q90.87 17.53 90.87 18.78Q90.87 20.04 91.47 21.07Q92.07 22.1 93.07 22.7Q94.07 23.31 95.25 23.31Z", "x": 87.07, "bounds": [87.9, 11.44, 102.45, 26.1]}, {"ch": "t", "d": "M112.02 14.72H109.1L109.1 25.74H106.2L106.21 14.72H103.99V11.83H106.21L106.2 7.46H109.1L109.1 11.83H112.02Z", "x": 103.01, "bounds": [103.99, 7.46, 112.02, 25.74]}, {"ch": "e", "d": "M119.75 26.1Q117.83 26.1 116.25 25.11Q114.67 24.13 113.73 22.46Q112.79 20.79 112.79 18.78Q112.79 17.25 113.34 15.92Q113.88 14.59 114.83 13.59Q115.78 12.58 117.05 12.01Q118.32 11.44 119.75 11.44Q121.35 11.44 122.68 12.1Q124.02 12.76 124.96 13.92Q125.91 15.08 126.36 16.59Q126.8 18.1 126.62 19.79H115.89Q116.07 20.76 116.6 21.53Q117.13 22.3 117.93 22.74Q118.74 23.19 119.75 23.21Q120.81 23.21 121.68 22.68Q122.54 22.14 123.1 21.22L126.04 21.9Q125.2 23.75 123.52 24.93Q121.84 26.1 119.75 26.1ZM115.8 17.59H123.7Q123.56 16.59 123 15.77Q122.44 14.96 121.59 14.47Q120.75 13.99 119.75 13.99Q118.76 13.99 117.92 14.47Q117.08 14.94 116.53 15.76Q115.97 16.57 115.8 17.59Z", "x": 111.96, "bounds": [112.79, 11.44, 126.67, 26.1]}, {"ch": "w", "d": "M134.45 25.74H131.56L126.9 11.83H129.74L133.02 21.6L136.28 11.83H139.19L142.45 21.6L145.72 11.83H148.57L143.91 25.74H141.01L137.72 15.9Z", "x": 126.07, "bounds": [126.9, 11.83, 148.57, 25.74]}, {"ch": "a", "d": "M160.44 11.83H163.34V25.74H160.44L160.31 23.72Q159.7 24.79 158.66 25.44Q157.63 26.1 156.21 26.1Q154.67 26.1 153.32 25.52Q151.98 24.94 150.95 23.92Q149.93 22.89 149.36 21.55Q148.79 20.2 148.79 18.66Q148.79 17.16 149.34 15.85Q149.9 14.54 150.88 13.55Q151.87 12.56 153.18 12Q154.48 11.44 155.97 11.44Q157.48 11.44 158.64 12.12Q159.8 12.81 160.57 13.89ZM156.14 23.31Q157.36 23.31 158.28 22.7Q159.19 22.09 159.7 21.06Q160.21 20.03 160.21 18.78Q160.21 17.51 159.7 16.49Q159.19 15.46 158.27 14.85Q157.35 14.25 156.14 14.25Q154.92 14.25 153.92 14.86Q152.92 15.47 152.34 16.5Q151.75 17.53 151.75 18.78Q151.75 20.04 152.35 21.07Q152.95 22.1 153.95 22.7Q154.95 23.31 156.14 23.31Z", "x": 147.95, "bounds": [148.79, 11.44, 163.34, 26.1]}, {"ch": "y", "d": "M174.5 11.83H177.58L169.96 32.7H166.89L169.41 25.74L164.41 11.83H167.57L170.86 21.77Z", "x": 163.3, "bounds": [164.41, 11.83, 177.58, 32.7]}]}, "figtree": {"label": "Figtree SemiBold", "fs": 27.83, "baseline": 25.74, "advance": 176.92, "cap": 19.48, "minx": 1.09, "maxx": 176.77, "miny": 5.93, "maxy": 31.64, "letters": [{"ch": "O", "d": "M10.96 26.07Q8.07 26.07 5.85 24.79Q3.62 23.5 2.36 21.23Q1.09 18.95 1.09 16.01Q1.09 13.05 2.35 10.78Q3.6 8.51 5.81 7.22Q8.01 5.94 10.89 5.94Q13.77 5.94 15.98 7.23Q18.19 8.53 19.44 10.79Q20.69 13.06 20.69 16.01Q20.69 18.95 19.45 21.23Q18.2 23.5 16.01 24.79Q13.82 26.07 10.96 26.07ZM10.96 23.12Q12.85 23.12 14.3 22.21Q15.76 21.3 16.58 19.69Q17.4 18.09 17.4 16.01Q17.4 13.93 16.56 12.33Q15.72 10.72 14.26 9.81Q12.8 8.89 10.89 8.89Q9 8.89 7.53 9.81Q6.06 10.72 5.22 12.32Q4.38 13.93 4.38 16.01Q4.38 18.09 5.22 19.69Q6.07 21.3 7.55 22.21Q9.04 23.12 10.96 23.12Z", "x": 0.0, "bounds": [1.09, 5.94, 20.69, 26.07]}, {"ch": "p", "d": "M23.1 31.3V11.83H26.02L26.16 13.95Q26.89 12.78 28.02 12.14Q29.14 11.49 30.6 11.49Q32.5 11.49 33.91 12.39Q35.32 13.3 36.1 14.93Q36.89 16.57 36.89 18.78Q36.89 20.97 36.1 22.62Q35.32 24.27 33.91 25.17Q32.5 26.07 30.6 26.07Q29.16 26.07 28.05 25.44Q26.94 24.81 26.2 23.67V31.3ZM29.83 23.23Q30.96 23.23 31.82 22.66Q32.68 22.09 33.18 21.09Q33.69 20.08 33.69 18.78Q33.69 17.48 33.2 16.48Q32.71 15.48 31.85 14.91Q30.99 14.35 29.86 14.35Q28.8 14.35 27.97 14.91Q27.13 15.48 26.67 16.48Q26.2 17.48 26.2 18.78Q26.2 20.09 26.67 21.09Q27.13 22.1 27.95 22.66Q28.77 23.23 29.83 23.23Z", "x": 21.23, "bounds": [23.1, 11.49, 36.89, 31.3]}, {"ch": "e", "d": "M45.22 26.07Q43.18 26.07 41.61 25.14Q40.03 24.21 39.14 22.57Q38.24 20.93 38.24 18.78Q38.24 16.64 39.15 15Q40.06 13.36 41.66 12.42Q43.26 11.49 45.32 11.49Q47.2 11.49 48.7 12.45Q50.2 13.41 51.07 15.22Q51.94 17.02 51.94 19.54H41.46Q41.6 21.28 42.75 22.28Q43.91 23.29 45.5 23.29Q46.78 23.29 47.62 22.71Q48.45 22.14 48.91 21.23L51.66 22.39Q51.09 23.52 50.19 24.35Q49.28 25.18 48.04 25.63Q46.8 26.07 45.22 26.07ZM41.64 17.15H48.58Q48.49 16.15 48 15.49Q47.5 14.83 46.77 14.5Q46.05 14.16 45.25 14.16Q44.47 14.16 43.68 14.49Q42.88 14.81 42.32 15.48Q41.76 16.14 41.64 17.15Z", "x": 37.29, "bounds": [38.24, 11.49, 51.94, 26.07]}, {"ch": "n", "d": "M53.79 25.74V11.83H56.71L56.84 13.79Q57.53 12.68 58.6 12.08Q59.67 11.49 61.06 11.49Q62.71 11.49 63.89 12.16Q65.08 12.82 65.71 14.23Q66.35 15.64 66.34 17.89V25.74H63.23V18.62Q63.23 16.95 62.84 16.05Q62.46 15.15 61.77 14.78Q61.09 14.41 60.2 14.4Q58.62 14.38 57.76 15.44Q56.9 16.5 56.9 18.48V25.74Z", "x": 52.06, "bounds": [53.79, 11.49, 66.34, 25.74]}, {"ch": "G", "d": "M78.45 26.07Q75.48 26.07 73.19 24.78Q70.89 23.49 69.59 21.22Q68.29 18.95 68.29 16.02Q68.29 13.05 69.59 10.78Q70.89 8.51 73.19 7.22Q75.48 5.93 78.45 5.93Q80.14 5.93 81.64 6.45Q83.13 6.98 84.31 7.89Q85.5 8.8 86.26 9.99L83.64 11.67Q83.12 10.85 82.29 10.22Q81.45 9.58 80.46 9.23Q79.46 8.88 78.45 8.88Q76.45 8.88 74.9 9.79Q73.35 10.7 72.47 12.31Q71.58 13.91 71.58 16.02Q71.58 18.08 72.45 19.69Q73.33 21.31 74.9 22.23Q76.46 23.14 78.51 23.14Q80.1 23.14 81.35 22.51Q82.6 21.87 83.34 20.73Q84.08 19.6 84.14 18.11H79.5V15.5H87.25V17.67Q87.21 20.27 86.08 22.16Q84.94 24.05 82.97 25.06Q81 26.07 78.45 26.07Z", "x": 67.2, "bounds": [68.29, 5.93, 87.25, 26.07]}, {"ch": "a", "d": "M93.71 26.07Q91.22 26.07 89.84 24.96Q88.47 23.84 88.47 21.82Q88.47 19.64 89.92 18.51Q91.38 17.38 94.01 17.38H97.53Q97.36 15.83 96.66 15.02Q95.96 14.22 94.61 14.22Q93.61 14.22 92.85 14.64Q92.1 15.06 91.58 15.94L88.84 14.95Q89.28 14.03 90.03 13.24Q90.78 12.45 91.91 11.97Q93.03 11.49 94.61 11.49Q96.65 11.49 98 12.28Q99.36 13.07 100.02 14.55Q100.68 16.02 100.66 18.1L100.6 25.74H97.72L97.64 24.03Q97.08 25.02 96.09 25.54Q95.1 26.07 93.71 26.07ZM94.12 23.45Q95.13 23.45 95.92 22.99Q96.71 22.54 97.16 21.76Q97.6 20.98 97.6 20.02V19.76H94.99Q93.13 19.76 92.38 20.28Q91.63 20.8 91.63 21.74Q91.63 22.54 92.29 22.99Q92.94 23.45 94.12 23.45Z", "x": 87.51, "bounds": [88.47, 11.49, 100.66, 26.07]}, {"ch": "t", "d": "M108.8 26.07Q106.6 26.07 105.44 24.97Q104.28 23.86 104.28 21.82V14.49H101.97V11.83H104.28V7.49H107.39V11.83H111.14V14.49H107.39V21.23Q107.39 22.2 107.85 22.7Q108.31 23.2 109.19 23.2Q109.47 23.2 109.8 23.09Q110.13 22.97 110.56 22.67L111.68 25.05Q110.98 25.55 110.25 25.81Q109.52 26.07 108.8 26.07Z", "x": 101.54, "bounds": [101.97, 7.49, 111.68, 26.07]}, {"ch": "e", "d": "M119.69 26.07Q117.64 26.07 116.07 25.14Q114.5 24.21 113.6 22.57Q112.7 20.93 112.7 18.78Q112.7 16.64 113.62 15Q114.53 13.36 116.12 12.42Q117.72 11.49 119.79 11.49Q121.66 11.49 123.16 12.45Q124.66 13.41 125.53 15.22Q126.4 17.02 126.4 19.54H115.92Q116.06 21.28 117.21 22.28Q118.37 23.29 119.97 23.29Q121.24 23.29 122.08 22.71Q122.92 22.14 123.38 21.23L126.12 22.39Q125.56 23.52 124.65 24.35Q123.74 25.18 122.5 25.63Q121.26 26.07 119.69 26.07ZM116.11 17.15H123.04Q122.96 16.15 122.46 15.49Q121.96 14.83 121.24 14.5Q120.51 14.16 119.71 14.16Q118.93 14.16 118.14 14.49Q117.34 14.81 116.78 15.48Q116.22 16.14 116.11 17.15Z", "x": 111.75, "bounds": [112.7, 11.49, 126.4, 26.07]}, {"ch": "w", "d": "M131.01 25.74 126.27 11.83H129.43L132.61 21.51L135.82 11.83H138.89L142.05 21.38L145.25 11.83H148.4L143.66 25.74H140.59L137.35 15.76L134.09 25.74Z", "x": 126.0, "bounds": [126.27, 11.83, 148.4, 25.74]}, {"ch": "a", "d": "M153.93 26.07Q151.44 26.07 150.06 24.96Q148.68 23.84 148.68 21.82Q148.68 19.64 150.14 18.51Q151.6 17.38 154.22 17.38H157.74Q157.57 15.83 156.87 15.02Q156.18 14.22 154.82 14.22Q153.83 14.22 153.07 14.64Q152.31 15.06 151.79 15.94L149.06 14.95Q149.5 14.03 150.25 13.24Q150.99 12.45 152.12 11.97Q153.25 11.49 154.82 11.49Q156.87 11.49 158.22 12.28Q159.57 13.07 160.24 14.55Q160.9 16.02 160.88 18.1L160.82 25.74H157.93L157.85 24.03Q157.29 25.02 156.3 25.54Q155.32 26.07 153.93 26.07ZM154.33 23.45Q155.35 23.45 156.14 22.99Q156.93 22.54 157.37 21.76Q157.81 20.98 157.81 20.02V19.76H155.2Q153.34 19.76 152.59 20.28Q151.85 20.8 151.85 21.74Q151.85 22.54 152.5 22.99Q153.16 23.45 154.33 23.45Z", "x": 147.73, "bounds": [148.68, 11.49, 160.88, 26.07]}, {"ch": "y", "d": "M165.36 31.64Q164.69 31.64 163.87 31.38Q163.06 31.12 162.32 30.76L163.47 28.21Q164.02 28.48 164.45 28.62Q164.89 28.77 165.18 28.77Q165.83 28.77 166.3 28.42Q166.76 28.07 167.05 27.4L167.78 25.62L161.62 11.83H165L169.24 22.07L173.38 11.83H176.77L170.31 27.22Q169.75 28.56 169.14 29.55Q168.54 30.55 167.65 31.09Q166.77 31.64 165.36 31.64Z", "x": 161.45, "bounds": [161.62, 11.83, 176.77, 31.64]}]}, "jost": {"label": "Jost SemiBold", "fs": 27.83, "baseline": 25.74, "advance": 173.41, "cap": 19.48, "minx": 0.99, "maxx": 173.58, "miny": 5.86, "maxy": 31.86, "letters": [{"ch": "O", "d": "M5.2 16Q5.2 14.12 5.98 12.68Q6.75 11.23 8.13 10.41Q9.5 9.58 11.29 9.58Q13.13 9.58 14.49 10.41Q15.85 11.23 16.61 12.68Q17.38 14.12 17.38 16Q17.38 17.86 16.6 19.32Q15.83 20.77 14.46 21.59Q13.1 22.42 11.29 22.42Q9.5 22.42 8.13 21.59Q6.75 20.77 5.98 19.32Q5.2 17.86 5.2 16ZM0.99 16Q0.99 18.21 1.76 20.09Q2.52 21.97 3.9 23.35Q5.28 24.73 7.17 25.49Q9.06 26.26 11.29 26.26Q13.54 26.26 15.42 25.49Q17.3 24.73 18.68 23.35Q20.06 21.97 20.82 20.09Q21.59 18.21 21.59 16Q21.59 13.77 20.82 11.92Q20.05 10.07 18.66 8.71Q17.26 7.36 15.39 6.61Q13.52 5.86 11.29 5.86Q9.1 5.86 7.22 6.61Q5.34 7.36 3.95 8.71Q2.55 10.07 1.77 11.92Q0.99 13.77 0.99 16Z", "x": 0.0, "bounds": [0.99, 5.86, 21.59, 26.26]}, {"ch": "p", "d": "M27.32 31.86V12.94H23.72V31.86ZM37.85 19.34Q37.85 17.13 36.98 15.63Q36.11 14.12 34.72 13.33Q33.32 12.54 31.69 12.54Q30.04 12.54 28.83 13.38Q27.62 14.22 26.97 15.73Q26.32 17.25 26.32 19.34Q26.32 21.4 26.97 22.93Q27.62 24.46 28.83 25.3Q30.04 26.14 31.69 26.14Q33.32 26.14 34.72 25.35Q36.11 24.56 36.98 23.04Q37.85 21.52 37.85 19.34ZM34.11 19.34Q34.11 20.48 33.63 21.28Q33.16 22.09 32.38 22.5Q31.59 22.92 30.63 22.92Q29.84 22.92 29.07 22.51Q28.3 22.11 27.81 21.31Q27.32 20.52 27.32 19.34Q27.32 18.16 27.81 17.37Q28.3 16.57 29.07 16.17Q29.84 15.76 30.63 15.76Q31.59 15.76 32.38 16.18Q33.16 16.59 33.63 17.39Q34.11 18.19 34.11 19.34Z", "x": 22.01, "bounds": [23.72, 12.54, 37.85, 31.86]}, {"ch": "e", "d": "M46.02 26.02Q48.34 26.02 49.99 25.11Q51.64 24.21 52.65 22.4L49.49 21.42Q48.95 22.35 48.1 22.81Q47.24 23.27 46.06 23.27Q44.99 23.27 44.25 22.8Q43.5 22.33 43.12 21.43Q42.73 20.54 42.74 19.26Q42.76 17.92 43.13 17.02Q43.51 16.12 44.23 15.66Q44.95 15.2 46 15.2Q46.86 15.2 47.49 15.58Q48.13 15.96 48.48 16.64Q48.84 17.32 48.84 18.26Q48.84 18.45 48.75 18.74Q48.65 19.03 48.53 19.21L49.49 17.93H41.19V20.13H52.65Q52.68 19.97 52.69 19.7Q52.69 19.43 52.69 19.17Q52.69 17.11 51.92 15.66Q51.14 14.2 49.66 13.43Q48.18 12.66 46.06 12.66Q43.93 12.66 42.37 13.48Q40.81 14.29 39.97 15.79Q39.12 17.29 39.12 19.34Q39.12 21.36 39.97 22.86Q40.82 24.36 42.37 25.19Q43.93 26.02 46.02 26.02Z", "x": 38.21, "bounds": [39.12, 12.66, 52.69, 26.02]}, {"ch": "n", "d": "M63.15 17.95V25.74H66.87V17.55Q66.87 15.25 65.75 13.95Q64.63 12.65 62.35 12.65Q60.99 12.65 60.03 13.22Q59.07 13.8 58.48 14.88V12.94H54.88V25.74H58.48V17.95Q58.48 17.13 58.79 16.55Q59.11 15.96 59.68 15.65Q60.26 15.33 61.02 15.33Q62.14 15.33 62.64 16Q63.15 16.67 63.15 17.95Z", "x": 53.09, "bounds": [54.88, 12.65, 66.87, 25.74]}, {"ch": "G", "d": "M79.66 18.5H85.25Q85 19.38 84.53 20.15Q84.07 20.91 83.38 21.49Q82.7 22.06 81.75 22.38Q80.8 22.7 79.62 22.7Q77.8 22.7 76.36 21.87Q74.92 21.04 74.09 19.53Q73.26 18.02 73.26 16Q73.26 13.96 74.11 12.49Q74.97 11.02 76.41 10.22Q77.86 9.42 79.62 9.42Q81.49 9.42 82.77 10.22Q84.05 11.02 84.79 12.3L88.19 10.33Q87.3 8.97 86.05 7.97Q84.81 6.98 83.21 6.42Q81.61 5.86 79.62 5.86Q77.36 5.86 75.44 6.59Q73.52 7.31 72.09 8.66Q70.67 10 69.88 11.87Q69.08 13.73 69.08 16Q69.08 18.27 69.86 20.15Q70.63 22.03 72.03 23.4Q73.43 24.77 75.34 25.51Q77.24 26.26 79.5 26.26Q81.9 26.26 83.77 25.39Q85.63 24.53 86.89 23.02Q88.15 21.5 88.75 19.53Q89.36 17.56 89.26 15.32H79.66Z", "x": 68.09, "bounds": [69.08, 5.86, 89.28, 26.26]}, {"ch": "a", "d": "M94.19 21.75Q94.19 21.21 94.44 20.84Q94.69 20.46 95.22 20.25Q95.75 20.05 96.59 20.05Q97.62 20.05 98.55 20.32Q99.47 20.6 100.23 21.16V19.47Q99.94 19.13 99.31 18.78Q98.68 18.42 97.74 18.17Q96.81 17.91 95.59 17.91Q93.19 17.91 91.88 19Q90.57 20.09 90.57 21.95Q90.57 23.25 91.19 24.17Q91.81 25.08 92.85 25.55Q93.88 26.02 95.09 26.02Q96.26 26.02 97.33 25.6Q98.39 25.18 99.08 24.34Q99.76 23.51 99.76 22.28L99.32 20.61Q99.32 21.54 98.9 22.2Q98.48 22.86 97.81 23.2Q97.13 23.54 96.31 23.54Q95.71 23.54 95.23 23.33Q94.74 23.13 94.47 22.72Q94.19 22.32 94.19 21.75ZM92.8 16.56Q93.08 16.37 93.67 16.09Q94.25 15.82 95.06 15.61Q95.87 15.41 96.74 15.41Q97.33 15.41 97.81 15.53Q98.29 15.64 98.63 15.89Q98.97 16.13 99.14 16.51Q99.32 16.89 99.32 17.42V25.74H102.79V16.68Q102.79 15.36 102.07 14.46Q101.34 13.56 100.06 13.08Q98.79 12.6 97.15 12.6Q95.35 12.6 93.9 13.09Q92.46 13.59 91.47 14.12Z", "x": 89.85, "bounds": [90.57, 12.6, 102.79, 26.02]}, {"ch": "t", "d": "M103.93 12.94V15.98H111.82V12.94ZM106.14 8.49V25.74H109.61V8.49Z", "x": 103.79, "bounds": [103.93, 8.49, 111.82, 25.74]}, {"ch": "e", "d": "M119.23 26.02Q121.55 26.02 123.2 25.11Q124.85 24.21 125.86 22.4L122.71 21.42Q122.16 22.35 121.31 22.81Q120.45 23.27 119.27 23.27Q118.2 23.27 117.46 22.8Q116.71 22.33 116.33 21.43Q115.94 20.54 115.96 19.26Q115.97 17.92 116.34 17.02Q116.72 16.12 117.44 15.66Q118.16 15.2 119.21 15.2Q120.07 15.2 120.7 15.58Q121.34 15.96 121.7 16.64Q122.05 17.32 122.05 18.26Q122.05 18.45 121.96 18.74Q121.86 19.03 121.74 19.21L122.71 17.93H114.4V20.13H125.87Q125.89 19.97 125.9 19.7Q125.91 19.43 125.91 19.17Q125.91 17.11 125.13 15.66Q124.35 14.2 122.87 13.43Q121.39 12.66 119.27 12.66Q117.14 12.66 115.58 13.48Q114.02 14.29 113.18 15.79Q112.33 17.29 112.33 19.34Q112.33 21.36 113.18 22.86Q114.03 24.36 115.58 25.19Q117.14 26.02 119.23 26.02Z", "x": 111.42, "bounds": [112.33, 12.66, 125.91, 26.02]}, {"ch": "w", "d": "M125.79 12.94 132.07 26.59 136.06 18.45 140.18 26.59 146.46 12.94H142.64L139.79 20.07L136.04 11.97L132.43 20.04L129.6 12.94Z", "x": 125.89, "bounds": [125.79, 11.97, 146.46, 26.59]}, {"ch": "a", "d": "M150.15 21.75Q150.15 21.21 150.4 20.84Q150.65 20.46 151.18 20.25Q151.7 20.05 152.55 20.05Q153.58 20.05 154.5 20.32Q155.43 20.6 156.19 21.16V19.47Q155.9 19.13 155.27 18.78Q154.63 18.42 153.7 18.17Q152.77 17.91 151.54 17.91Q149.14 17.91 147.83 19Q146.52 20.09 146.52 21.95Q146.52 23.25 147.15 24.17Q147.77 25.08 148.81 25.55Q149.84 26.02 151.04 26.02Q152.22 26.02 153.29 25.6Q154.35 25.18 155.04 24.34Q155.72 23.51 155.72 22.28L155.27 20.61Q155.27 21.54 154.86 22.2Q154.44 22.86 153.76 23.2Q153.09 23.54 152.27 23.54Q151.67 23.54 151.19 23.33Q150.7 23.13 150.43 22.72Q150.15 22.32 150.15 21.75ZM148.75 16.56Q149.04 16.37 149.62 16.09Q150.21 15.82 151.02 15.61Q151.83 15.41 152.7 15.41Q153.29 15.41 153.77 15.53Q154.25 15.64 154.59 15.89Q154.92 16.13 155.1 16.51Q155.27 16.89 155.27 17.42V25.74H158.75V16.68Q158.75 15.36 158.02 14.46Q157.3 13.56 156.02 13.08Q154.74 12.6 153.11 12.6Q151.31 12.6 149.86 13.09Q148.42 13.59 147.43 14.12Z", "x": 145.81, "bounds": [146.52, 12.6, 158.75, 26.02]}, {"ch": "y", "d": "M173.58 12.94H169.83L165.78 23.07L167.56 23.14L163.45 12.94H159.43L164.77 24.47L161.54 31.86H165.24Z", "x": 159.61, "bounds": [159.43, 12.94, 173.58, 31.86]}]}, "onest": {"label": "Onest SemiBold", "fs": 27.83, "baseline": 25.84, "advance": 185.6, "cap": 19.67, "minx": 1.11, "maxx": 183.95, "miny": 5.92, "maxy": 32.54, "letters": [{"ch": "O", "d": "M10.67 26.06Q7.63 26.06 5.49 24.77Q3.36 23.49 2.23 21.21Q1.11 18.94 1.11 16.01Q1.11 13.04 2.25 10.77Q3.39 8.49 5.54 7.2Q7.69 5.92 10.72 5.92Q13.75 5.92 15.88 7.19Q18.01 8.47 19.15 10.73Q20.29 13 20.29 15.96Q20.29 18.88 19.15 21.17Q18.02 23.45 15.87 24.76Q13.73 26.06 10.67 26.06ZM10.71 22.84Q12.83 22.84 14.16 21.87Q15.5 20.89 16.12 19.34Q16.74 17.78 16.74 16.01Q16.74 14.69 16.39 13.46Q16.04 12.22 15.32 11.25Q14.59 10.28 13.44 9.71Q12.29 9.14 10.7 9.14Q8.61 9.14 7.27 10.11Q5.93 11.08 5.28 12.64Q4.64 14.19 4.64 16Q4.64 17.79 5.28 19.35Q5.92 20.91 7.27 21.87Q8.62 22.84 10.71 22.84Z", "x": 0.0, "bounds": [1.11, 5.92, 20.29, 26.06]}, {"ch": "p", "d": "M22.48 31.25V11.17H25.66L25.83 13.14Q26.68 12.02 27.88 11.48Q29.08 10.95 30.51 10.95Q32.7 10.95 34.11 11.95Q35.53 12.95 36.23 14.65Q36.93 16.35 36.93 18.48Q36.93 20.64 36.21 22.35Q35.48 24.06 34.02 25.04Q32.56 26.03 30.35 26.03Q29.33 26.03 28.48 25.8Q27.63 25.57 26.98 25.17Q26.33 24.76 25.87 24.23V31.25ZM29.83 22.98Q31.24 22.98 32.06 22.35Q32.87 21.72 33.22 20.71Q33.57 19.69 33.57 18.55Q33.57 17.35 33.2 16.31Q32.83 15.28 32 14.65Q31.17 14.02 29.76 14.02Q28.49 14.02 27.62 14.65Q26.74 15.27 26.29 16.3Q25.84 17.33 25.84 18.52Q25.84 19.74 26.27 20.76Q26.7 21.78 27.59 22.38Q28.48 22.98 29.83 22.98Z", "x": 20.84, "bounds": [22.48, 10.95, 36.93, 31.25]}, {"ch": "e", "d": "M45.67 26.03Q43.37 26.03 41.78 25.09Q40.19 24.15 39.36 22.46Q38.54 20.78 38.54 18.55Q38.54 16.31 39.39 14.6Q40.25 12.9 41.84 11.92Q43.44 10.95 45.68 10.95Q47.36 10.95 48.61 11.54Q49.87 12.13 50.72 13.16Q51.57 14.19 51.98 15.5Q52.4 16.82 52.36 18.27Q52.35 18.61 52.32 18.93Q52.3 19.25 52.24 19.59H41.92Q42.01 20.57 42.45 21.38Q42.88 22.19 43.68 22.66Q44.47 23.13 45.66 23.13Q46.36 23.13 47 22.96Q47.64 22.79 48.12 22.42Q48.6 22.05 48.78 21.5H52.16Q51.81 23.08 50.84 24.08Q49.87 25.09 48.52 25.56Q47.17 26.03 45.67 26.03ZM49.06 17.08Q49.05 16.16 48.63 15.41Q48.22 14.66 47.46 14.22Q46.7 13.78 45.6 13.78Q44.43 13.78 43.65 14.25Q42.87 14.72 42.46 15.47Q42.06 16.23 41.99 17.08Z", "x": 37.45, "bounds": [38.54, 10.95, 52.37, 26.03]}, {"ch": "n", "d": "M54.51 25.84V11.17H57.69L57.87 13.04Q58.45 12.27 59.24 11.81Q60.02 11.36 60.88 11.15Q61.74 10.95 62.49 10.95Q64.6 10.95 65.79 11.86Q66.99 12.77 67.5 14.31Q68.01 15.84 68.01 17.74V25.84H64.62V18.29Q64.62 17.49 64.51 16.73Q64.4 15.97 64.08 15.35Q63.76 14.73 63.17 14.37Q62.59 14 61.62 14Q60.4 14 59.57 14.64Q58.73 15.28 58.32 16.36Q57.9 17.44 57.9 18.79V25.84Z", "x": 52.87, "bounds": [54.51, 10.95, 68.01, 25.84]}, {"ch": "G", "d": "M79.45 26.06Q76.43 26.06 74.35 24.79Q72.26 23.52 71.19 21.25Q70.12 18.99 70.12 16Q70.12 13.77 70.72 11.92Q71.32 10.08 72.5 8.73Q73.68 7.38 75.41 6.65Q77.13 5.92 79.38 5.92Q81.6 5.92 83.33 6.63Q85.06 7.33 86.19 8.69Q87.32 10.05 87.71 12.02H84.13Q83.9 11.08 83.27 10.45Q82.64 9.81 81.69 9.47Q80.75 9.14 79.53 9.14Q77.94 9.14 76.83 9.7Q75.71 10.25 75.02 11.21Q74.32 12.17 74.01 13.42Q73.69 14.66 73.69 16.04Q73.69 17.86 74.29 19.41Q74.89 20.97 76.18 21.9Q77.46 22.84 79.54 22.84Q81.04 22.84 82.16 22.34Q83.28 21.84 83.94 20.87Q84.6 19.9 84.65 18.5H78.92V15.49H88.15V16.77Q88.15 19.63 87.15 21.71Q86.15 23.8 84.22 24.93Q82.28 26.06 79.45 26.06Z", "x": 69.01, "bounds": [70.12, 5.92, 88.15, 26.06]}, {"ch": "a", "d": "M95.02 26.03Q94.01 26.03 93.08 25.76Q92.15 25.49 91.43 24.94Q90.7 24.39 90.28 23.56Q89.85 22.74 89.85 21.63Q89.85 20.21 90.41 19.31Q90.97 18.42 91.92 17.94Q92.87 17.46 94.11 17.28Q95.34 17.09 96.68 17.09H99.53Q99.53 16.13 99.23 15.41Q98.92 14.69 98.27 14.29Q97.61 13.88 96.58 13.88Q95.92 13.88 95.35 14.03Q94.78 14.19 94.38 14.5Q93.99 14.81 93.83 15.31H90.35Q90.53 14.17 91.13 13.35Q91.72 12.52 92.6 12Q93.48 11.47 94.53 11.22Q95.58 10.96 96.67 10.96Q98.8 10.96 100.15 11.79Q101.5 12.61 102.14 14.17Q102.79 15.72 102.79 17.93V25.84H99.79L99.71 23.95Q99.05 24.88 98.17 25.33Q97.29 25.78 96.45 25.9Q95.62 26.03 95.02 26.03ZM95.69 23.27Q96.8 23.27 97.67 22.86Q98.54 22.44 99.05 21.7Q99.56 20.95 99.56 19.97V19.49H96.01Q95.58 19.49 95.08 19.55Q94.59 19.61 94.17 19.8Q93.75 19.98 93.48 20.34Q93.22 20.71 93.22 21.34Q93.22 22 93.56 22.42Q93.9 22.84 94.47 23.06Q95.04 23.27 95.69 23.27Z", "x": 88.71, "bounds": [89.85, 10.96, 102.79, 26.03]}, {"ch": "t", "d": "M111.29 26.01Q108.87 26.01 107.77 24.67Q106.66 23.33 106.66 20.96V14.18H104.4V11.17H106.66V7.07H110.05V11.17H114.52V14.18H110.05V20.32Q110.05 21.06 110.22 21.64Q110.39 22.22 110.83 22.56Q111.27 22.89 112.06 22.9Q112.72 22.9 113.19 22.69Q113.66 22.47 113.97 22.2L115.12 24.78Q114.58 25.22 113.93 25.5Q113.29 25.78 112.62 25.9Q111.96 26.01 111.29 26.01Z", "x": 103.82, "bounds": [104.4, 7.07, 115.12, 26.01]}, {"ch": "e", "d": "M122.97 26.03Q120.67 26.03 119.08 25.09Q117.49 24.15 116.66 22.46Q115.84 20.78 115.84 18.55Q115.84 16.31 116.69 14.6Q117.55 12.9 119.15 11.92Q120.74 10.95 122.98 10.95Q124.66 10.95 125.92 11.54Q127.17 12.13 128.02 13.16Q128.87 14.19 129.29 15.5Q129.7 16.82 129.66 18.27Q129.65 18.61 129.62 18.93Q129.6 19.25 129.54 19.59H119.22Q119.31 20.57 119.75 21.38Q120.18 22.19 120.98 22.66Q121.77 23.13 122.96 23.13Q123.66 23.13 124.3 22.96Q124.94 22.79 125.42 22.42Q125.9 22.05 126.09 21.5H129.46Q129.11 23.08 128.14 24.08Q127.17 25.09 125.82 25.56Q124.47 26.03 122.97 26.03ZM126.36 17.08Q126.35 16.16 125.94 15.41Q125.52 14.66 124.76 14.22Q124 13.78 122.9 13.78Q121.73 13.78 120.95 14.25Q120.17 14.72 119.76 15.47Q119.36 16.23 119.29 17.08Z", "x": 114.75, "bounds": [115.84, 10.95, 129.67, 26.03]}, {"ch": "w", "d": "M134.91 25.84 130.32 11.17H134.06L137.03 22.11L140.17 11.17H144.16L147.3 22.11L150.23 11.17H153.93L149.33 25.84H145.27L142.11 15.27L138.97 25.84Z", "x": 130.17, "bounds": [130.32, 11.17, 153.93, 25.84]}, {"ch": "a", "d": "M159.83 26.03Q158.81 26.03 157.89 25.76Q156.96 25.49 156.24 24.94Q155.51 24.39 155.08 23.56Q154.66 22.74 154.66 21.63Q154.66 20.21 155.22 19.31Q155.78 18.42 156.73 17.94Q157.68 17.46 158.91 17.28Q160.15 17.09 161.49 17.09H164.34Q164.34 16.13 164.03 15.41Q163.72 14.69 163.07 14.29Q162.42 13.88 161.39 13.88Q160.73 13.88 160.16 14.03Q159.58 14.19 159.19 14.5Q158.79 14.81 158.64 15.31H155.16Q155.34 14.17 155.93 13.35Q156.53 12.52 157.41 12Q158.29 11.47 159.34 11.22Q160.38 10.96 161.47 10.96Q163.61 10.96 164.96 11.79Q166.31 12.61 166.95 14.17Q167.6 15.72 167.6 17.93V25.84H164.6L164.51 23.95Q163.86 24.88 162.98 25.33Q162.1 25.78 161.26 25.9Q160.42 26.03 159.83 26.03ZM160.5 23.27Q161.61 23.27 162.48 22.86Q163.35 22.44 163.86 21.7Q164.36 20.95 164.36 19.97V19.49H160.82Q160.38 19.49 159.89 19.55Q159.39 19.61 158.97 19.8Q158.55 19.98 158.29 20.34Q158.02 20.71 158.02 21.34Q158.02 22 158.37 22.42Q158.71 22.84 159.28 23.06Q159.85 23.27 160.5 23.27Z", "x": 153.52, "bounds": [154.66, 10.96, 167.6, 26.03]}, {"ch": "y", "d": "M176.65 32.54Q175.01 32.54 173.43 32.08Q171.85 31.62 170.48 30.53L171.7 27.82Q172.66 28.55 173.87 29.01Q175.09 29.48 176.56 29.48Q178.76 29.48 179.68 28.34Q180.61 27.21 180.61 25.28V24.18Q180.12 24.85 179.39 25.26Q178.67 25.68 177.83 25.87Q177 26.06 176.13 26.06Q174 26.06 172.73 25.18Q171.46 24.3 170.9 22.76Q170.35 21.22 170.35 19.24V11.17H173.74V18.5Q173.74 19.3 173.84 20.11Q173.94 20.91 174.26 21.57Q174.59 22.22 175.24 22.62Q175.89 23.01 176.99 23.01Q178.4 23.01 179.18 22.32Q179.96 21.62 180.26 20.52Q180.56 19.41 180.56 18.16V11.17H183.95V24.43Q183.95 26.3 183.55 27.8Q183.16 29.29 182.29 30.35Q181.43 31.41 180.04 31.97Q178.65 32.54 176.65 32.54Z", "x": 168.63, "bounds": [170.35, 11.17, 183.95, 32.54]}]}, "hanken": {"label": "Hanken Grotesk SemiBold", "fs": 27.83, "baseline": 25.7, "advance": 172.63, "cap": 19.39, "minx": 1.19, "maxx": 172.13, "miny": 5.9, "maxy": 31.37, "letters": [{"ch": "O", "d": "M10.36 26.1Q7.64 26.1 5.57 24.84Q3.5 23.57 2.35 21.3Q1.19 19.03 1.19 16.01Q1.19 12.97 2.35 10.7Q3.5 8.43 5.57 7.16Q7.64 5.9 10.36 5.9Q13.13 5.9 15.21 7.16Q17.29 8.42 18.44 10.69Q19.6 12.96 19.6 16.01Q19.6 19.04 18.43 21.31Q17.26 23.58 15.18 24.84Q13.11 26.1 10.36 26.1ZM10.36 23.03Q12.22 23.03 13.55 22.16Q14.88 21.3 15.61 19.72Q16.35 18.15 16.35 16.01Q16.35 13.85 15.61 12.28Q14.88 10.7 13.54 9.84Q12.21 8.97 10.36 8.97Q8.55 8.97 7.21 9.82Q5.86 10.67 5.13 12.25Q4.41 13.82 4.41 16.01Q4.41 18.18 5.13 19.75Q5.86 21.33 7.21 22.18Q8.55 23.03 10.36 23.03Z", "x": 0.0, "bounds": [1.19, 5.9, 19.6, 26.1]}, {"ch": "p", "d": "M22.06 31.37V11.98H25.13V15.79L24.34 15.81Q24.7 14.31 25.42 13.42Q26.14 12.52 27.12 12.12Q28.1 11.72 29.22 11.72Q31.05 11.72 32.39 12.66Q33.74 13.59 34.47 15.2Q35.19 16.81 35.19 18.85Q35.19 21.05 34.39 22.65Q33.59 24.25 32.23 25.1Q30.88 25.95 29.22 25.95Q28.05 25.95 27.06 25.5Q26.07 25.05 25.39 24.17Q24.7 23.28 24.43 22L25.13 21.86V31.37ZM28.46 23.13Q29.56 23.13 30.37 22.6Q31.19 22.07 31.64 21.1Q32.09 20.13 32.09 18.85Q32.09 17.56 31.64 16.59Q31.19 15.62 30.37 15.09Q29.56 14.56 28.46 14.56Q27.38 14.56 26.56 15.1Q25.75 15.64 25.31 16.6Q24.86 17.56 24.86 18.85Q24.86 20.13 25.31 21.09Q25.75 22.06 26.56 22.59Q27.38 23.13 28.46 23.13Z", "x": 20.26, "bounds": [22.06, 11.72, 35.19, 31.37]}, {"ch": "e", "d": "M43.63 25.95Q41.9 25.95 40.64 25.39Q39.38 24.82 38.57 23.83Q37.76 22.84 37.37 21.58Q36.98 20.31 36.98 18.92Q36.98 17.56 37.36 16.27Q37.74 14.98 38.52 13.96Q39.3 12.94 40.51 12.33Q41.73 11.72 43.4 11.72Q45 11.72 46.18 12.27Q47.37 12.81 48.14 13.77Q48.91 14.72 49.29 15.96Q49.68 17.19 49.68 18.58Q49.68 18.76 49.66 19.12Q49.65 19.49 49.59 19.85H40.06Q40.18 21.5 41.18 22.36Q42.18 23.23 43.75 23.23Q44.87 23.23 45.83 22.86Q46.79 22.49 47.57 21.72L49.26 23.77Q48.38 24.55 47.52 25.03Q46.66 25.51 45.73 25.73Q44.8 25.95 43.63 25.95ZM46.63 18.3Q46.68 18.07 46.71 17.85Q46.73 17.63 46.73 17.42Q46.73 16.8 46.51 16.24Q46.29 15.68 45.88 15.24Q45.46 14.81 44.86 14.55Q44.25 14.3 43.47 14.3Q42.38 14.3 41.64 14.77Q40.89 15.23 40.5 15.97Q40.1 16.72 40.02 17.56H47.65Z", "x": 35.81, "bounds": [36.98, 11.72, 49.68, 25.95]}, {"ch": "n", "d": "M52.11 25.7V11.98H55.21V15.02L54.66 15.14Q55.02 13.95 55.71 13.2Q56.4 12.45 57.33 12.09Q58.26 11.72 59.29 11.72Q61.6 11.72 62.9 13.04Q64.21 14.37 64.21 16.82V25.7H61.11V17.96Q61.11 16.12 60.44 15.26Q59.78 14.4 58.38 14.4Q57.34 14.4 56.64 14.88Q55.94 15.37 55.57 16.4Q55.21 17.42 55.21 19.03V25.7Z", "x": 50.31, "bounds": [52.11, 11.72, 64.21, 25.7]}, {"ch": "G", "d": "M75.48 26.1Q72.84 26.1 70.81 24.83Q68.79 23.57 67.65 21.29Q66.5 19.01 66.5 15.99Q66.5 12.96 67.65 10.69Q68.81 8.43 70.88 7.17Q72.94 5.9 75.67 5.9Q78.7 5.9 80.91 7.41Q83.12 8.92 84.09 11.58L81.07 12.69Q80.42 10.93 79.03 9.95Q77.65 8.97 75.67 8.97Q73.86 8.97 72.51 9.83Q71.17 10.69 70.44 12.25Q69.72 13.81 69.72 15.99Q69.72 18.16 70.44 19.74Q71.17 21.31 72.51 22.17Q73.86 23.03 75.67 23.03Q76.61 23.03 77.58 22.76Q78.56 22.5 79.39 21.86Q80.22 21.21 80.73 20.06Q81.24 18.91 81.24 17.15V15.93L81.96 18.16H76.43V15.28H84.09V25.7H81.25L80.81 22.34L81.36 22.76Q80.85 23.89 79.96 24.63Q79.08 25.37 77.93 25.73Q76.78 26.1 75.48 26.1Z", "x": 65.31, "bounds": [66.5, 5.9, 84.09, 26.1]}, {"ch": "a", "d": "M95.84 25.7 95.6 23.4V17.05Q95.6 15.71 94.8 15.03Q94 14.34 92.55 14.34Q91.45 14.34 90.37 14.78Q89.3 15.22 88.41 16.1L86.68 14.05Q88.03 12.82 89.53 12.27Q91.03 11.72 92.79 11.72Q95.58 11.72 97.13 13.13Q98.68 14.54 98.68 17.05V25.7ZM91.57 25.95Q90.05 25.95 88.87 25.4Q87.69 24.85 87.03 23.84Q86.37 22.83 86.37 21.51Q86.37 20.34 86.85 19.51Q87.33 18.68 88.11 18.22Q88.8 17.79 89.69 17.62Q90.57 17.44 91.55 17.44H95.87V19.75H92Q91.5 19.75 90.99 19.84Q90.48 19.93 90.11 20.2Q89.81 20.42 89.64 20.76Q89.47 21.09 89.47 21.52Q89.47 22.41 90.15 22.93Q90.83 23.45 91.97 23.45Q92.97 23.45 93.79 23.01Q94.61 22.56 95.11 21.78Q95.6 21 95.6 20.01L96.55 21.56Q96.23 23.01 95.5 23.99Q94.78 24.98 93.77 25.47Q92.77 25.95 91.57 25.95Z", "x": 85.18, "bounds": [86.37, 11.72, 98.68, 25.95]}, {"ch": "t", "d": "M106.56 25.95Q105.51 25.95 104.61 25.55Q103.71 25.15 103.18 24.24Q102.65 23.33 102.65 21.85V8.63L105.78 6.93V21Q105.78 22.17 106.12 22.8Q106.46 23.42 107.39 23.42Q107.67 23.42 108.04 23.35Q108.4 23.29 108.83 23.18V25.56Q108.27 25.76 107.7 25.86Q107.12 25.95 106.56 25.95ZM100.03 14.38V11.98H108.83V14.38Z", "x": 99.76, "bounds": [100.03, 6.93, 108.83, 25.95]}, {"ch": "e", "d": "M116.75 25.95Q115.03 25.95 113.77 25.39Q112.51 24.82 111.7 23.83Q110.89 22.84 110.49 21.58Q110.1 20.31 110.1 18.92Q110.1 17.56 110.49 16.27Q110.87 14.98 111.65 13.96Q112.42 12.94 113.64 12.33Q114.86 11.72 116.53 11.72Q118.13 11.72 119.31 12.27Q120.49 12.81 121.26 13.77Q122.03 14.72 122.42 15.96Q122.8 17.19 122.8 18.58Q122.8 18.76 122.79 19.12Q122.77 19.49 122.72 19.85H113.19Q113.3 21.5 114.3 22.36Q115.31 23.23 116.88 23.23Q118 23.23 118.96 22.86Q119.91 22.49 120.69 21.72L122.39 23.77Q121.51 24.55 120.65 25.03Q119.79 25.51 118.86 25.73Q117.93 25.95 116.75 25.95ZM119.75 18.3Q119.81 18.07 119.83 17.85Q119.86 17.63 119.86 17.42Q119.86 16.8 119.64 16.24Q119.42 15.68 119 15.24Q118.59 14.81 117.99 14.55Q117.38 14.3 116.6 14.3Q115.51 14.3 114.77 14.77Q114.02 15.23 113.63 15.97Q113.23 16.72 113.15 17.56H120.78Z", "x": 108.94, "bounds": [110.1, 11.72, 122.8, 25.95]}, {"ch": "w", "d": "M128.11 25.7 123.94 11.98H127.22L129.68 20.98L132.44 11.98H135.11L137.87 20.92L140.35 11.98H143.65L139.45 25.7H136.67L133.79 16.34L130.93 25.7Z", "x": 123.33, "bounds": [123.94, 11.98, 143.65, 25.7]}, {"ch": "a", "d": "M154.36 25.7 154.12 23.4V17.05Q154.12 15.71 153.32 15.03Q152.51 14.34 151.07 14.34Q149.97 14.34 148.89 14.78Q147.82 15.22 146.93 16.1L145.2 14.05Q146.54 12.82 148.05 12.27Q149.55 11.72 151.31 11.72Q154.1 11.72 155.65 13.13Q157.2 14.54 157.2 17.05V25.7ZM150.09 25.95Q148.56 25.95 147.39 25.4Q146.21 24.85 145.55 23.84Q144.89 22.83 144.89 21.51Q144.89 20.34 145.37 19.51Q145.85 18.68 146.63 18.22Q147.32 17.79 148.2 17.62Q149.09 17.44 150.07 17.44H154.39V19.75H150.52Q150.02 19.75 149.51 19.84Q149 19.93 148.62 20.2Q148.33 20.42 148.16 20.76Q147.99 21.09 147.99 21.52Q147.99 22.41 148.67 22.93Q149.35 23.45 150.49 23.45Q151.49 23.45 152.31 23.01Q153.13 22.56 153.63 21.78Q154.12 21 154.12 20.01L155.06 21.56Q154.75 23.01 154.02 23.99Q153.29 24.98 152.29 25.47Q151.28 25.95 150.09 25.95Z", "x": 143.69, "bounds": [144.89, 11.72, 157.2, 25.95]}, {"ch": "y", "d": "M160.88 31.36 163.61 24.77 158.38 11.98H161.77L165.25 21.68L168.79 11.98H172.13L164.24 31.36Z", "x": 157.89, "bounds": [158.38, 11.98, 172.13, 31.36]}]}, "poppins": {"label": "Poppins SemiBold", "fs": 27.83, "baseline": 25.75, "advance": 198.09, "cap": 19.51, "minx": 0.97, "maxx": 197.93, "miny": 6.05, "maxy": 33.1, "letters": [{"ch": "O", "d": "M0.97 15.99Q0.97 13.15 2.31 10.88Q3.65 8.61 5.93 7.33Q8.21 6.05 10.94 6.05Q13.69 6.05 15.96 7.33Q18.23 8.61 19.55 10.88Q20.87 13.15 20.87 15.99Q20.87 18.85 19.55 21.12Q18.23 23.39 15.94 24.67Q13.66 25.95 10.94 25.95Q8.21 25.95 5.93 24.67Q3.65 23.39 2.31 21.12Q0.97 18.85 0.97 15.99ZM16.86 15.99Q16.86 14.04 16.11 12.58Q15.36 11.12 14.02 10.34Q12.69 9.56 10.94 9.56Q9.18 9.56 7.83 10.34Q6.48 11.12 5.73 12.58Q4.98 14.04 4.98 15.99Q4.98 17.93 5.73 19.41Q6.48 20.88 7.83 21.68Q9.18 22.47 10.94 22.47Q12.69 22.47 14.02 21.68Q15.36 20.88 16.11 19.41Q16.86 17.93 16.86 15.99Z", "x": 0.0, "bounds": [0.97, 6.05, 20.87, 25.95]}, {"ch": "p", "d": "M32.19 10.09Q34.17 10.09 35.77 11.06Q37.37 12.03 38.3 13.83Q39.23 15.62 39.23 17.99Q39.23 20.35 38.3 22.18Q37.37 24 35.77 25Q34.17 26 32.19 26Q30.5 26 29.2 25.31Q27.91 24.61 27.1 23.55V33.1H23.21V10.34H27.1V12.56Q27.85 11.51 29.18 10.8Q30.5 10.09 32.19 10.09ZM31.17 13.51Q30.11 13.51 29.18 14.05Q28.24 14.59 27.67 15.62Q27.1 16.65 27.1 18.05Q27.1 19.44 27.67 20.47Q28.24 21.5 29.18 22.04Q30.11 22.58 31.17 22.58Q32.25 22.58 33.18 22.02Q34.11 21.47 34.69 20.44Q35.26 19.41 35.26 17.99Q35.26 16.6 34.69 15.58Q34.11 14.57 33.18 14.04Q32.25 13.51 31.17 13.51Z", "x": 21.29, "bounds": [23.21, 10.09, 39.23, 33.1]}, {"ch": "e", "d": "M55.74 19.21H44.47Q44.61 20.88 45.63 21.83Q46.66 22.78 48.17 22.78Q50.34 22.78 51.26 20.91H55.46Q54.79 23.14 52.9 24.57Q51.01 26 48.25 26Q46.02 26 44.26 25.02Q42.49 24.03 41.5 22.22Q40.51 20.41 40.51 18.05Q40.51 15.65 41.49 13.84Q42.46 12.03 44.22 11.06Q45.97 10.09 48.25 10.09Q50.45 10.09 52.19 11.03Q53.93 11.98 54.89 13.72Q55.85 15.46 55.85 17.71Q55.85 18.55 55.74 19.21ZM51.81 16.6Q51.78 15.1 50.73 14.19Q49.67 13.29 48.14 13.29Q46.69 13.29 45.7 14.16Q44.72 15.04 44.49 16.6Z", "x": 39.6, "bounds": [40.51, 10.09, 55.85, 26.0]}, {"ch": "n", "d": "M72.82 16.71V25.75H68.93V17.24Q68.93 15.4 68.01 14.41Q67.09 13.43 65.5 13.43Q63.89 13.43 62.96 14.41Q62.02 15.4 62.02 17.24V25.75H58.13V10.34H62.02V12.26Q62.8 11.26 64.01 10.69Q65.22 10.11 66.67 10.11Q69.43 10.11 71.12 11.85Q72.82 13.59 72.82 16.71Z", "x": 56.21, "bounds": [58.13, 10.11, 72.82, 25.75]}, {"ch": "G", "d": "M89.49 12.17Q88.82 10.95 87.65 10.31Q86.48 9.67 84.93 9.67Q83.2 9.67 81.86 10.45Q80.53 11.23 79.78 12.67Q79.03 14.12 79.03 16.01Q79.03 17.96 79.79 19.41Q80.56 20.86 81.92 21.63Q83.28 22.41 85.09 22.41Q87.32 22.41 88.74 21.23Q90.16 20.05 90.6 17.93H83.92V14.96H94.44V18.35Q94.05 20.38 92.77 22.11Q91.49 23.83 89.47 24.88Q87.46 25.92 84.95 25.92Q82.14 25.92 79.87 24.65Q77.61 23.39 76.31 21.13Q75.02 18.88 75.02 16.01Q75.02 13.15 76.31 10.88Q77.61 8.61 79.87 7.35Q82.14 6.08 84.93 6.08Q88.21 6.08 90.63 7.68Q93.05 9.28 93.97 12.17Z", "x": 74.05, "bounds": [75.02, 6.08, 94.44, 25.92]}, {"ch": "a", "d": "M102.82 10.09Q104.54 10.09 105.84 10.78Q107.13 11.48 107.91 12.54V10.34H111.83V25.75H107.91V23.5Q107.16 24.58 105.84 25.29Q104.51 26 102.79 26Q100.84 26 99.24 25Q97.64 24 96.71 22.18Q95.78 20.35 95.78 17.99Q95.78 15.65 96.71 13.84Q97.64 12.03 99.24 11.06Q100.84 10.09 102.82 10.09ZM103.82 13.51Q102.73 13.51 101.82 14.04Q100.9 14.57 100.33 15.58Q99.76 16.6 99.76 17.99Q99.76 19.38 100.33 20.42Q100.9 21.47 101.83 22.02Q102.76 22.58 103.82 22.58Q104.9 22.58 105.85 22.04Q106.8 21.5 107.35 20.48Q107.91 19.46 107.91 18.05Q107.91 16.63 107.35 15.61Q106.8 14.59 105.85 14.05Q104.9 13.51 103.82 13.51Z", "x": 94.86, "bounds": [95.78, 10.09, 111.83, 26.0]}, {"ch": "t", "d": "M119.62 13.54V20.99Q119.62 21.77 120 22.12Q120.38 22.47 121.27 22.47H123.07V25.75H120.63Q115.7 25.75 115.7 20.97V13.54H113.86V10.34H115.7V6.53H119.62V10.34H123.07V13.54Z", "x": 113.17, "bounds": [113.86, 6.53, 123.07, 25.75]}, {"ch": "e", "d": "M139.55 19.21H128.28Q128.42 20.88 129.45 21.83Q130.48 22.78 131.98 22.78Q134.15 22.78 135.07 20.91H139.27Q138.6 23.14 136.71 24.57Q134.82 26 132.06 26Q129.84 26 128.07 25.02Q126.3 24.03 125.31 22.22Q124.33 20.41 124.33 18.05Q124.33 15.65 125.3 13.84Q126.27 12.03 128.03 11.06Q129.78 10.09 132.06 10.09Q134.26 10.09 136 11.03Q137.74 11.98 138.7 13.72Q139.66 15.46 139.66 17.71Q139.66 18.55 139.55 19.21ZM135.62 16.6Q135.6 15.1 134.54 14.19Q133.48 13.29 131.95 13.29Q130.5 13.29 129.52 14.16Q128.53 15.04 128.31 16.6Z", "x": 123.41, "bounds": [124.33, 10.09, 139.66, 26.0]}, {"ch": "w", "d": "M163.31 10.34 158.8 25.75H154.6L151.79 14.98L148.98 25.75H144.75L140.22 10.34H144.17L146.89 22.08L149.84 10.34H153.96L156.86 22.05L159.58 10.34Z", "x": 140.02, "bounds": [140.22, 10.34, 163.31, 25.75]}, {"ch": "a", "d": "M170.91 10.09Q172.63 10.09 173.93 10.78Q175.22 11.48 176 12.54V10.34H179.92V25.75H176V23.5Q175.25 24.58 173.93 25.29Q172.61 26 170.88 26Q168.93 26 167.33 25Q165.73 24 164.8 22.18Q163.87 20.35 163.87 17.99Q163.87 15.65 164.8 13.84Q165.73 12.03 167.33 11.06Q168.93 10.09 170.91 10.09ZM171.91 13.51Q170.82 13.51 169.91 14.04Q168.99 14.57 168.42 15.58Q167.85 16.6 167.85 17.99Q167.85 19.38 168.42 20.42Q168.99 21.47 169.92 22.02Q170.85 22.58 171.91 22.58Q172.99 22.58 173.94 22.04Q174.89 21.5 175.44 20.48Q176 19.46 176 18.05Q176 16.63 175.44 15.61Q174.89 14.59 173.94 14.05Q172.99 13.51 171.91 13.51Z", "x": 162.95, "bounds": [163.87, 10.09, 179.92, 26.0]}, {"ch": "y", "d": "M197.93 10.34 188.38 33.04H184.24L187.58 25.36L181.4 10.34H185.77L189.75 21.11L193.78 10.34Z", "x": 181.26, "bounds": [181.4, 10.34, 197.93, 33.04]}]}} \ No newline at end of file diff --git a/docs/assets/logo-explore/motion/wordmarks.json b/docs/assets/logo-explore/motion/wordmarks.json new file mode 100644 index 0000000..8b0723a --- /dev/null +++ b/docs/assets/logo-explore/motion/wordmarks.json @@ -0,0 +1 @@ +{"instrument": {"label": "Instrument Sans SemiBold", "fs": 27.83, "baseline": 26.02, "advance": 180.17, "cap": 20.03, "minx": 1.02, "maxx": 180.05, "miny": 5.7, "maxy": 31.72, "letters": [{"ch": "O", "d": "M11.05 26.3Q8.87 26.3 7.04 25.54Q5.2 24.78 3.86 23.4Q2.51 22.01 1.77 20.11Q1.02 18.21 1.02 15.92Q1.02 12.88 2.3 10.58Q3.58 8.28 5.85 6.99Q8.12 5.7 11.02 5.7Q13.98 5.7 16.24 6.99Q18.5 8.28 19.78 10.59Q21.07 12.9 21.07 15.94Q21.07 18.22 20.33 20.12Q19.58 22.02 18.24 23.4Q16.89 24.78 15.05 25.54Q13.22 26.3 11.05 26.3ZM11.02 23.35Q12.9 23.35 14.34 22.4Q15.77 21.44 16.58 19.77Q17.4 18.09 17.4 15.89Q17.4 13.72 16.6 12.09Q15.8 10.45 14.36 9.55Q12.93 8.65 11.02 8.65Q9.13 8.65 7.7 9.55Q6.27 10.45 5.48 12.07Q4.69 13.69 4.69 15.89Q4.69 18.11 5.5 19.79Q6.3 21.46 7.73 22.4Q9.15 23.35 11.02 23.35Z", "x": 0.0, "bounds": [1.02, 5.7, 21.07, 26.3]}, {"ch": "p", "d": "M31.5 26.29Q29.48 26.29 28.21 25.33Q26.94 24.38 26.62 22.69L26.92 22.65V31.72H23.39V11.83H26.82V15.03L26.55 14.96Q26.93 13.38 28.33 12.46Q29.73 11.55 31.71 11.55Q33.62 11.55 35.03 12.46Q36.43 13.37 37.2 15.01Q37.97 16.65 37.97 18.87Q37.97 21.11 37.17 22.78Q36.36 24.45 34.91 25.37Q33.47 26.29 31.5 26.29ZM30.61 23.41Q32.3 23.41 33.31 22.22Q34.32 21.03 34.32 18.88Q34.32 16.75 33.3 15.59Q32.28 14.42 30.57 14.42Q28.9 14.42 27.87 15.61Q26.84 16.79 26.84 18.93Q26.84 21.06 27.87 22.23Q28.9 23.41 30.61 23.41Z", "x": 21.54, "bounds": [23.39, 11.55, 37.97, 31.72]}, {"ch": "e", "d": "M46.7 26.3Q44.45 26.3 42.76 25.35Q41.07 24.41 40.13 22.74Q39.2 21.07 39.2 18.9Q39.2 16.71 40.13 15.06Q41.06 13.4 42.74 12.48Q44.42 11.55 46.63 11.55Q48.75 11.55 50.31 12.43Q51.88 13.31 52.74 14.89Q53.59 16.46 53.59 18.6Q53.59 18.99 53.57 19.32Q53.55 19.65 53.49 19.98H41.39V17.52H50.79L50.07 18.2Q50.07 16.19 49.15 15.19Q48.22 14.18 46.59 14.18Q44.79 14.18 43.76 15.41Q42.72 16.64 42.72 18.95Q42.72 21.24 43.76 22.45Q44.79 23.66 46.72 23.66Q47.84 23.66 48.67 23.24Q49.49 22.82 49.89 21.95H53.21Q52.52 23.97 50.85 25.13Q49.19 26.3 46.7 26.3Z", "x": 38.46, "bounds": [39.2, 11.55, 53.59, 26.3]}, {"ch": "n", "d": "M55.64 26.02V11.83H59.07V15.17H59.17V26.02ZM65.61 26.02V17.06Q65.61 15.74 64.95 15.08Q64.28 14.42 62.99 14.42Q61.88 14.42 61.01 14.92Q60.14 15.42 59.65 16.31Q59.17 17.19 59.17 18.37L58.81 14.97Q59.51 13.4 60.91 12.48Q62.3 11.55 64.25 11.55Q66.57 11.55 67.85 12.85Q69.14 14.15 69.14 16.32V26.02Z", "x": 53.79, "bounds": [55.64, 11.55, 69.14, 26.02]}, {"ch": "G", "d": "M81.37 26.3Q78.18 26.3 75.87 25.02Q73.56 23.75 72.31 21.46Q71.06 19.16 71.06 16.07Q71.06 13 72.32 10.67Q73.57 8.34 75.8 7.02Q78.03 5.7 80.97 5.7Q83.36 5.7 85.26 6.58Q87.17 7.45 88.36 8.97Q89.55 10.49 89.79 12.49H86.17Q85.89 10.71 84.5 9.68Q83.12 8.65 81.04 8.65Q79.14 8.65 77.72 9.55Q76.3 10.44 75.52 12.09Q74.73 13.73 74.73 16Q74.73 18.33 75.52 19.98Q76.32 21.64 77.84 22.51Q79.37 23.38 81.57 23.38Q83.25 23.38 84.47 22.94Q85.7 22.5 86.46 21.66L89.93 23.01Q88.35 24.63 86.27 25.46Q84.18 26.3 81.37 26.3ZM86.32 23.01V17.18L87.18 18.04H81.54V15.18H89.93V23.01Z", "x": 70.04, "bounds": [71.06, 5.7, 89.93, 26.3]}, {"ch": "a", "d": "M101.23 26.02Q101.08 25.45 101.02 24.78Q100.96 24.12 100.96 23.19H100.84V16.45Q100.84 15.34 100.22 14.76Q99.59 14.18 98.3 14.18Q97.06 14.18 96.32 14.64Q95.58 15.1 95.42 15.96H92.05Q92.28 14 93.94 12.77Q95.6 11.55 98.42 11.55Q101.34 11.55 102.85 12.87Q104.35 14.2 104.35 16.74V23.19Q104.35 23.86 104.44 24.55Q104.53 25.24 104.73 26.02ZM96.31 26.3Q94.16 26.3 92.9 25.22Q91.63 24.15 91.63 22.34Q91.63 20.41 93.05 19.26Q94.47 18.11 97.07 17.73L101.48 17.06V19.28L97.64 19.88Q96.38 20.07 95.76 20.56Q95.14 21.06 95.14 21.95Q95.14 22.77 95.72 23.2Q96.3 23.62 97.34 23.62Q98.83 23.62 99.84 22.87Q100.84 22.11 100.84 21.03L101.22 23.17Q100.67 24.7 99.4 25.5Q98.13 26.3 96.31 26.3Z", "x": 90.52, "bounds": [91.63, 11.55, 104.73, 26.3]}, {"ch": "t", "d": "M112.95 26.3Q110.34 26.3 109.1 25.07Q107.86 23.83 107.86 21.35V8.61L111.39 7.29V21.44Q111.39 22.46 111.94 22.97Q112.5 23.47 113.71 23.47Q114.17 23.47 114.55 23.39Q114.92 23.31 115.25 23.2V25.94Q114.91 26.11 114.3 26.21Q113.7 26.3 112.95 26.3ZM105.13 14.57V11.83H115.25V14.57Z", "x": 104.71, "bounds": [105.13, 7.29, 115.25, 26.3]}, {"ch": "e", "d": "M123.25 26.3Q121 26.3 119.31 25.35Q117.62 24.41 116.68 22.74Q115.75 21.07 115.75 18.9Q115.75 16.71 116.68 15.06Q117.61 13.4 119.29 12.48Q120.97 11.55 123.18 11.55Q125.3 11.55 126.86 12.43Q128.43 13.31 129.28 14.89Q130.14 16.46 130.14 18.6Q130.14 18.99 130.12 19.32Q130.1 19.65 130.04 19.98H117.94V17.52H127.34L126.62 18.2Q126.62 16.19 125.7 15.19Q124.77 14.18 123.14 14.18Q121.34 14.18 120.31 15.41Q119.27 16.64 119.27 18.95Q119.27 21.24 120.31 22.45Q121.34 23.66 123.27 23.66Q124.39 23.66 125.22 23.24Q126.04 22.82 126.44 21.95H129.76Q129.07 23.97 127.4 25.13Q125.74 26.3 123.25 26.3Z", "x": 115.01, "bounds": [115.75, 11.55, 130.14, 26.3]}, {"ch": "w", "d": "M134.79 26.02 130.45 11.83H134.13L136.69 22.48H136.43L139.58 11.83H142.78L145.97 22.48H145.71L148.24 11.83H151.81L147.47 26.02H144.19L141 15.47H141.27L138.06 26.02Z", "x": 130.0, "bounds": [130.45, 11.83, 151.81, 26.02]}, {"ch": "a", "d": "M161.81 26.02Q161.66 25.45 161.6 24.78Q161.54 24.12 161.54 23.19H161.42V16.45Q161.42 15.34 160.79 14.76Q160.17 14.18 158.88 14.18Q157.63 14.18 156.9 14.64Q156.16 15.1 155.99 15.96H152.63Q152.86 14 154.52 12.77Q156.18 11.55 159 11.55Q161.92 11.55 163.42 12.87Q164.93 14.2 164.93 16.74V23.19Q164.93 23.86 165.02 24.55Q165.11 25.24 165.31 26.02ZM156.88 26.3Q154.74 26.3 153.47 25.22Q152.21 24.15 152.21 22.34Q152.21 20.41 153.63 19.26Q155.05 18.11 157.64 17.73L162.06 17.06V19.28L158.22 19.88Q156.96 20.07 156.34 20.56Q155.71 21.06 155.71 21.95Q155.71 22.77 156.29 23.2Q156.87 23.62 157.91 23.62Q159.41 23.62 160.41 22.87Q161.42 22.11 161.42 21.03L161.8 23.17Q161.24 24.7 159.98 25.5Q158.71 26.3 156.88 26.3Z", "x": 151.1, "bounds": [152.21, 11.55, 165.31, 26.3]}, {"ch": "y", "d": "M168 31.72 171.68 23.48 172.33 22.42 176.32 11.83H180.05L171.58 31.72ZM171.15 25.88 165.39 11.83H169.21L174.02 24.56Z", "x": 165.26, "bounds": [165.39, 11.83, 180.05, 31.72]}]}, "outfit": {"label": "Outfit SemiBold", "fs": 27.83, "baseline": 25.78, "advance": 179.23, "cap": 19.56, "minx": 0.99, "maxx": 179.01, "miny": 5.91, "maxy": 31.42, "letters": [{"ch": "O", "d": "M11.17 26.09Q9.02 26.09 7.17 25.31Q5.32 24.53 3.93 23.15Q2.55 21.77 1.77 19.93Q0.99 18.09 0.99 15.97Q0.99 13.85 1.77 12.03Q2.54 10.21 3.91 8.82Q5.29 7.44 7.12 6.68Q8.96 5.91 11.11 5.91Q13.26 5.91 15.1 6.68Q16.93 7.44 18.32 8.82Q19.7 10.21 20.47 12.04Q21.25 13.88 21.25 16Q21.25 18.11 20.47 19.95Q19.7 21.79 18.33 23.16Q16.95 24.54 15.12 25.31Q13.29 26.09 11.17 26.09ZM11.11 22.57Q12.98 22.57 14.38 21.73Q15.78 20.9 16.57 19.41Q17.36 17.93 17.36 15.97Q17.36 14.52 16.91 13.32Q16.46 12.13 15.62 11.25Q14.79 10.37 13.65 9.9Q12.51 9.43 11.11 9.43Q9.26 9.43 7.86 10.25Q6.46 11.07 5.67 12.55Q4.88 14.02 4.88 15.97Q4.88 17.43 5.33 18.65Q5.78 19.87 6.61 20.74Q7.43 21.61 8.58 22.09Q9.73 22.57 11.11 22.57Z", "x": 0.0, "bounds": [0.99, 5.91, 21.25, 26.09]}, {"ch": "p", "d": "M30.78 26.06Q29.32 26.06 28.15 25.47Q26.98 24.88 26.29 23.84Q25.59 22.8 25.53 21.48V16.71Q25.59 15.39 26.3 14.33Q27.01 13.28 28.17 12.67Q29.34 12.06 30.78 12.06Q32.64 12.06 34.11 12.99Q35.59 13.91 36.44 15.5Q37.29 17.08 37.29 19.08Q37.29 21.06 36.44 22.64Q35.59 24.22 34.11 25.14Q32.64 26.06 30.78 26.06ZM23.3 31.42V12.34H26.96V15.87L26.34 19.14L26.93 22.4V31.42ZM30.14 22.69Q31.15 22.69 31.91 22.23Q32.68 21.77 33.12 20.95Q33.56 20.12 33.56 19.06Q33.56 17.99 33.12 17.17Q32.68 16.35 31.92 15.89Q31.15 15.43 30.15 15.43Q29.15 15.43 28.37 15.89Q27.6 16.35 27.17 17.17Q26.74 17.99 26.74 19.06Q26.74 20.13 27.17 20.95Q27.59 21.77 28.37 22.23Q29.14 22.69 30.14 22.69Z", "x": 21.7, "bounds": [23.3, 12.06, 37.29, 31.42]}, {"ch": "e", "d": "M45.54 26.07Q43.44 26.07 41.79 25.17Q40.14 24.26 39.19 22.67Q38.23 21.07 38.23 19.06Q38.23 17.06 39.17 15.47Q40.1 13.89 41.71 12.97Q43.33 12.05 45.31 12.05Q47.24 12.05 48.73 12.91Q50.21 13.78 51.06 15.3Q51.91 16.81 51.91 18.75Q51.91 19.11 51.87 19.49Q51.83 19.86 51.73 20.33L40.42 20.35V17.63L50.03 17.61L48.54 18.73Q48.5 17.54 48.12 16.73Q47.74 15.93 47.04 15.51Q46.33 15.08 45.29 15.08Q44.21 15.08 43.41 15.56Q42.61 16.04 42.17 16.91Q41.74 17.79 41.74 19.01Q41.74 20.23 42.2 21.13Q42.66 22.03 43.52 22.51Q44.38 23 45.52 23Q46.54 23 47.38 22.65Q48.22 22.3 48.83 21.6L50.98 23.76Q49.99 24.9 48.58 25.49Q47.18 26.07 45.54 26.07Z", "x": 37.51, "bounds": [38.23, 12.05, 51.91, 26.07]}, {"ch": "n", "d": "M62.65 25.78V18.08Q62.65 16.88 61.9 16.12Q61.15 15.37 59.98 15.37Q59.18 15.37 58.57 15.71Q57.95 16.05 57.61 16.66Q57.27 17.28 57.27 18.08L55.84 17.34Q55.84 15.78 56.53 14.59Q57.21 13.41 58.42 12.73Q59.63 12.06 61.16 12.06Q62.64 12.06 63.81 12.8Q64.97 13.54 65.64 14.73Q66.31 15.92 66.31 17.27V25.78ZM53.61 25.78V12.34H57.27V25.78Z", "x": 52.01, "bounds": [53.61, 12.06, 66.31, 25.78]}, {"ch": "G", "d": "M78.12 26.07Q76.04 26.07 74.22 25.3Q72.39 24.52 71.02 23.14Q69.66 21.76 68.88 19.93Q68.1 18.09 68.1 15.98Q68.1 13.86 68.89 12.03Q69.68 10.21 71.07 8.83Q72.46 7.46 74.33 6.69Q76.19 5.93 78.35 5.93Q80.77 5.93 82.8 6.83Q84.83 7.74 86.17 9.37L83.6 11.94Q82.7 10.72 81.34 10.08Q79.97 9.45 78.33 9.45Q76.46 9.45 75.02 10.27Q73.59 11.09 72.78 12.56Q71.98 14.02 71.98 15.97Q71.98 17.92 72.78 19.4Q73.58 20.88 74.96 21.72Q76.33 22.55 78.1 22.55Q79.94 22.55 81.25 21.87Q82.56 21.2 83.26 19.87Q83.96 18.55 83.96 16.63L86.41 18.32L77.87 18.25V14.95H87.84V15.49Q87.84 18.99 86.59 21.35Q85.34 23.71 83.15 24.89Q80.96 26.07 78.12 26.07Z", "x": 67.09, "bounds": [68.1, 5.93, 87.84, 26.07]}, {"ch": "a", "d": "M95.4 26.06Q93.57 26.06 92.1 25.14Q90.64 24.22 89.79 22.64Q88.95 21.06 88.95 19.08Q88.95 17.08 89.79 15.5Q90.64 13.91 92.1 12.99Q93.57 12.06 95.4 12.06Q96.85 12.06 97.99 12.65Q99.13 13.25 99.82 14.28Q100.51 15.32 100.56 16.64V21.48Q100.51 22.8 99.83 23.84Q99.15 24.88 98 25.47Q96.85 26.06 95.4 26.06ZM96.08 22.69Q97.61 22.69 98.55 21.67Q99.49 20.65 99.49 19.06Q99.49 17.99 99.07 17.17Q98.64 16.35 97.88 15.89Q97.11 15.43 96.09 15.43Q95.09 15.43 94.32 15.89Q93.56 16.35 93.12 17.18Q92.67 18 92.67 19.06Q92.67 20.13 93.11 20.95Q93.55 21.77 94.32 22.23Q95.09 22.69 96.08 22.69ZM99.3 25.78V22.17L99.89 18.89L99.3 15.65V12.34H102.93V25.78Z", "x": 88.18, "bounds": [88.95, 12.06, 102.93, 26.06]}, {"ch": "t", "d": "M107.56 25.78V6.76H111.22V25.78ZM104.43 15.54V12.34H114.35V15.54Z", "x": 103.99, "bounds": [104.43, 6.76, 114.35, 25.78]}, {"ch": "e", "d": "M121.95 26.07Q119.85 26.07 118.2 25.17Q116.55 24.26 115.6 22.67Q114.64 21.07 114.64 19.06Q114.64 17.06 115.58 15.47Q116.51 13.89 118.13 12.97Q119.74 12.05 121.72 12.05Q123.65 12.05 125.14 12.91Q126.62 13.78 127.47 15.3Q128.32 16.81 128.32 18.75Q128.32 19.11 128.28 19.49Q128.24 19.86 128.14 20.33L116.83 20.35V17.63L126.44 17.61L124.95 18.73Q124.91 17.54 124.53 16.73Q124.15 15.93 123.45 15.51Q122.74 15.08 121.7 15.08Q120.62 15.08 119.82 15.56Q119.02 16.04 118.58 16.91Q118.15 17.79 118.15 19.01Q118.15 20.23 118.61 21.13Q119.07 22.03 119.93 22.51Q120.79 23 121.93 23Q122.95 23 123.79 22.65Q124.63 22.3 125.25 21.6L127.39 23.76Q126.4 24.9 124.99 25.49Q123.59 26.07 121.95 26.07Z", "x": 113.92, "bounds": [114.64, 12.05, 128.32, 26.07]}, {"ch": "w", "d": "M133.45 25.78 128.42 12.34H132.1L135.36 22.11L134.27 22.14L137.63 12.34H140.26L143.63 22.14L142.53 22.11L145.81 12.34H149.48L144.46 25.78H141.82L138.44 16.49H139.47L136.07 25.78Z", "x": 128.25, "bounds": [128.42, 12.34, 149.48, 25.78]}, {"ch": "a", "d": "M156.01 26.06Q154.17 26.06 152.71 25.14Q151.24 24.22 150.4 22.64Q149.55 21.06 149.55 19.08Q149.55 17.08 150.4 15.5Q151.24 13.91 152.71 12.99Q154.17 12.06 156.01 12.06Q157.45 12.06 158.59 12.65Q159.74 13.25 160.43 14.28Q161.12 15.32 161.17 16.64V21.48Q161.12 22.8 160.44 23.84Q159.76 24.88 158.61 25.47Q157.45 26.06 156.01 26.06ZM156.69 22.69Q158.22 22.69 159.16 21.67Q160.1 20.65 160.1 19.06Q160.1 17.99 159.67 17.17Q159.25 16.35 158.48 15.89Q157.72 15.43 156.7 15.43Q155.69 15.43 154.93 15.89Q154.16 16.35 153.72 17.18Q153.28 18 153.28 19.06Q153.28 20.13 153.72 20.95Q154.16 21.77 154.93 22.23Q155.69 22.69 156.69 22.69ZM159.91 25.78V22.17L160.49 18.89L159.91 15.65V12.34H163.53V25.78Z", "x": 148.79, "bounds": [149.55, 12.06, 163.53, 26.06]}, {"ch": "y", "d": "M170.43 25.89 164.8 12.34H168.77L172.49 22.69H171.16L175.03 12.34H179.01L173.02 25.89ZM166.7 31.42 170.8 22.76 173.02 25.89 170.58 31.42Z", "x": 164.59, "bounds": [164.8, 12.34, 179.01, 31.42]}]}, "space": {"label": "Space Grotesk SemiBold", "fs": 27.83, "baseline": 25.74, "advance": 180.65, "cap": 19.48, "minx": 1.46, "maxx": 178.61, "miny": 5.87, "maxy": 31.3, "letters": [{"ch": "O", "d": "M9.38 26.13Q5.75 26.13 3.61 24.11Q1.46 22.09 1.46 18.31V13.69Q1.46 9.91 3.61 7.89Q5.75 5.87 9.38 5.87Q13.01 5.87 15.16 7.89Q17.3 9.91 17.3 13.69V18.31Q17.3 22.09 15.16 24.11Q13.01 26.13 9.38 26.13ZM9.38 23.14Q11.56 23.14 12.76 21.86Q13.96 20.59 13.96 18.41V13.59Q13.96 11.41 12.76 10.14Q11.56 8.86 9.38 8.86Q7.23 8.86 6.02 10.14Q4.8 11.41 4.8 13.59V18.41Q4.8 20.59 6.02 21.86Q7.23 23.14 9.38 23.14Z", "x": 0.0, "bounds": [1.46, 5.87, 17.3, 26.13]}, {"ch": "p", "d": "M20.23 31.3V11.99H23.36V13.8H23.83Q24.33 12.94 25.37 12.27Q26.41 11.6 28.34 11.6Q30.03 11.6 31.46 12.42Q32.89 13.25 33.76 14.83Q34.62 16.41 34.62 18.65V19.08Q34.62 21.31 33.76 22.89Q32.9 24.48 31.47 25.3Q30.04 26.13 28.34 26.13Q27.05 26.13 26.17 25.82Q25.28 25.51 24.75 25.01Q24.21 24.52 23.89 24.02H23.42V31.3ZM27.4 23.34Q29.16 23.34 30.29 22.22Q31.43 21.1 31.43 19V18.73Q31.43 16.62 30.28 15.5Q29.14 14.38 27.4 14.38Q25.67 14.38 24.52 15.5Q23.37 16.62 23.37 18.73V19Q23.37 21.1 24.52 22.22Q25.67 23.34 27.4 23.34Z", "x": 18.2, "bounds": [20.23, 11.6, 34.62, 31.3]}, {"ch": "e", "d": "M43.74 26.13Q41.67 26.13 40.11 25.25Q38.54 24.38 37.66 22.78Q36.79 21.18 36.79 19.03V18.7Q36.79 16.54 37.65 14.94Q38.52 13.35 40.07 12.47Q41.62 11.6 43.66 11.6Q45.66 11.6 47.16 12.48Q48.65 13.37 49.49 14.95Q50.32 16.53 50.32 18.63V19.78H40.02Q40.08 21.39 41.16 22.36Q42.25 23.34 43.83 23.34Q45.38 23.34 46.13 22.66Q46.89 21.98 47.29 21.12L49.91 22.48Q49.52 23.23 48.78 24.09Q48.04 24.94 46.83 25.54Q45.61 26.13 43.74 26.13ZM40.05 17.36H47.09Q46.98 15.99 46.05 15.19Q45.12 14.38 43.63 14.38Q42.11 14.38 41.19 15.19Q40.26 15.99 40.05 17.36Z", "x": 35.42, "bounds": [36.79, 11.6, 50.32, 26.13]}, {"ch": "n", "d": "M53.07 25.74V11.99H56.2V13.92H56.67Q57.03 13.13 57.99 12.44Q58.95 11.75 60.86 11.75Q62.45 11.75 63.67 12.47Q64.88 13.18 65.56 14.47Q66.24 15.75 66.24 17.5V25.74H63.06V17.75Q63.06 16.08 62.23 15.26Q61.4 14.45 59.91 14.45Q58.21 14.45 57.23 15.57Q56.25 16.7 56.25 18.77V25.74Z", "x": 51.03, "bounds": [53.07, 11.75, 66.24, 25.74]}, {"ch": "G", "d": "M76.27 26.13Q74.22 26.13 72.59 25.22Q70.95 24.32 70 22.58Q69.05 20.83 69.05 18.31V13.69Q69.05 9.91 71.15 7.89Q73.25 5.87 76.84 5.87Q80.39 5.87 82.32 7.77Q84.24 9.68 84.24 12.91V13.03H80.94V12.81Q80.94 11.7 80.49 10.8Q80.05 9.9 79.14 9.38Q78.23 8.86 76.84 8.86Q74.76 8.86 73.57 10.14Q72.39 11.41 72.39 13.63V18.37Q72.39 20.57 73.57 21.87Q74.76 23.18 76.88 23.18Q79 23.18 79.98 22.04Q80.95 20.9 80.95 19.12V18.79H76.02V15.95H84.24V25.74H81.16V23.82H80.68Q80.46 24.32 80 24.86Q79.54 25.41 78.65 25.77Q77.77 26.13 76.27 26.13Z", "x": 67.59, "bounds": [69.05, 5.87, 84.24, 26.13]}, {"ch": "a", "d": "M91.72 26.13Q90.26 26.13 89.09 25.62Q87.92 25.11 87.25 24.13Q86.57 23.15 86.57 21.74Q86.57 20.33 87.25 19.39Q87.92 18.44 89.12 17.96Q90.32 17.48 91.85 17.48H95.84V16.65Q95.84 15.56 95.17 14.88Q94.5 14.2 93.08 14.2Q91.69 14.2 90.98 14.85Q90.27 15.49 90.04 16.53L87.1 15.56Q87.43 14.47 88.17 13.58Q88.9 12.69 90.13 12.14Q91.36 11.6 93.13 11.6Q95.82 11.6 97.38 12.96Q98.93 14.31 98.93 16.87V22.26Q98.93 23.1 99.71 23.1H100.86V25.74H98.62Q97.62 25.74 96.98 25.24Q96.34 24.73 96.34 23.89V23.83H95.86Q95.7 24.21 95.28 24.76Q94.86 25.31 94.01 25.72Q93.16 26.13 91.72 26.13ZM92.25 23.52Q93.84 23.52 94.84 22.62Q95.84 21.72 95.84 20.18V19.89H92.06Q91.01 19.89 90.38 20.34Q89.75 20.8 89.75 21.65Q89.75 22.5 90.41 23.01Q91.06 23.52 92.25 23.52Z", "x": 85.43, "bounds": [86.57, 11.6, 100.86, 26.13]}, {"ch": "t", "d": "M107.21 25.74Q105.89 25.74 105.1 24.94Q104.3 24.14 104.3 22.8V14.64H100.69V11.99H104.3V7.56H107.49V11.99H111.44V14.64H107.49V22.25Q107.49 23.09 108.27 23.09H111.03V25.74Z", "x": 99.95, "bounds": [100.69, 7.56, 111.44, 25.74]}, {"ch": "e", "d": "M119.92 26.13Q117.86 26.13 116.29 25.25Q114.73 24.38 113.85 22.78Q112.98 21.18 112.98 19.03V18.7Q112.98 16.54 113.84 14.94Q114.7 13.35 116.25 12.47Q117.81 11.6 119.84 11.6Q121.85 11.6 123.35 12.48Q124.84 13.37 125.68 14.95Q126.51 16.53 126.51 18.63V19.78H116.21Q116.27 21.39 117.35 22.36Q118.43 23.34 120.02 23.34Q121.56 23.34 122.32 22.66Q123.08 21.98 123.47 21.12L126.1 22.48Q125.71 23.23 124.97 24.09Q124.23 24.94 123.02 25.54Q121.8 26.13 119.92 26.13ZM116.24 17.36H123.28Q123.17 15.99 122.24 15.19Q121.31 14.38 119.82 14.38Q118.3 14.38 117.37 15.19Q116.45 15.99 116.24 17.36Z", "x": 111.61, "bounds": [112.98, 11.6, 126.51, 26.13]}, {"ch": "w", "d": "M130.45 25.74 128.31 11.99H131.48L132.96 23.59H133.43L135.51 11.99H140.64L142.72 23.59H143.19L144.67 11.99H147.84L145.7 25.74H140.39L138.31 14.13H137.84L135.76 25.74Z", "x": 127.11, "bounds": [128.31, 11.99, 147.84, 25.74]}, {"ch": "a", "d": "M154.72 26.13Q153.26 26.13 152.09 25.62Q150.92 25.11 150.24 24.13Q149.56 23.15 149.56 21.74Q149.56 20.33 150.24 19.39Q150.92 18.44 152.12 17.96Q153.32 17.48 154.85 17.48H158.84V16.65Q158.84 15.56 158.17 14.88Q157.5 14.2 156.08 14.2Q154.69 14.2 153.98 14.85Q153.27 15.49 153.04 16.53L150.1 15.56Q150.43 14.47 151.17 13.58Q151.9 12.69 153.13 12.14Q154.36 11.6 156.12 11.6Q158.82 11.6 160.38 12.96Q161.93 14.31 161.93 16.87V22.26Q161.93 23.1 162.71 23.1H163.86V25.74H161.62Q160.62 25.74 159.98 25.24Q159.34 24.73 159.34 23.89V23.83H158.85Q158.7 24.21 158.28 24.76Q157.86 25.31 157.01 25.72Q156.16 26.13 154.72 26.13ZM155.25 23.52Q156.84 23.52 157.84 22.62Q158.84 21.72 158.84 20.18V19.89H155.06Q154 19.89 153.38 20.34Q152.75 20.8 152.75 21.65Q152.75 22.5 153.4 23.01Q154.06 23.52 155.25 23.52Z", "x": 148.42, "bounds": [149.56, 11.6, 163.86, 26.13]}, {"ch": "y", "d": "M167.11 31.3V28.52H174.69Q175.48 28.52 175.48 27.68V23.84H175Q174.76 24.35 174.27 24.84Q173.77 25.33 172.94 25.65Q172.11 25.97 170.82 25.97Q169.23 25.97 168.02 25.26Q166.8 24.54 166.12 23.25Q165.44 21.95 165.44 20.23V11.99H168.63V19.98Q168.63 21.65 169.45 22.46Q170.28 23.28 171.78 23.28Q173.47 23.28 174.45 22.15Q175.43 21.03 175.43 18.96V11.99H178.61V28.36Q178.61 29.71 177.83 30.51Q177.05 31.3 175.71 31.3Z", "x": 163.53, "bounds": [165.44, 11.99, 178.61, 31.3]}]}, "syne": {"label": "Syne SemiBold", "fs": 27.83, "baseline": 25.04, "advance": 201.63, "cap": 18.09, "minx": 1.44, "maxx": 201.13, "miny": 6.68, "maxy": 30.61, "letters": [{"ch": "O", "d": "M11.91 25.37Q8.73 25.37 6.38 24.24Q4.03 23.1 2.74 21.01Q1.44 18.93 1.44 16.04Q1.44 13.13 2.74 11.03Q4.03 8.94 6.38 7.81Q8.73 6.68 11.91 6.68Q15.11 6.68 17.45 7.81Q19.79 8.94 21.08 11.03Q22.38 13.13 22.38 16.04Q22.38 18.93 21.08 21.01Q19.79 23.1 17.45 24.24Q15.11 25.37 11.91 25.37ZM11.91 22.59Q14.01 22.59 15.63 21.86Q17.25 21.14 18.18 19.68Q19.12 18.22 19.12 16.04Q19.12 13.85 18.18 12.39Q17.25 10.94 15.63 10.21Q14.01 9.47 11.91 9.47Q9.84 9.47 8.19 10.21Q6.54 10.94 5.6 12.39Q4.65 13.85 4.65 16.04Q4.65 18.22 5.6 19.68Q6.54 21.14 8.19 21.86Q9.84 22.59 11.91 22.59Z", "x": 0.0, "bounds": [1.44, 6.68, 22.38, 25.37]}, {"ch": "p", "d": "M33.91 25.36Q32.18 25.36 31.01 24.68Q29.83 24.01 29.12 22.92Q28.41 21.82 28.08 20.56Q27.76 19.3 27.76 18.13Q27.76 16.61 28.07 15.3Q28.38 14 29.09 13Q29.79 12.01 30.97 11.45Q32.15 10.89 33.91 10.89Q35.84 10.89 37.4 11.82Q38.95 12.74 39.88 14.38Q40.8 16.01 40.8 18.13Q40.8 20.29 39.86 21.92Q38.92 23.55 37.36 24.45Q35.8 25.36 33.91 25.36ZM32.96 22.8Q34.37 22.8 35.42 22.21Q36.48 21.62 37.06 20.57Q37.65 19.52 37.65 18.13Q37.65 16.05 36.43 14.73Q35.21 13.42 32.96 13.42Q31.58 13.42 30.47 14Q29.36 14.57 28.72 15.62Q28.09 16.67 28.09 18.13Q28.09 19.49 28.66 20.54Q29.23 21.59 30.33 22.2Q31.42 22.8 32.96 22.8ZM24.98 11.18H27.95L28.09 14.58V30.61H24.98Z", "x": 23.26, "bounds": [24.98, 10.89, 40.8, 30.61]}, {"ch": "e", "d": "M54.57 20.49H57.56Q57.33 21.89 56.45 22.99Q55.56 24.09 54.07 24.72Q52.57 25.36 50.48 25.36Q48.1 25.36 46.24 24.48Q44.39 23.6 43.33 21.98Q42.28 20.36 42.28 18.15Q42.28 15.94 43.3 14.31Q44.33 12.67 46.15 11.78Q47.97 10.89 50.36 10.89Q52.83 10.89 54.49 11.77Q56.15 12.64 56.96 14.38Q57.76 16.12 57.62 18.76H45.4Q45.54 19.93 46.18 20.85Q46.82 21.78 47.9 22.3Q48.98 22.83 50.43 22.83Q52.04 22.83 53.14 22.18Q54.23 21.53 54.57 20.49ZM50.29 13.4Q48.39 13.4 47.17 14.32Q45.95 15.24 45.57 16.69H54.52Q54.39 15.13 53.25 14.26Q52.1 13.4 50.29 13.4Z", "x": 41.27, "bounds": [42.28, 10.89, 57.64, 25.36]}, {"ch": "n", "d": "M59.82 11.18H62.93V25.04H59.82ZM68.75 10.89Q69.97 10.89 70.98 11.22Q71.99 11.56 72.73 12.23Q73.46 12.91 73.87 13.91Q74.27 14.92 74.27 16.28V25.04H71.17V16.89Q71.17 15.18 70.35 14.37Q69.54 13.55 67.78 13.55Q66.44 13.55 65.34 14.12Q64.25 14.69 63.56 15.6Q62.87 16.51 62.75 17.58L62.72 16.09Q62.86 15.04 63.34 14.11Q63.81 13.18 64.59 12.45Q65.37 11.72 66.42 11.3Q67.47 10.89 68.75 10.89Z", "x": 58.1, "bounds": [59.82, 10.89, 74.27, 25.04]}, {"ch": "G", "d": "M86.95 25.37Q84.64 25.37 82.78 24.76Q80.93 24.14 79.6 22.96Q78.27 21.78 77.56 20.06Q76.85 18.35 76.85 16.17Q76.85 13.97 77.61 12.22Q78.36 10.46 79.75 9.22Q81.14 7.98 83.05 7.33Q84.97 6.68 87.29 6.68Q89.98 6.68 91.96 7.51Q93.94 8.33 95.11 9.83Q96.28 11.32 96.46 13.37H93.51Q93.15 12.17 92.33 11.3Q91.52 10.43 90.26 9.95Q89 9.47 87.29 9.47Q85.06 9.47 83.44 10.31Q81.82 11.16 80.95 12.67Q80.08 14.18 80.08 16.17Q80.08 18.13 80.85 19.57Q81.62 21.02 83.17 21.8Q84.72 22.59 87.03 22.59Q88.82 22.59 90.17 22.04Q91.53 21.5 92.42 20.54Q93.31 19.57 93.66 18.28L95.08 17.94Q94.69 21.53 92.53 23.45Q90.38 25.37 86.95 25.37ZM94.32 25.04Q94.32 24.16 94.4 23.02Q94.49 21.89 94.62 20.72Q94.76 19.55 94.87 18.58H87.41V16.32H97.14V25.04Z", "x": 75.41, "bounds": [76.85, 6.68, 97.14, 25.37]}, {"ch": "a", "d": "M106.22 25.34Q104.31 25.34 102.74 24.42Q101.17 23.51 100.25 21.88Q99.32 20.25 99.32 18.13Q99.32 15.98 100.26 14.35Q101.2 12.73 102.81 11.81Q104.42 10.89 106.45 10.89Q108.67 10.89 109.97 11.86Q111.27 12.83 111.83 14.47Q112.39 16.11 112.39 18.13Q112.39 19.3 112.06 20.55Q111.73 21.81 111.02 22.9Q110.31 23.99 109.13 24.66Q107.94 25.34 106.22 25.34ZM107.16 22.8Q108.72 22.8 109.82 22.2Q110.92 21.59 111.49 20.53Q112.06 19.47 112.06 18.13Q112.06 16.67 111.47 15.62Q110.89 14.57 109.8 14.01Q108.71 13.44 107.16 13.44Q104.94 13.44 103.71 14.74Q102.48 16.05 102.48 18.13Q102.48 19.52 103.07 20.57Q103.67 21.62 104.73 22.21Q105.79 22.8 107.16 22.8ZM112.06 11.18H115.16V25.04H112.29Q112.29 25.04 112.23 24.52Q112.17 23.99 112.11 23.22Q112.06 22.44 112.06 21.77Z", "x": 98.31, "bounds": [99.32, 10.89, 115.16, 25.34]}, {"ch": "t", "d": "M117.03 11.18H126.83V13.73H117.03ZM120.38 7.46H123.48V25.04H120.38Z", "x": 116.29, "bounds": [117.03, 7.46, 126.83, 25.04]}, {"ch": "e", "d": "M140.06 20.49H143.04Q142.82 21.89 141.93 22.99Q141.04 24.09 139.55 24.72Q138.05 25.36 135.97 25.36Q133.58 25.36 131.72 24.48Q129.87 23.6 128.81 21.98Q127.76 20.36 127.76 18.15Q127.76 15.94 128.79 14.31Q129.81 12.67 131.63 11.78Q133.45 10.89 135.85 10.89Q138.32 10.89 139.97 11.77Q141.63 12.64 142.44 14.38Q143.24 16.12 143.1 18.76H130.88Q131.02 19.93 131.66 20.85Q132.3 21.78 133.38 22.3Q134.46 22.83 135.91 22.83Q137.53 22.83 138.62 22.18Q139.71 21.53 140.06 20.49ZM135.77 13.4Q133.87 13.4 132.65 14.32Q131.43 15.24 131.06 16.69H140Q139.87 15.13 138.73 14.26Q137.59 13.4 135.77 13.4Z", "x": 126.75, "bounds": [127.76, 10.89, 143.13, 25.36]}, {"ch": "w", "d": "M161.33 22.71 160.41 22.69 164.45 11.18H167.86L162.44 25.04H159.53L155.2 13.51H156.18L151.76 25.04H148.85L143.55 11.18H146.95L150.87 22.7L149.95 22.71L153.9 11.18H157.51Z", "x": 143.08, "bounds": [143.55, 11.18, 167.86, 25.04]}, {"ch": "a", "d": "M175.15 25.34Q173.24 25.34 171.67 24.42Q170.1 23.51 169.17 21.88Q168.25 20.25 168.25 18.13Q168.25 15.98 169.19 14.35Q170.13 12.73 171.74 11.81Q173.34 10.89 175.37 10.89Q177.6 10.89 178.9 11.86Q180.19 12.83 180.75 14.47Q181.31 16.11 181.31 18.13Q181.31 19.3 180.98 20.55Q180.66 21.81 179.95 22.9Q179.24 23.99 178.05 24.66Q176.87 25.34 175.15 25.34ZM176.09 22.8Q177.65 22.8 178.75 22.2Q179.85 21.59 180.41 20.53Q180.98 19.47 180.98 18.13Q180.98 16.67 180.4 15.62Q179.82 14.57 178.73 14.01Q177.64 13.44 176.09 13.44Q173.86 13.44 172.63 14.74Q171.4 16.05 171.4 18.13Q171.4 19.52 172 20.57Q172.6 21.62 173.65 22.21Q174.71 22.8 176.09 22.8ZM180.98 11.18H184.08V25.04H181.21Q181.21 25.04 181.15 24.52Q181.09 23.99 181.04 23.22Q180.98 22.44 180.98 21.77Z", "x": 167.23, "bounds": [168.25, 10.89, 184.08, 25.34]}, {"ch": "y", "d": "M185.68 11.18H188.98L194.6 24.49L191.91 25.04ZM201.13 11.18 192.22 30.61H188.96L192.78 23.03L197.82 11.18Z", "x": 185.21, "bounds": [185.68, 11.18, 201.13, 30.61]}]}, "familjen": {"label": "Familjen Grotesk SemiBold", "fs": 27.83, "baseline": 25.04, "advance": 168.21, "cap": 18.09, "minx": 0.77, "maxx": 167.92, "miny": 6.63, "maxy": 29.68, "letters": [{"ch": "O", "d": "M9.25 25.37Q6.4 25.37 4.51 24.22Q2.62 23.07 1.69 20.96Q0.77 18.85 0.77 16Q0.77 13.12 1.69 11.03Q2.62 8.93 4.51 7.78Q6.4 6.63 9.25 6.63Q12.12 6.63 13.99 7.78Q15.87 8.93 16.8 11.03Q17.73 13.12 17.73 16Q17.73 18.85 16.8 20.96Q15.87 23.07 13.99 24.22Q12.12 25.37 9.25 25.37ZM9.25 22.66Q11.71 22.66 12.88 20.92Q14.04 19.18 14.04 15.99Q14.04 12.81 12.88 11.08Q11.71 9.34 9.25 9.34Q6.78 9.34 5.62 11.08Q4.45 12.81 4.45 15.99Q4.45 19.18 5.62 20.92Q6.78 22.66 9.25 22.66Z", "x": 0.0, "bounds": [0.77, 6.63, 17.73, 25.37]}, {"ch": "p", "d": "M19.73 29.68V11.13H22.9V12.94H23.46Q23.91 12.07 24.85 11.45Q25.78 10.83 27.34 10.83Q29.22 10.83 30.45 11.76Q31.69 12.69 32.3 14.33Q32.92 15.97 32.92 18.1Q32.92 20.26 32.3 21.89Q31.67 23.53 30.43 24.43Q29.19 25.34 27.36 25.34Q25.99 25.34 25.11 24.83Q24.23 24.33 23.73 23.48H23.18V29.68ZM26.43 22.83Q27.81 22.83 28.59 21.63Q29.36 20.43 29.36 18.09Q29.36 15.74 28.59 14.54Q27.81 13.34 26.43 13.34Q24.94 13.34 24.05 14.62Q23.16 15.9 23.16 18.09Q23.16 20.26 24.05 21.54Q24.94 22.83 26.43 22.83Z", "x": 17.95, "bounds": [19.73, 10.83, 32.92, 29.68]}, {"ch": "e", "d": "M40.7 25.39Q37.47 25.39 35.77 23.41Q34.08 21.43 34.08 18.07Q34.08 15.82 34.84 14.19Q35.6 12.57 37.07 11.68Q38.53 10.78 40.66 10.78Q43.7 10.78 45.32 12.66Q46.94 14.53 46.94 18.14V18.91H37.44Q37.55 20.74 38.39 21.83Q39.22 22.91 40.71 22.91Q41.86 22.91 42.55 22.24Q43.25 21.57 43.5 20.59H46.77Q46.53 21.9 45.82 22.99Q45.11 24.08 43.84 24.73Q42.58 25.39 40.7 25.39ZM37.44 16.68H43.58Q43.5 15 42.75 14.08Q42 13.16 40.64 13.16Q39.27 13.16 38.45 14.05Q37.63 14.94 37.44 16.68Z", "x": 33.23, "bounds": [34.08, 10.78, 46.94, 25.39]}, {"ch": "n", "d": "M48.95 25.04V11.13H52.12V13.22H52.67Q53.08 12.14 53.98 11.46Q54.88 10.78 56.52 10.78Q58.87 10.78 60.01 12.25Q61.16 13.72 61.16 16.19V25.04H57.72V16.79Q57.72 15.26 57.17 14.43Q56.63 13.61 55.41 13.61Q53.98 13.61 53.19 14.74Q52.39 15.86 52.39 17.54V25.04Z", "x": 47.17, "bounds": [48.95, 10.78, 61.16, 25.04]}, {"ch": "G", "d": "M70.81 25.37Q68.31 25.37 66.56 24.19Q64.81 23.01 63.9 20.88Q62.98 18.75 62.98 15.95Q62.98 13.1 63.92 11.01Q64.86 8.92 66.72 7.77Q68.57 6.63 71.3 6.63Q73.62 6.63 75.21 7.43Q76.8 8.23 77.7 9.65Q78.61 11.08 78.86 12.99H75.44Q75.16 11.22 74.1 10.28Q73.04 9.34 71.24 9.34Q68.87 9.34 67.72 11.09Q66.56 12.83 66.56 15.96Q66.56 19.19 67.77 20.95Q68.99 22.71 71.34 22.71Q73.32 22.71 74.4 21.54Q75.48 20.38 75.6 18.38H70.3V15.75H78.89V25.04H76.26V22.92H75.7Q75.23 23.88 74.09 24.63Q72.96 25.37 70.81 25.37Z", "x": 62.21, "bounds": [62.98, 6.63, 78.89, 25.37]}, {"ch": "a", "d": "M86.31 25.34Q84.41 25.34 83.15 24.41Q81.9 23.48 81.28 21.83Q80.67 20.19 80.67 18.07Q80.67 15.9 81.29 14.27Q81.92 12.64 83.17 11.74Q84.43 10.83 86.31 10.83Q87.8 10.83 88.74 11.43Q89.68 12.02 90.13 12.94H90.69V11.13H93.86V25.04H90.69V23.23H90.13Q89.68 24.1 88.74 24.72Q87.81 25.34 86.31 25.34ZM87.15 22.83Q88.65 22.83 89.54 21.54Q90.43 20.26 90.43 18.09Q90.43 15.9 89.54 14.62Q88.65 13.34 87.15 13.34Q85.77 13.34 85 14.54Q84.23 15.74 84.23 18.09Q84.23 20.43 85 21.63Q85.77 22.83 87.15 22.83Z", "x": 79.81, "bounds": [80.67, 10.83, 93.86, 25.34]}, {"ch": "t", "d": "M98.85 25.04Q98.32 24.42 98.01 23.54Q97.69 22.66 97.69 20.89V13.6H95.79V11.13H97.74V7.65H101.11V11.13H103.39V13.6H101.12V20.5Q101.12 22.35 101.53 23.31Q101.95 24.28 102.53 24.9V25.04Z", "x": 95.1, "bounds": [95.79, 7.65, 103.39, 25.04]}, {"ch": "e", "d": "M111.01 25.39Q107.77 25.39 106.08 23.41Q104.39 21.43 104.39 18.07Q104.39 15.82 105.15 14.19Q105.91 12.57 107.37 11.68Q108.84 10.78 110.97 10.78Q114.01 10.78 115.63 12.66Q117.25 14.53 117.25 18.14V18.91H107.74Q107.86 20.74 108.7 21.83Q109.53 22.91 111.01 22.91Q112.16 22.91 112.86 22.24Q113.56 21.57 113.8 20.59H117.07Q116.84 21.9 116.13 22.99Q115.41 24.08 114.15 24.73Q112.89 25.39 111.01 25.39ZM107.75 16.68H113.89Q113.8 15 113.06 14.08Q112.31 13.16 110.94 13.16Q109.58 13.16 108.76 14.05Q107.94 14.94 107.75 16.68Z", "x": 103.54, "bounds": [104.39, 10.78, 117.25, 25.39]}, {"ch": "w", "d": "M120.6 25.04 118.38 11.18V11.13H121.58L123.36 22.43H123.9L125.96 11.13H131.46L133.52 22.43H134.06L135.84 11.13H138.83V11.18L136.6 25.04H131L128.89 13.66H128.31L126.21 25.04Z", "x": 117.47, "bounds": [118.38, 11.13, 138.83, 25.04]}, {"ch": "a", "d": "M145.68 25.34Q143.77 25.34 142.52 24.41Q141.26 23.48 140.65 21.83Q140.03 20.19 140.03 18.07Q140.03 15.9 140.66 14.27Q141.28 12.64 142.54 11.74Q143.79 10.83 145.67 10.83Q147.16 10.83 148.1 11.43Q149.04 12.02 149.5 12.94H150.05V11.13H153.22V25.04H150.05V23.23H149.5Q149.04 24.1 148.1 24.72Q147.17 25.34 145.68 25.34ZM146.52 22.83Q148.01 22.83 148.9 21.54Q149.79 20.26 149.79 18.09Q149.79 15.9 148.9 14.62Q148.01 13.34 146.52 13.34Q145.14 13.34 144.36 14.54Q143.59 15.74 143.59 18.09Q143.59 20.43 144.36 21.63Q145.14 22.83 146.52 22.83Z", "x": 139.18, "bounds": [140.03, 10.83, 153.22, 25.34]}, {"ch": "y", "d": "M157.06 29.68V29.61Q157.66 28.64 158.35 27.36Q159.04 26.08 159.57 24.86L154.74 11.27V11.13H158.14L159.87 16Q160.19 16.9 160.51 17.92Q160.82 18.93 161.14 20.48H161.7Q162.02 18.93 162.33 17.92Q162.65 16.9 162.97 16L164.7 11.13H167.92V11.27L163.52 23.3Q162.72 25.52 161.96 27.15Q161.19 28.78 160.64 29.68Z", "x": 154.46, "bounds": [154.74, 11.13, 167.92, 29.68]}]}, "unbounded": {"label": "Unbounded SemiBold", "fs": 27.83, "baseline": 26.43, "advance": 244.81, "cap": 20.87, "minx": 1.07, "maxx": 244.77, "miny": 5.13, "maxy": 31.47, "letters": [{"ch": "O", "d": "M13.36 26.87Q9.69 26.87 6.92 25.51Q4.15 24.15 2.61 21.7Q1.07 19.25 1.07 16Q1.07 12.75 2.61 10.3Q4.15 7.85 6.92 6.49Q9.69 5.13 13.36 5.13Q17.04 5.13 19.81 6.49Q22.58 7.85 24.12 10.3Q25.66 12.75 25.66 16Q25.66 19.25 24.12 21.7Q22.58 24.15 19.81 25.51Q17.04 26.87 13.36 26.87ZM13.36 22.19Q15.52 22.19 17.08 21.44Q18.64 20.69 19.5 19.3Q20.35 17.91 20.35 16Q20.35 14.09 19.5 12.7Q18.64 11.31 17.08 10.56Q15.52 9.81 13.36 9.81Q11.22 9.81 9.65 10.56Q8.09 11.31 7.23 12.7Q6.38 14.09 6.38 16Q6.38 17.91 7.23 19.3Q8.09 20.69 9.65 21.44Q11.22 22.19 13.36 22.19Z", "x": 0.0, "bounds": [1.07, 5.13, 25.66, 26.87]}, {"ch": "p", "d": "M27.76 10.57H32.93V15.37L32.71 16.02V20.15L32.93 21.37V31.03H27.76ZM31.22 18.51Q31.64 15.96 32.81 14.08Q33.98 12.2 35.76 11.17Q37.55 10.13 39.78 10.13Q42.11 10.13 43.88 11.18Q45.65 12.23 46.64 14.11Q47.64 16 47.64 18.51Q47.64 21 46.64 22.89Q45.65 24.77 43.88 25.82Q42.11 26.87 39.78 26.87Q37.54 26.87 35.77 25.83Q33.99 24.79 32.82 22.91Q31.65 21.02 31.22 18.51ZM42.41 18.51Q42.41 17.22 41.88 16.23Q41.36 15.24 40.42 14.68Q39.49 14.12 38.27 14.12Q37.05 14.12 35.95 14.68Q34.85 15.24 34.03 16.23Q33.2 17.22 32.74 18.51Q33.2 19.8 34.03 20.78Q34.85 21.77 35.95 22.33Q37.05 22.89 38.27 22.89Q39.49 22.89 40.42 22.33Q41.36 21.77 41.88 20.78Q42.41 19.8 42.41 18.51Z", "x": 26.1, "bounds": [27.76, 10.13, 47.64, 31.03]}, {"ch": "e", "d": "M58.81 26.87Q55.95 26.87 53.72 25.82Q51.48 24.76 50.19 22.87Q48.91 20.97 48.91 18.46Q48.91 15.97 50.14 14.1Q51.37 12.23 53.52 11.18Q55.67 10.13 58.4 10.13Q61.2 10.13 63.2 11.35Q65.2 12.57 66.28 14.8Q67.37 17.02 67.37 20.04H53.07V16.78H64.52L62.67 17.94Q62.58 16.64 62.03 15.73Q61.49 14.82 60.6 14.33Q59.71 13.85 58.48 13.85Q57.15 13.85 56.18 14.38Q55.21 14.91 54.67 15.86Q54.13 16.81 54.13 18.07Q54.13 19.64 54.84 20.75Q55.54 21.86 56.9 22.45Q58.25 23.03 60.19 23.03Q61.96 23.03 63.74 22.55Q65.52 22.06 66.95 21.16V24.53Q65.35 25.64 63.26 26.26Q61.18 26.87 58.81 26.87Z", "x": 48.03, "bounds": [48.91, 10.13, 67.37, 26.87]}, {"ch": "n", "d": "M68.58 10.57H73.75L74.77 16.76V26.43H69.6V15.98ZM80.71 10.13Q82.98 10.13 84.58 11.04Q86.17 11.95 87.02 13.62Q87.87 15.29 87.87 17.59V26.43H82.69V18.36Q82.69 16.4 81.73 15.37Q80.76 14.35 78.94 14.35Q77.67 14.35 76.73 14.9Q75.78 15.44 75.28 16.43Q74.77 17.43 74.77 18.78L73.13 17.92Q73.44 15.4 74.51 13.67Q75.57 11.93 77.18 11.03Q78.78 10.13 80.71 10.13Z", "x": 67.67, "bounds": [68.58, 10.13, 87.87, 26.43]}, {"ch": "G", "d": "M111.9 17.39Q111.83 19.29 111.14 21Q110.45 22.71 109.14 24.04Q107.84 25.36 105.91 26.12Q103.99 26.87 101.44 26.87Q98.99 26.87 96.85 26.15Q94.72 25.42 93.11 24.03Q91.5 22.64 90.6 20.61Q89.7 18.59 89.7 16Q89.7 13.41 90.61 11.39Q91.52 9.36 93.15 7.97Q94.79 6.58 96.97 5.85Q99.15 5.13 101.72 5.13Q105.11 5.13 107.59 6.18Q110.07 7.24 111.59 9.07Q113.11 10.91 113.52 13.29H108.23Q107.94 12.13 107.05 11.29Q106.16 10.45 104.82 10Q103.47 9.54 101.73 9.54Q99.66 9.54 98.14 10.28Q96.63 11.03 95.81 12.46Q94.98 13.89 94.98 16Q94.98 18.12 95.86 19.59Q96.74 21.07 98.36 21.82Q99.98 22.58 102.18 22.58Q104.32 22.58 105.94 21.94Q107.57 21.3 108.53 20.1Q109.49 18.9 109.63 17.22ZM102.78 19.51V15.77H113.72V26.43H109.98L109.25 18.08L110.29 19.51Z", "x": 88.6, "bounds": [89.7, 5.13, 113.72, 26.87]}, {"ch": "a", "d": "M130.76 26.43 130.06 20.68 130.82 18.51 130.06 16.37 130.76 10.57H136.11L135.15 18.5L136.11 26.43ZM132.03 18.51Q131.62 21.02 130.45 22.91Q129.28 24.79 127.5 25.83Q125.72 26.87 123.48 26.87Q121.16 26.87 119.38 25.82Q117.61 24.77 116.61 22.89Q115.62 21 115.62 18.51Q115.62 16 116.61 14.11Q117.61 12.23 119.38 11.18Q121.16 10.13 123.48 10.13Q125.72 10.13 127.5 11.17Q129.28 12.2 130.46 14.08Q131.65 15.96 132.03 18.51ZM120.84 18.51Q120.84 19.8 121.37 20.78Q121.9 21.77 122.84 22.33Q123.77 22.89 124.98 22.89Q126.22 22.89 127.31 22.33Q128.41 21.77 129.24 20.78Q130.08 19.8 130.52 18.51Q130.08 17.22 129.24 16.23Q128.41 15.24 127.31 14.68Q126.22 14.12 124.98 14.12Q123.77 14.12 122.84 14.68Q121.9 15.24 121.37 16.23Q120.84 17.22 120.84 18.51Z", "x": 114.67, "bounds": [115.62, 10.13, 136.11, 26.87]}, {"ch": "t", "d": "M136.97 14.42V12.15L140.24 10.96L142.46 6.34H145.41V10.57H152.25V14.42H145.41V19.65Q145.41 21.42 146.14 22.14Q146.87 22.86 148.74 22.86Q149.91 22.86 150.83 22.64Q151.74 22.43 152.53 22.05V26Q151.71 26.32 150.36 26.6Q149.01 26.87 147.57 26.87Q145.02 26.87 143.4 26.07Q141.79 25.27 141.01 23.8Q140.24 22.33 140.24 20.37V14.42Z", "x": 136.82, "bounds": [136.97, 6.34, 152.53, 26.87]}, {"ch": "e", "d": "M162.96 26.87Q160.11 26.87 157.87 25.82Q155.63 24.76 154.35 22.87Q153.06 20.97 153.06 18.46Q153.06 15.97 154.29 14.1Q155.52 12.23 157.67 11.18Q159.82 10.13 162.55 10.13Q165.35 10.13 167.35 11.35Q169.35 12.57 170.44 14.8Q171.52 17.02 171.52 20.04H157.23V16.78H168.67L166.82 17.94Q166.73 16.64 166.19 15.73Q165.65 14.82 164.75 14.33Q163.86 13.85 162.63 13.85Q161.3 13.85 160.33 14.38Q159.36 14.91 158.82 15.86Q158.29 16.81 158.29 18.07Q158.29 19.64 158.99 20.75Q159.7 21.86 161.05 22.45Q162.41 23.03 164.35 23.03Q166.12 23.03 167.9 22.55Q169.68 22.06 171.1 21.16V24.53Q169.5 25.64 167.42 26.26Q165.33 26.87 162.96 26.87Z", "x": 152.18, "bounds": [153.06, 10.13, 171.52, 26.87]}, {"ch": "w", "d": "M194.25 23.57H192.6L197.33 10.57H202.5L196.45 26.43H191.17L186.05 13.02H187.83L182.63 26.43H177.35L171.32 10.57H176.52L181.26 23.57H179.57L184.46 10.57H189.43Z", "x": 171.19, "bounds": [171.32, 10.57, 202.5, 26.43]}, {"ch": "a", "d": "M217.57 26.43 216.88 20.68 217.64 18.51 216.88 16.37 217.57 10.57H222.93L221.97 18.5L222.93 26.43ZM218.85 18.51Q218.43 21.02 217.27 22.91Q216.1 24.79 214.32 25.83Q212.53 26.87 210.3 26.87Q207.98 26.87 206.2 25.82Q204.43 24.77 203.43 22.89Q202.44 21 202.44 18.51Q202.44 16 203.43 14.11Q204.43 12.23 206.2 11.18Q207.98 10.13 210.3 10.13Q212.53 10.13 214.32 11.17Q216.1 12.2 217.28 14.08Q218.46 15.96 218.85 18.51ZM207.66 18.51Q207.66 19.8 208.19 20.78Q208.72 21.77 209.65 22.33Q210.59 22.89 211.8 22.89Q213.04 22.89 214.13 22.33Q215.22 21.77 216.06 20.78Q216.9 19.8 217.33 18.51Q216.9 17.22 216.06 16.23Q215.22 15.24 214.13 14.68Q213.04 14.12 211.8 14.12Q210.59 14.12 209.65 14.68Q208.72 15.24 208.19 16.23Q207.66 17.22 207.66 18.51Z", "x": 201.49, "bounds": [202.44, 10.13, 222.93, 26.87]}, {"ch": "y", "d": "M230.16 31.47Q228.49 31.47 227.19 31.06Q225.89 30.64 224.67 29.81V26.16Q225.92 26.96 227.02 27.31Q228.11 27.66 229.44 27.66Q230.59 27.66 231.47 27.15Q232.36 26.65 232.93 25.32L239.27 10.57H244.77L237 26.95Q236.22 28.61 235.13 29.61Q234.04 30.6 232.77 31.03Q231.51 31.47 230.16 31.47ZM230.75 25.2 223.88 10.57H229.57L235.76 25.2Z", "x": 223.64, "bounds": [223.88, 10.57, 244.77, 31.47]}]}, "sora": {"label": "Sora SemiBold", "fs": 27.83, "baseline": 26.16, "advance": 198.34, "cap": 20.31, "minx": 1.31, "maxx": 197.87, "miny": 5.26, "maxy": 32.11, "letters": [{"ch": "O", "d": "M12.03 26.69Q9.28 26.69 7.26 25.72Q5.25 24.75 3.92 23.17Q2.6 21.59 1.95 19.77Q1.31 17.95 1.31 16.25V15.64Q1.31 13.77 1.98 11.93Q2.64 10.09 3.99 8.58Q5.34 7.07 7.35 6.16Q9.35 5.26 12.03 5.26Q14.71 5.26 16.71 6.16Q18.71 7.07 20.06 8.58Q21.41 10.09 22.09 11.93Q22.76 13.77 22.76 15.64V16.25Q22.76 17.95 22.11 19.77Q21.45 21.59 20.13 23.17Q18.81 24.75 16.79 25.72Q14.78 26.69 12.03 26.69ZM12.03 23.01Q13.59 23.01 14.84 22.46Q16.08 21.9 16.96 20.92Q17.84 19.94 18.3 18.67Q18.75 17.41 18.75 15.97Q18.75 14.46 18.29 13.18Q17.82 11.9 16.95 10.94Q16.07 9.99 14.82 9.46Q13.58 8.93 12.03 8.93Q10.48 8.93 9.23 9.46Q7.99 9.99 7.11 10.94Q6.23 11.9 5.77 13.18Q5.31 14.46 5.31 15.97Q5.31 17.41 5.77 18.67Q6.23 19.94 7.11 20.92Q7.99 21.9 9.23 22.46Q10.48 23.01 12.03 23.01Z", "x": 0.0, "bounds": [1.31, 5.26, 22.76, 26.69]}, {"ch": "p", "d": "M25.71 31.72V11.05H28.77V17.57L28.3 17.54Q28.41 15.22 29.26 13.67Q30.11 12.12 31.53 11.35Q32.95 10.57 34.7 10.57Q36.3 10.57 37.6 11.17Q38.9 11.76 39.83 12.81Q40.77 13.86 41.26 15.26Q41.75 16.67 41.75 18.3V18.88Q41.75 20.51 41.27 21.92Q40.78 23.33 39.85 24.4Q38.93 25.46 37.62 26.06Q36.31 26.66 34.69 26.66Q33 26.66 31.6 25.95Q30.19 25.25 29.32 23.76Q28.45 22.26 28.35 19.91L29.58 21.77V31.72ZM33.71 23.4Q34.96 23.4 35.9 22.79Q36.84 22.18 37.36 21.09Q37.89 19.99 37.89 18.59Q37.89 17.15 37.36 16.09Q36.84 15.03 35.91 14.43Q34.98 13.83 33.71 13.83Q32.58 13.83 31.62 14.34Q30.65 14.85 30.06 15.81Q29.47 16.78 29.47 18.14V19.26Q29.47 20.56 30.08 21.49Q30.69 22.41 31.67 22.91Q32.64 23.4 33.71 23.4Z", "x": 23.51, "bounds": [25.71, 10.57, 41.75, 31.72]}, {"ch": "e", "d": "M51.37 26.69Q49.42 26.69 47.96 26.02Q46.5 25.35 45.54 24.23Q44.58 23.11 44.09 21.72Q43.6 20.33 43.6 18.87V18.34Q43.6 16.83 44.09 15.44Q44.58 14.04 45.53 12.93Q46.48 11.83 47.91 11.17Q49.34 10.52 51.2 10.52Q53.65 10.52 55.3 11.6Q56.95 12.67 57.79 14.41Q58.63 16.14 58.63 18.14V19.55H45.25V17.18H56.19L55.01 18.34Q55.01 16.89 54.59 15.86Q54.16 14.83 53.32 14.27Q52.48 13.72 51.2 13.72Q49.91 13.72 49.02 14.31Q48.13 14.9 47.67 15.99Q47.22 17.09 47.22 18.62Q47.22 20.05 47.66 21.15Q48.1 22.25 49.02 22.87Q49.93 23.49 51.37 23.49Q52.77 23.49 53.66 22.91Q54.55 22.34 54.8 21.52H58.37Q58.05 23.07 57.09 24.24Q56.14 25.41 54.69 26.05Q53.23 26.69 51.37 26.69Z", "x": 42.41, "bounds": [43.6, 10.52, 58.63, 26.69]}, {"ch": "n", "d": "M61.3 26.16V11.05H64.36V17.53H64.08Q64.08 15.23 64.69 13.69Q65.29 12.15 66.51 11.36Q67.73 10.57 69.52 10.57H69.69Q72.4 10.57 73.79 12.32Q75.19 14.07 75.19 17.52V26.16H71.32V17.17Q71.32 15.78 70.52 14.91Q69.72 14.05 68.33 14.05Q66.91 14.05 66.04 14.94Q65.17 15.82 65.17 17.28V26.16Z", "x": 59.1, "bounds": [61.3, 10.57, 75.19, 26.16]}, {"ch": "G", "d": "M88.53 26.69Q85.91 26.69 83.92 25.82Q81.93 24.95 80.59 23.46Q79.25 21.97 78.57 20.08Q77.89 18.2 77.89 16.18V15.57Q77.89 13.65 78.55 11.81Q79.21 9.98 80.5 8.49Q81.79 7.01 83.7 6.14Q85.61 5.26 88.08 5.26Q90.75 5.26 92.82 6.25Q94.89 7.25 96.14 9.02Q97.39 10.78 97.6 13.11H93.62Q93.44 11.8 92.66 10.87Q91.88 9.93 90.7 9.43Q89.52 8.93 88.08 8.93Q86.66 8.93 85.52 9.43Q84.37 9.93 83.56 10.86Q82.75 11.78 82.32 13.06Q81.89 14.34 81.89 15.9Q81.89 17.59 82.36 18.92Q82.82 20.26 83.69 21.19Q84.55 22.12 85.78 22.61Q87.01 23.1 88.53 23.1Q90.57 23.1 92.1 22.25Q93.62 21.41 94.33 20.05L93.63 23.23V18H97.28V22.51Q95.96 24.5 93.71 25.59Q91.46 26.69 88.53 26.69ZM87.89 18.78V15.83H98.98V18.78Z", "x": 76.58, "bounds": [77.89, 5.26, 98.98, 26.69]}, {"ch": "a", "d": "M110.51 26.16V21.68H109.87V16.7Q109.87 15.4 109.23 14.75Q108.59 14.11 107.27 14.11Q106.57 14.11 105.6 14.14Q104.63 14.16 103.64 14.21Q102.65 14.25 101.87 14.3V11.02Q102.51 10.96 103.31 10.91Q104.11 10.87 104.97 10.85Q105.82 10.82 106.57 10.82Q108.9 10.82 110.45 11.44Q112 12.06 112.79 13.37Q113.57 14.68 113.57 16.79V26.16ZM105.64 26.55Q104 26.55 102.77 25.97Q101.54 25.39 100.86 24.3Q100.17 23.21 100.17 21.68Q100.17 20.02 100.99 18.96Q101.8 17.89 103.29 17.37Q104.78 16.85 106.78 16.85H110.29V19.16H106.73Q105.41 19.16 104.7 19.81Q103.99 20.45 103.99 21.5Q103.99 22.53 104.7 23.16Q105.41 23.79 106.73 23.79Q107.53 23.79 108.22 23.5Q108.91 23.21 109.36 22.5Q109.82 21.8 109.87 20.56L110.82 21.65Q110.68 23.23 110.05 24.32Q109.43 25.41 108.33 25.98Q107.23 26.55 105.64 26.55Z", "x": 99.17, "bounds": [100.17, 10.82, 113.57, 26.55]}, {"ch": "t", "d": "M123.73 26.35Q121.64 26.35 120.29 25.81Q118.94 25.27 118.28 23.98Q117.62 22.69 117.62 20.49L117.63 6.79H121.25L121.24 20.73Q121.24 21.84 121.84 22.44Q122.43 23.04 123.55 23.04H125.91V26.35ZM115.23 13.89V11.05H125.91V13.89Z", "x": 114.98, "bounds": [115.23, 6.79, 125.91, 26.35]}, {"ch": "e", "d": "M135.29 26.69Q133.34 26.69 131.88 26.02Q130.42 25.35 129.46 24.23Q128.5 23.11 128.01 21.72Q127.53 20.33 127.53 18.87V18.34Q127.53 16.83 128.01 15.44Q128.5 14.04 129.45 12.93Q130.41 11.83 131.83 11.17Q133.26 10.52 135.12 10.52Q137.57 10.52 139.22 11.6Q140.87 12.67 141.71 14.41Q142.55 16.14 142.55 18.14V19.55H129.17V17.18H140.12L138.94 18.34Q138.94 16.89 138.51 15.86Q138.09 14.83 137.25 14.27Q136.4 13.72 135.12 13.72Q133.83 13.72 132.94 14.31Q132.05 14.9 131.6 15.99Q131.14 17.09 131.14 18.62Q131.14 20.05 131.58 21.15Q132.02 22.25 132.94 22.87Q133.86 23.49 135.29 23.49Q136.7 23.49 137.59 22.91Q138.48 22.34 138.73 21.52H142.29Q141.97 23.07 141.02 24.24Q140.06 25.41 138.61 26.05Q137.15 26.69 135.29 26.69Z", "x": 126.33, "bounds": [127.53, 10.52, 142.55, 26.69]}, {"ch": "w", "d": "M150.26 26.16 153.13 11.55H157.58L160.89 26.16H157.83L154.46 11.63H156.08L153.09 26.16ZM149.11 26.16 149.09 22.98H152.17L152.18 26.16ZM147.56 26.16 143.44 11.05H147.2L151.11 26.16ZM158.79 26.16 158.8 22.98H161.88L161.86 26.16ZM160.03 26.16 163.49 11.05H167.01L163.35 26.16Z", "x": 142.89, "bounds": [143.44, 11.05, 167.01, 26.16]}, {"ch": "a", "d": "M178.43 26.16V21.68H177.79V16.7Q177.79 15.4 177.15 14.75Q176.51 14.11 175.19 14.11Q174.5 14.11 173.52 14.14Q172.55 14.16 171.56 14.21Q170.57 14.25 169.79 14.3V11.02Q170.43 10.96 171.23 10.91Q172.03 10.87 172.89 10.85Q173.75 10.82 174.5 10.82Q176.82 10.82 178.37 11.44Q179.92 12.06 180.71 13.37Q181.5 14.68 181.5 16.79V26.16ZM173.57 26.55Q171.92 26.55 170.69 25.97Q169.46 25.39 168.78 24.3Q168.1 23.21 168.1 21.68Q168.1 20.02 168.91 18.96Q169.73 17.89 171.21 17.37Q172.7 16.85 174.71 16.85H178.21V19.16H174.65Q173.33 19.16 172.62 19.81Q171.91 20.45 171.91 21.5Q171.91 22.53 172.62 23.16Q173.33 23.79 174.65 23.79Q175.46 23.79 176.15 23.5Q176.83 23.21 177.29 22.5Q177.74 21.8 177.79 20.56L178.74 21.65Q178.6 23.23 177.98 24.32Q177.35 25.41 176.25 25.98Q175.15 26.55 173.57 26.55Z", "x": 167.1, "bounds": [168.1, 10.82, 181.5, 26.55]}, {"ch": "y", "d": "M184.46 32.11V28.8H187.3Q188.09 28.8 188.64 28.6Q189.19 28.4 189.54 27.93Q189.9 27.46 190.11 26.67L194.17 11.05H197.87L193.38 27.51Q192.93 29.19 192.13 30.21Q191.33 31.22 190.07 31.67Q188.8 32.11 186.94 32.11ZM189.16 25.82V22.76H192.39V25.82ZM187.85 25.82 182.96 11.05H186.85L191.5 25.82Z", "x": 182.71, "bounds": [182.96, 11.05, 197.87, 32.11]}]}, "parkinsans": {"label": "Parkinsans SemiBold", "fs": 27.83, "baseline": 25.71, "advance": 200.85, "cap": 19.42, "minx": 0.98, "maxx": 199.22, "miny": 6.08, "maxy": 33.24, "letters": [{"ch": "O", "d": "M10.93 25.91Q8.88 25.91 7.07 25.18Q5.26 24.45 3.89 23.12Q2.52 21.79 1.75 19.97Q0.98 18.16 0.98 15.98Q0.98 13.81 1.75 12Q2.52 10.19 3.89 8.87Q5.26 7.54 7.07 6.81Q8.88 6.08 10.93 6.08Q12.99 6.08 14.8 6.81Q16.61 7.54 17.97 8.87Q19.32 10.19 20.09 12Q20.86 13.81 20.86 15.98Q20.86 18.16 20.09 19.97Q19.32 21.79 17.97 23.12Q16.61 24.45 14.8 25.18Q12.99 25.91 10.93 25.91ZM10.92 22.38Q12.68 22.38 14.01 21.59Q15.33 20.8 16.09 19.36Q16.84 17.92 16.84 15.98Q16.84 14.03 16.09 12.6Q15.33 11.17 14.01 10.39Q12.68 9.61 10.92 9.61Q9.17 9.61 7.84 10.39Q6.5 11.17 5.76 12.6Q5.01 14.02 5.01 15.98Q5.01 17.92 5.76 19.36Q6.5 20.8 7.84 21.59Q9.17 22.38 10.92 22.38Z", "x": 0.0, "bounds": [0.98, 6.08, 20.86, 25.91]}, {"ch": "p", "d": "M32.32 25.91Q30.51 25.91 29.12 24.96Q27.74 24 27.15 22.33V33.05H23.2V10.29H27.15V13.66Q27.74 11.98 29.12 11.04Q30.51 10.09 32.32 10.09Q34.34 10.09 35.9 11.11Q37.47 12.12 38.36 13.91Q39.25 15.69 39.25 17.99Q39.25 20.29 38.36 22.08Q37.47 23.87 35.9 24.89Q34.34 25.91 32.32 25.91ZM31.19 22.47Q32.26 22.47 33.18 21.95Q34.1 21.44 34.66 20.43Q35.23 19.42 35.23 18Q35.23 16.55 34.66 15.56Q34.1 14.56 33.18 14.04Q32.26 13.51 31.19 13.51Q30.14 13.51 29.22 14.04Q28.29 14.56 27.72 15.56Q27.15 16.55 27.15 18Q27.15 19.42 27.72 20.43Q28.29 21.44 29.22 21.96Q30.15 22.47 31.19 22.47Z", "x": 21.29, "bounds": [23.2, 10.09, 39.25, 33.05]}, {"ch": "e", "d": "M49.15 25.91Q47.37 25.91 45.84 25.35Q44.32 24.79 43.19 23.76Q42.06 22.72 41.44 21.3Q40.81 19.87 40.81 18.11Q40.81 16.31 41.46 14.84Q42.11 13.37 43.24 12.3Q44.37 11.23 45.85 10.66Q47.33 10.09 48.97 10.09Q50.37 10.09 51.59 10.47Q52.81 10.84 53.75 11.53Q54.68 12.22 55.2 13.16Q55.72 14.1 55.72 15.21Q55.72 16.68 54.87 17.65Q54.03 18.62 52.66 19.19Q51.28 19.75 49.65 19.96Q48.01 20.17 46.41 20.12Q46.17 20.1 45.83 20.09Q45.5 20.08 45.23 20.07Q44.96 20.05 44.88 20.05Q45.06 20.82 45.68 21.45Q46.29 22.08 47.22 22.44Q48.15 22.8 49.19 22.8Q50.11 22.8 50.98 22.48Q51.85 22.17 52.58 21.63Q53.31 21.1 53.8 20.44L56.49 22.47Q55.61 23.64 54.43 24.41Q53.26 25.17 51.92 25.54Q50.58 25.91 49.15 25.91ZM46.56 17.31Q47.46 17.31 48.39 17.22Q49.33 17.13 50.12 16.9Q50.91 16.66 51.4 16.23Q51.9 15.81 51.9 15.14Q51.9 14.58 51.52 14.14Q51.15 13.71 50.52 13.45Q49.88 13.2 49.08 13.2Q47.8 13.2 46.81 13.78Q45.82 14.37 45.27 15.3Q44.72 16.22 44.66 17.23Q44.99 17.26 45.49 17.28Q46 17.31 46.56 17.31Z", "x": 39.9, "bounds": [40.81, 10.09, 56.49, 25.91]}, {"ch": "n", "d": "M58.48 25.71V10.29H62.41V13.54Q62.77 12.51 63.53 11.74Q64.29 10.96 65.29 10.53Q66.29 10.09 67.34 10.09Q69.1 10.09 70.42 10.88Q71.74 11.66 72.46 13.12Q73.19 14.58 73.19 16.63V25.71H69.28V17.37Q69.28 15.54 68.34 14.48Q67.4 13.43 65.86 13.43Q64.84 13.43 64.06 13.98Q63.29 14.53 62.85 15.48Q62.41 16.43 62.41 17.65V25.71Z", "x": 56.57, "bounds": [58.48, 10.09, 73.19, 25.71]}, {"ch": "G", "d": "M90.12 13.02Q89.85 12.22 89.39 11.59Q88.93 10.96 88.31 10.52Q87.68 10.08 86.91 9.85Q86.15 9.61 85.24 9.61Q83.52 9.61 82.21 10.41Q80.9 11.2 80.16 12.63Q79.42 14.06 79.42 15.97Q79.42 17.94 80.15 19.39Q80.89 20.84 82.26 21.64Q83.64 22.45 85.51 22.45Q86.6 22.45 87.52 22.04Q88.44 21.63 89.14 20.95Q89.84 20.28 90.28 19.45Q90.72 18.62 90.88 17.82H84.53V14.71H94.84V25.71H91.39V20.78L91.65 20.65Q91.61 21.11 91.33 21.78Q91.05 22.45 90.52 23.17Q90 23.88 89.2 24.5Q88.4 25.12 87.34 25.51Q86.28 25.91 84.94 25.91Q83.01 25.91 81.29 25.18Q79.56 24.46 78.24 23.13Q76.91 21.81 76.15 19.98Q75.39 18.16 75.39 15.97Q75.39 13.78 76.15 11.97Q76.9 10.15 78.26 8.83Q79.61 7.5 81.41 6.79Q83.21 6.08 85.29 6.08Q88.4 6.08 90.71 7.5Q93.01 8.92 93.99 11.62Z", "x": 74.41, "bounds": [75.39, 6.08, 94.84, 25.91]}, {"ch": "a", "d": "M103.68 25.91Q101.66 25.91 100.09 24.89Q98.52 23.87 97.63 22.08Q96.74 20.29 96.74 17.99Q96.74 15.69 97.63 13.91Q98.52 12.12 100.09 11.11Q101.66 10.09 103.68 10.09Q105.52 10.09 106.88 11.04Q108.25 11.98 108.85 13.66V10.29H112.8V25.71H110.92Q109.98 25.71 109.41 25.14Q108.85 24.58 108.85 23.63V22.33Q108.25 24 106.88 24.96Q105.52 25.91 103.68 25.91ZM104.8 22.47Q105.87 22.47 106.79 21.96Q107.71 21.44 108.28 20.43Q108.85 19.42 108.85 18Q108.85 16.55 108.28 15.56Q107.71 14.56 106.79 14.04Q105.86 13.51 104.8 13.51Q103.75 13.51 102.83 14.04Q101.9 14.56 101.33 15.56Q100.77 16.55 100.77 18Q100.77 19.42 101.33 20.43Q101.9 21.44 102.83 21.95Q103.76 22.47 104.8 22.47Z", "x": 95.83, "bounds": [96.74, 10.09, 112.8, 25.91]}, {"ch": "t", "d": "M121.93 25.91Q119.77 25.91 118.51 24.61Q117.24 23.31 117.24 20.93V13.49H114.83V10.29H124.61V13.49H121.2V21.06Q121.2 21.79 121.62 22.15Q122.04 22.5 122.57 22.5Q122.92 22.5 123.2 22.4Q123.49 22.29 123.76 22.07L125.16 24.77Q124.44 25.34 123.66 25.62Q122.89 25.91 121.93 25.91ZM116.01 11.51V10.29Q116.61 10.29 116.96 9.92Q117.31 9.56 117.31 8.96V6.48H121.2V8.33Q121.2 8.73 121 9.15Q120.8 9.56 120.43 9.87Q120.06 10.18 119.57 10.29Z", "x": 114.14, "bounds": [114.83, 6.48, 125.16, 25.91]}, {"ch": "e", "d": "M134.44 25.91Q132.66 25.91 131.13 25.35Q129.6 24.79 128.48 23.76Q127.35 22.72 126.72 21.3Q126.1 19.87 126.1 18.11Q126.1 16.31 126.75 14.84Q127.4 13.37 128.53 12.3Q129.66 11.23 131.14 10.66Q132.62 10.09 134.26 10.09Q135.66 10.09 136.88 10.47Q138.1 10.84 139.03 11.53Q139.97 12.22 140.48 13.16Q141 14.1 141 15.21Q141 16.68 140.16 17.65Q139.32 18.62 137.94 19.19Q136.57 19.75 134.93 19.96Q133.3 20.17 131.7 20.12Q131.46 20.1 131.12 20.09Q130.78 20.08 130.51 20.07Q130.24 20.05 130.17 20.05Q130.35 20.82 130.96 21.45Q131.58 22.08 132.51 22.44Q133.44 22.8 134.47 22.8Q135.39 22.8 136.26 22.48Q137.14 22.17 137.86 21.63Q138.59 21.1 139.08 20.44L141.77 22.47Q140.89 23.64 139.72 24.41Q138.55 25.17 137.21 25.54Q135.87 25.91 134.44 25.91ZM131.85 17.31Q132.75 17.31 133.68 17.22Q134.61 17.13 135.41 16.9Q136.2 16.66 136.69 16.23Q137.18 15.81 137.18 15.14Q137.18 14.58 136.81 14.14Q136.44 13.71 135.81 13.45Q135.17 13.2 134.36 13.2Q133.08 13.2 132.1 13.78Q131.11 14.37 130.56 15.3Q130 16.22 129.95 17.23Q130.27 17.26 130.78 17.28Q131.28 17.31 131.85 17.31Z", "x": 125.19, "bounds": [126.1, 10.09, 141.77, 25.91]}, {"ch": "w", "d": "M142.06 10.29H146.02L149.29 24.45H148.13L151.68 10.29H155.84L159.32 24.45H158.16L161.4 10.29H165.18L160.69 25.71H156.42L153.04 12.67H154.26L150.83 25.71H146.57Z", "x": 141.86, "bounds": [142.06, 10.29, 165.18, 25.71]}, {"ch": "a", "d": "M172.66 25.91Q170.64 25.91 169.07 24.89Q167.5 23.87 166.61 22.08Q165.72 20.29 165.72 17.99Q165.72 15.69 166.61 13.91Q167.5 12.12 169.07 11.11Q170.64 10.09 172.66 10.09Q174.5 10.09 175.87 11.04Q177.23 11.98 177.83 13.66V10.29H181.78V25.71H179.9Q178.96 25.71 178.39 25.14Q177.83 24.58 177.83 23.63V22.33Q177.23 24 175.87 24.96Q174.5 25.91 172.66 25.91ZM173.79 22.47Q174.85 22.47 175.77 21.96Q176.69 21.44 177.26 20.43Q177.83 19.42 177.83 18Q177.83 16.55 177.26 15.56Q176.69 14.56 175.77 14.04Q174.85 13.51 173.78 13.51Q172.74 13.51 171.81 14.04Q170.88 14.56 170.31 15.56Q169.75 16.55 169.75 18Q169.75 19.42 170.31 20.43Q170.88 21.44 171.81 21.95Q172.74 22.47 173.79 22.47Z", "x": 164.81, "bounds": [165.72, 10.09, 181.78, 25.91]}, {"ch": "y", "d": "M195.29 25.56V10.29H199.22V25.56Q199.22 27.16 198.73 28.54Q198.24 29.93 197.26 30.99Q196.29 32.05 194.82 32.64Q193.36 33.24 191.39 33.24Q189.54 33.24 188.04 32.64Q186.55 32.04 185.48 30.86Q184.41 29.68 183.87 27.97L187.35 26.77Q187.87 28.16 188.83 29.01Q189.79 29.87 191.27 29.87Q193.14 29.87 194.22 28.73Q195.29 27.59 195.29 25.56ZM195.91 18.7Q195.86 20.85 195.36 22.21Q194.86 23.58 194.07 24.31Q193.28 25.04 192.38 25.32Q191.47 25.6 190.6 25.6Q188.81 25.6 187.44 24.82Q186.07 24.04 185.32 22.56Q184.56 21.08 184.56 18.98V10.29H188.45V18.33Q188.45 20.19 189.38 21.21Q190.32 22.23 191.86 22.23Q192.92 22.23 193.69 21.69Q194.47 21.15 194.88 20.21Q195.29 19.26 195.29 18.05Z", "x": 183.12, "bounds": [183.87, 10.29, 199.22, 33.24]}]}, "martian": {"label": "Martian Mono SemiBold", "fs": 27.83, "baseline": 27.13, "advance": 178.09, "cap": 22.26, "minx": 1.06, "maxx": 176.84, "miny": 4.37, "maxy": 33.19, "letters": [{"ch": "O", "d": "M8.35 27.63Q4.64 27.63 2.85 24.78Q1.06 21.93 1.06 16Q1.06 10.06 2.85 7.22Q4.64 4.37 8.35 4.37Q12.07 4.37 13.85 7.22Q15.64 10.06 15.64 16Q15.64 21.93 13.85 24.78Q12.07 27.63 8.35 27.63ZM8.35 23.84Q9.98 23.84 10.72 21.98Q11.47 20.12 11.47 16Q11.47 11.88 10.72 10.02Q9.98 8.16 8.35 8.16Q6.71 8.16 5.97 10.02Q5.23 11.88 5.23 16Q5.23 20.12 5.97 21.98Q6.71 23.84 8.35 23.84Z", "x": 0.0, "bounds": [1.06, 4.37, 15.64, 27.63]}, {"ch": "p", "d": "M18.24 32.7V10.16H22.25V11.58H23.22Q23.67 10.68 24.45 10.2Q25.24 9.72 26.25 9.72Q27.95 9.72 29.13 10.76Q30.32 11.81 30.93 13.79Q31.54 15.78 31.54 18.65Q31.54 21.5 30.93 23.49Q30.32 25.49 29.13 26.53Q27.95 27.58 26.25 27.58Q25.24 27.58 24.45 27.09Q23.66 26.6 23.22 25.72H22.25V32.7ZM24.88 24.03Q26.29 24.03 26.99 22.67Q27.7 21.31 27.7 18.65Q27.7 15.97 26.99 14.61Q26.29 13.26 24.88 13.26Q23.47 13.26 22.76 14.62Q22.05 15.97 22.05 18.65Q22.05 21.31 22.76 22.67Q23.47 24.03 24.88 24.03Z", "x": 16.14, "bounds": [18.24, 9.72, 31.54, 32.7]}, {"ch": "e", "d": "M37.19 16.92H44.84L43.4 18.36Q43.4 15.66 42.78 14.39Q42.16 13.12 40.85 13.12Q39.44 13.12 38.76 14.48Q38.08 15.84 38.08 18.54Q38.08 21.44 38.76 22.8Q39.44 24.17 40.96 24.17Q41.88 24.17 42.55 23.62Q43.23 23.06 43.75 21.88H47.28Q46.88 23.77 46.02 25.05Q45.16 26.34 43.89 26.98Q42.61 27.63 40.91 27.63Q38.71 27.63 37.23 26.61Q35.75 25.59 35.01 23.56Q34.27 21.54 34.27 18.54Q34.27 15.69 35.04 13.71Q35.81 11.74 37.28 10.7Q38.75 9.67 40.85 9.67Q42.88 9.67 44.22 10.64Q45.57 11.62 46.26 13.56Q46.95 15.49 46.95 18.36Q46.95 18.83 46.93 19.19Q46.9 19.55 46.86 19.92H37.19Z", "x": 32.28, "bounds": [34.27, 9.67, 47.28, 27.63]}, {"ch": "n", "d": "M50.66 27.13V10.16H54.67V11.55H55.64Q56.07 10.63 56.89 10.15Q57.71 9.67 58.82 9.67Q60.17 9.67 61.12 10.34Q62.08 11.01 62.59 12.29Q63.1 13.57 63.1 15.37V27.13H59.09V16.71Q59.09 15.07 58.55 14.27Q58.01 13.48 56.88 13.48Q55.74 13.48 55.2 14.27Q54.67 15.07 54.67 16.71V27.13Z", "x": 48.42, "bounds": [50.66, 9.67, 63.1, 27.13]}, {"ch": "G", "d": "M71.87 27.52Q69.04 27.52 67.49 24.63Q65.95 21.73 65.95 16Q65.95 10.06 67.74 7.22Q69.54 4.37 73.28 4.37Q75.35 4.37 76.8 5.25Q78.25 6.13 79.06 7.85Q79.87 9.57 80 12.1H76.29Q75.93 10.03 75.22 9.09Q74.51 8.16 73.28 8.16Q71.61 8.16 70.85 10.02Q70.09 11.88 70.09 16Q70.09 20.29 70.88 22.23Q71.67 24.17 73.4 24.17Q74.25 24.17 74.9 23.53Q75.55 22.9 75.94 21.8Q76.32 20.7 76.32 19.3H73.17V15.62H79.66V27.13H76.32V25.6H75.32Q74.95 26.49 74.04 27.01Q73.13 27.52 71.87 27.52Z", "x": 64.56, "bounds": [65.95, 4.37, 80.0, 27.52]}, {"ch": "a", "d": "M91.42 16.56V19.64L88.63 20.13Q87.7 20.28 87.21 20.84Q86.72 21.4 86.72 22.33Q86.72 23.18 87.22 23.68Q87.71 24.17 88.57 24.17Q89.25 24.17 89.83 23.76Q90.41 23.35 90.78 22.66Q91.14 21.98 91.14 21.15V15.72Q91.14 14.54 90.53 13.83Q89.91 13.12 88.88 13.12Q88.11 13.12 87.51 13.46Q86.91 13.79 86.5 14.44Q86.08 15.08 85.88 16H82.13Q82.24 14.03 83.1 12.61Q83.96 11.19 85.45 10.43Q86.94 9.67 88.91 9.67Q91.87 9.67 93.44 11.19Q95.01 12.72 95.01 15.61V27.13H91.14V25.72H90.17Q89.81 26.6 89.09 27.06Q88.37 27.52 87.21 27.52Q85.92 27.52 84.91 26.92Q83.91 26.31 83.32 25.25Q82.74 24.2 82.74 22.85Q82.74 20.53 84.01 19.1Q85.29 17.68 87.71 17.23Z", "x": 80.7, "bounds": [82.13, 9.67, 95.01, 27.52]}, {"ch": "t", "d": "M105.25 5.77V10.16H111.54V13.71H105.25V21.71Q105.25 22.86 105.58 23.47Q105.91 24.09 106.52 24.09Q107.27 24.09 107.71 23.29Q108.16 22.5 108.42 20.75H111.9Q111.81 24.15 110.4 25.89Q109 27.63 106.35 27.63Q103.88 27.63 102.56 26.14Q101.25 24.64 101.25 21.85V13.71H98.6V10.16H101.25V5.77Z", "x": 96.83, "bounds": [98.6, 5.77, 111.9, 27.63]}, {"ch": "e", "d": "M117.89 16.92H125.54L124.1 18.36Q124.1 15.66 123.48 14.39Q122.86 13.12 121.55 13.12Q120.13 13.12 119.46 14.48Q118.78 15.84 118.78 18.54Q118.78 21.44 119.46 22.8Q120.13 24.17 121.66 24.17Q122.58 24.17 123.25 23.62Q123.92 23.06 124.45 21.88H127.98Q127.57 23.77 126.71 25.05Q125.85 26.34 124.58 26.98Q123.31 27.63 121.6 27.63Q119.41 27.63 117.93 26.61Q116.45 25.59 115.71 23.56Q114.96 21.54 114.96 18.54Q114.96 15.69 115.73 13.71Q116.5 11.74 117.98 10.7Q119.45 9.67 121.55 9.67Q123.57 9.67 124.92 10.64Q126.27 11.62 126.95 13.56Q127.64 15.49 127.64 18.36Q127.64 18.83 127.62 19.19Q127.6 19.55 127.56 19.92H117.89Z", "x": 112.97, "bounds": [114.96, 9.67, 127.98, 27.63]}, {"ch": "w", "d": "M131.69 27.13 129.67 10.16H133.12L133.86 17.13L134.33 23.93H134.74L135.57 10.16H139.69L140.59 23.93H141.01L141.43 17.13L142.16 10.16H145.25L143.23 27.13H138.63L137.71 13.17H137.3L136.46 27.13Z", "x": 129.11, "bounds": [129.67, 10.16, 145.25, 27.13]}, {"ch": "a", "d": "M155.98 16.56V19.64L153.18 20.13Q152.26 20.28 151.77 20.84Q151.28 21.4 151.28 22.33Q151.28 23.18 151.78 23.68Q152.27 24.17 153.12 24.17Q153.81 24.17 154.39 23.76Q154.97 23.35 155.33 22.66Q155.7 21.98 155.7 21.15V15.72Q155.7 14.54 155.08 13.83Q154.47 13.12 153.43 13.12Q152.67 13.12 152.06 13.46Q151.46 13.79 151.05 14.44Q150.64 15.08 150.43 16H146.69Q146.8 14.03 147.66 12.61Q148.52 11.19 150.01 10.43Q151.49 9.67 153.46 9.67Q156.43 9.67 158 11.19Q159.57 12.72 159.57 15.61V27.13H155.7V25.72H154.73Q154.36 26.6 153.65 27.06Q152.93 27.52 151.77 27.52Q150.48 27.52 149.47 26.92Q148.46 26.31 147.88 25.25Q147.3 24.2 147.3 22.85Q147.3 20.53 148.57 19.1Q149.84 17.68 152.26 17.23Z", "x": 145.25, "bounds": [146.69, 9.67, 159.57, 27.52]}, {"ch": "y", "d": "M162.67 10.16H166.65L169.5 22.5H171.41L170.7 25.74H166.51ZM176.84 10.16 172.53 29.12Q172.05 31.09 170.85 32.14Q169.66 33.19 167.88 33.19Q165.7 33.19 164.49 31.93Q163.28 30.66 163.08 28.14H166.49Q166.75 28.98 167.07 29.36Q167.39 29.73 167.89 29.73Q168.35 29.73 168.64 29.39Q168.92 29.05 169.1 28.3L173.16 10.16Z", "x": 161.39, "bounds": [162.67, 10.16, 176.84, 33.19]}]}, "dela": {"label": "Dela Gothic One", "fs": 27.83, "baseline": 26.1, "advance": 225.45, "cap": 20.2, "minx": 1.11, "maxx": 225.45, "miny": 5.31, "maxy": 33.39, "letters": [{"ch": "O", "d": "M1.11 15.83Q1.11 10.88 4.1 8.1Q7.1 5.31 13.5 5.31Q19.92 5.31 22.91 8.1Q25.91 10.88 25.91 15.83Q25.91 20.87 22.93 23.62Q19.95 26.38 13.5 26.38Q7.12 26.38 4.12 23.61Q1.11 20.84 1.11 15.83ZM19.31 15.83Q19.31 13.38 17.95 12.19Q16.58 10.99 13.5 10.99Q10.43 10.99 9.07 12.19Q7.71 13.38 7.71 15.83Q7.71 18.31 9.07 19.51Q10.43 20.7 13.5 20.7Q16.58 20.7 17.95 19.52Q19.31 18.34 19.31 15.83Z", "x": 0.0, "bounds": [1.11, 5.31, 25.91, 26.38]}, {"ch": "p", "d": "M46.05 18.34Q46.05 20.42 45.25 22.27Q44.44 24.13 42.98 25.25Q41.52 26.38 39.65 26.38Q37.48 26.38 35.94 25.47Q34.39 24.57 33.64 22.68Q33.64 24.26 33.75 27.77Q33.86 31.17 33.86 32.56H27.58Q27.74 27.38 27.74 21.73Q27.74 16.08 27.58 10.91H32.92L33.73 14.39Q34.5 12.44 36.06 11.53Q37.62 10.63 39.74 10.63Q41.54 10.63 42.99 11.66Q44.44 12.69 45.25 14.46Q46.05 16.22 46.05 18.34ZM39.71 18.5Q39.71 17.09 38.93 16.19Q38.15 15.3 36.81 15.3Q35.28 15.3 34.5 16.04Q33.73 16.78 33.73 18.5Q33.73 19.48 34.23 20.13Q34.73 20.79 35.42 21.08Q36.12 21.37 36.76 21.37Q38.01 21.37 38.86 20.58Q39.71 19.78 39.71 18.5Z", "x": 26.46, "bounds": [27.58, 10.63, 46.05, 32.56]}, {"ch": "e", "d": "M62.16 19.14H53.09Q53.31 20.37 54.19 21.13Q55.07 21.9 56.68 21.9Q57.85 21.9 58.88 21.44Q59.91 20.98 60.52 20.15Q62.39 21.06 65.47 22.12Q64.61 24.07 62.34 25.22Q60.08 26.38 56.43 26.38Q51.7 26.38 49.43 24.19Q47.17 22.01 47.17 18.45Q47.17 14.97 49.42 12.8Q51.67 10.63 56.43 10.63Q59.27 10.63 61.36 11.58Q63.44 12.52 64.56 14.21Q65.67 15.89 65.67 18.06Q65.67 18.78 65.61 19.14ZM53.29 16.81H59.85Q59.52 15.92 58.77 15.37Q58.02 14.83 56.82 14.83Q55.43 14.83 54.54 15.36Q53.65 15.89 53.29 16.81Z", "x": 46.33, "bounds": [47.17, 10.63, 65.67, 26.38]}, {"ch": "n", "d": "M77.91 26.1Q78.08 22.01 78.08 18.56Q78.08 17.34 77.48 16.61Q76.88 15.89 75.97 15.89Q75.05 15.89 74.41 16.71Q73.77 17.53 73.66 18.95Q73.6 21.06 73.6 21.95Q73.6 23.85 73.71 26.1H67.06Q67.28 22.09 67.28 18.53Q67.28 14.97 67.06 10.91Q68.9 10.99 69.87 10.99Q70.9 10.99 72.68 10.91L73.49 15Q74.13 12.77 75.49 11.7Q76.86 10.63 78.66 10.63Q80.33 10.63 81.56 11.37Q82.78 12.1 83.41 13.36Q84.03 14.61 84.03 16.17V20.17Q84.03 23.87 84.17 26.1Z", "x": 65.95, "bounds": [67.06, 10.63, 84.17, 26.1]}, {"ch": "G", "d": "M97.31 14.58H109.19V26.1H104.29L103.71 21.18Q102.87 23.82 100.73 25.1Q98.59 26.38 95.5 26.38Q92.69 26.38 90.5 25.13Q88.32 23.87 87.08 21.54Q85.84 19.2 85.84 16.08Q85.84 11.1 88.82 8.21Q91.8 5.31 98.09 5.31Q101.68 5.31 104.42 6.48Q107.16 7.65 109.08 10.1Q107.83 10.74 104.18 12.66L103.49 13.05Q102.4 11.97 100.95 11.42Q99.51 10.88 97.89 10.88Q95.22 10.88 93.91 12.29Q92.61 13.69 92.61 15.75Q92.61 17.98 94.01 19.34Q95.42 20.7 98.31 20.7Q99.56 20.7 100.74 20.34Q101.93 19.98 102.73 19.09H97.31Z", "x": 84.73, "bounds": [85.84, 5.31, 109.19, 26.38]}, {"ch": "a", "d": "M128.7 21.48Q128.7 22.43 128.92 22.82Q129.14 23.21 129.81 23.37L129.7 25.99Q128.81 26.13 128.15 26.18Q127.5 26.24 126.41 26.24Q124.02 26.24 123.24 25.17Q122.46 24.1 122.46 22.43V21.73Q121.66 24.01 120.11 25.13Q118.57 26.24 116.17 26.24Q113.39 26.24 111.99 25.13Q110.58 24.01 110.58 21.82Q110.58 20.01 111.78 18.98Q112.97 17.95 115.48 17.56Q113.36 16.06 111.25 14.94Q112.92 12.83 115.06 11.73Q117.2 10.63 120.26 10.63Q124.55 10.63 126.62 12.48Q128.7 14.33 128.7 18.17ZM116.4 17.42Q118.07 17.22 120.57 17.22Q121.6 17.22 122.02 16.97Q122.43 16.72 122.43 16.33Q122.43 16 122.02 15.76Q121.6 15.53 120.85 15.53Q119.43 15.53 118.34 16Q117.26 16.47 116.4 17.42ZM122.46 19.53V18.81Q121.79 19.14 120.99 19.33Q120.18 19.51 119.04 19.67L118.04 19.84Q116.48 20.15 116.48 20.9Q116.48 21.7 117.93 21.7Q119.49 21.7 120.74 21.11Q121.99 20.51 122.46 19.53Z", "x": 109.75, "bounds": [110.58, 10.63, 129.81, 26.24]}, {"ch": "t", "d": "M143.53 15.89Q140.97 15.72 138.85 15.64L138.82 18.59Q138.82 20.09 139.48 20.94Q140.13 21.79 141.8 21.79Q142.44 21.79 143.03 21.68Q142.91 22.71 142.91 23.76Q142.91 24.26 142.97 26.05Q141.75 26.27 140.91 26.32Q140.08 26.38 138.74 26.38Q135.49 26.38 134.14 24.81Q132.79 23.23 132.79 20.42L132.81 15.72Q131.51 15.78 130.09 15.89V10.96Q131.53 10.96 132.54 10.05Q133.54 9.13 133.73 7.85H141.3Q139.88 9.79 138.16 10.96H143.53Z", "x": 129.81, "bounds": [130.09, 7.85, 143.53, 26.38]}, {"ch": "e", "d": "M159.36 19.14H150.29Q150.51 20.37 151.39 21.13Q152.26 21.9 153.88 21.9Q155.05 21.9 156.08 21.44Q157.11 20.98 157.72 20.15Q159.58 21.06 162.67 22.12Q161.81 24.07 159.54 25.22Q157.27 26.38 153.63 26.38Q148.9 26.38 146.63 24.19Q144.36 22.01 144.36 18.45Q144.36 14.97 146.62 12.8Q148.87 10.63 153.63 10.63Q156.47 10.63 158.55 11.58Q160.64 12.52 161.75 14.21Q162.87 15.89 162.87 18.06Q162.87 18.78 162.81 19.14ZM150.48 16.81H157.05Q156.72 15.92 155.97 15.37Q155.21 14.83 154.02 14.83Q152.63 14.83 151.74 15.36Q150.85 15.89 150.48 16.81Z", "x": 143.53, "bounds": [144.36, 10.63, 162.87, 26.38]}, {"ch": "w", "d": "M178.59 21.82 180.98 10.91H186.52L183.43 26.1H176L174.25 19.01L172.27 26.1H165.12L162.03 10.91H167.57L169.93 21.79L171.55 10.91H177.03Z", "x": 162.03, "bounds": [162.03, 10.91, 186.52, 26.1]}, {"ch": "a", "d": "M204.91 21.48Q204.91 22.43 205.13 22.82Q205.36 23.21 206.02 23.37L205.91 25.99Q205.02 26.13 204.37 26.18Q203.71 26.24 202.63 26.24Q200.24 26.24 199.46 25.17Q198.68 24.1 198.68 22.43V21.73Q197.87 24.01 196.33 25.13Q194.78 26.24 192.39 26.24Q189.61 26.24 188.2 25.13Q186.8 24.01 186.8 21.82Q186.8 20.01 187.99 18.98Q189.19 17.95 191.69 17.56Q189.58 16.06 187.46 14.94Q189.13 12.83 191.28 11.73Q193.42 10.63 196.48 10.63Q200.77 10.63 202.84 12.48Q204.91 14.33 204.91 18.17ZM192.61 17.42Q194.28 17.22 196.79 17.22Q197.82 17.22 198.23 16.97Q198.65 16.72 198.65 16.33Q198.65 16 198.23 15.76Q197.82 15.53 197.06 15.53Q195.65 15.53 194.56 16Q193.47 16.47 192.61 17.42ZM198.68 19.53V18.81Q198.01 19.14 197.2 19.33Q196.4 19.51 195.26 19.67L194.25 19.84Q192.7 20.15 192.7 20.9Q192.7 21.7 194.14 21.7Q195.7 21.7 196.95 21.11Q198.21 20.51 198.68 19.53Z", "x": 185.96, "bounds": [186.8, 10.63, 206.02, 26.24]}, {"ch": "y", "d": "M225.45 10.91 224.81 12.52Q222.16 19.56 219.27 26.1Q217.77 29.44 214.93 31.46Q212.09 33.47 207.81 33.39Q207.28 31.08 206.93 29.79Q206.58 28.49 206.02 27.19Q207.55 27.55 208.89 27.55Q211.06 27.55 212.34 26.63Q211.34 23.65 209.98 20.23Q208.61 16.81 206.02 10.91H212.62Q212.95 12.3 213.54 14.14Q214.12 15.97 214.71 17.67Q214.87 18.2 215.25 19.27Q215.62 20.34 215.85 21.06L216.57 18.87Q218.66 12.83 219.05 10.91Z", "x": 206.02, "bounds": [206.02, 10.91, 225.45, 33.39]}]}} \ No newline at end of file diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..d308ebf --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,102 @@ +# Configuration + +Everything is environment variables or `.env`. The README shows the quick paths; this page is the full reference. + +## Environment variables + +| Variable | Default | Description | +|---|---|---| +| `ROOT_KEY` | `sk-root-change-me` | Admin key with full access. **Replace before deploying.** | +| `OPENAI_API_KEY` | _(unset)_ | Upstream key for `gpt-*` and `openai/*`. | +| `ANTHROPIC_API_KEY` | _(unset)_ | Upstream key for `claude-*` and `anthropic/*`. | +| `ROUTES_JSON` | _(unset)_ | JSON array of `RouteRule` objects, see [Routing table](#routing-table). | +| `DATABASE_URL` | `postgresql://...` | Tenants, keys, audit logs. | +| `REDIS_URL` | `redis://...` | Rate limits and short-lived caches. | +| `HOST` | `0.0.0.0` | Bind address. | +| `PORT` | `8080` | Bind port. | +| `WORKERS` | `1` | uvicorn workers (Python only). | +| `DEBUG` | `false` | Reload on file changes. | +| `LOG_LEVEL` | `INFO` | `DEBUG` / `INFO` / `WARNING` / `ERROR`. | +| `REQUIRE_AUTH` | `true` | Reject requests without a valid `Authorization` header. | + +API keys follow [`sk-og-{token}`](../adr/001-api-key-format.md), the same prefix shape as OpenAI, branded to OpenGateway. + +## Routing table (`ROUTES_JSON`) + +Pin a model to a primary provider with optional fallback, plus pricing for the cost cap: + +```bash +ROUTES_JSON='[ + { + "model": "gpt-4o-mini", + "primary": "openai", + "input_price_per_1k": 0.00015, + "output_price_per_1k": 0.0006, + "max_output_tokens": 16384 + }, + { + "model": "gpt-4o", + "primary": "openai", + "fallback": "anthropic", + "input_price_per_1k": 0.0025, + "output_price_per_1k": 0.01, + "max_output_tokens": 4096 + } +]' +``` + +- `model`: exact match against the request body. +- `primary` / `fallback`: short provider name (`openai`, `anthropic`) or full module path (`opengateway.providers.openai`). +- Pricing fields are USD per 1k tokens. They drive the per-key `max_cost_usd` cap (below); if unset, the cap is not enforced. +- `max_output_tokens`: ceiling used when the request omits `max_tokens`. Caps the worst-case completion cost. + +When `ROUTES_JSON` is unset, the bridge falls through to the legacy prefix-based routing (`gpt-*` to OpenAI, `claude-*` to Anthropic, `bedrock/*` to Bedrock). Drift between the Python bridge and the Mojo router is guarded by `tests/test_mojo_bridge.py::test_routing_rules_match_mojo_router`. + +## Per-key USD cost cap + +Set `max_cost_usd` on a virtual key (column added to the `virtual_keys` table). The bridge estimates the upstream cost of each request before calling the provider: + +``` +estimate = prompt_chars / 4 × input_price + max_tokens × output_price +``` + +When `budget_used + estimate > max_cost_usd`, the request is rejected with `429 rate_limit_error` before any upstream call. Over-estimation is the right bias for a cap. The cap is independent from the token-denominated `max_budget` field (ADR-003 §166), so set both, set either, or set neither. + +## Provider fallback + +When the primary provider returns a retryable failure (`5xx`, `429`, connection / timeout), the bridge retries the request against `fallback` once. Non-retryable failures (4xx other than 429) surface immediately: the request is broken, not the provider. + +Streaming falls back only before the first frame is queued. Mid-stream errors log a warning and emit the canonical `[DONE]` terminator; the client sees a clean stream end, not a silent provider swap mid-flight. + +## TLS termination (in-binary) + +Set `TLS_CERT_FILE` and `TLS_KEY_FILE` (PEM-encoded) to terminate TLS inside the Mojo binary via flare's reactor-side TLS state machine, no sidecar and no edge LB. When unset, the listener accepts cleartext on the loopback address so an LB can terminate TLS in front. The `TLS_PORT` env var is unused; the listener binds on `PORT`. + +```bash +$ TLS_CERT_FILE=./server.pem TLS_KEY_FILE=./server.key ./og-tls3 +opengateway (mojo): HTTPS on 127.0.0.1:8080 with 1 workers + +$ curl --cacert ./server.pem --resolve localhost:8080:127.0.0.1 \ + https://localhost:8080/health +{"status":"ok"} +``` + +> **Self-signed cert gotcha.** The in-binary TLS path requires the cert subject to match the hostname curl uses. Generate the cert with `CN=localhost` (not `CN=127.0.0.1`) and use `--resolve localhost:8080:127.0.0.1` so the cert verifies. Hitting `https://127.0.0.1:8080/health` directly will exit code 60 silently under `curl -s` and look like a hang. + +## Demo without an API key (mock upstream) + +No OpenAI key? The repo ships a mock SSE upstream so the full path (gateway to provider to streaming client) runs offline: + +```bash +$ python tests/mock_upstream.py 9100 & +mock upstream on 127.0.0.1:9100 +``` + +Then set these in `.env` instead of a real key: + +```bash +OPENAI_API_KEY=sk-mock-local +OPENAI_BASE_URL=http://127.0.0.1:9100/v1 +``` + +Restart the gateway and hit `/v1/chat/completions` as in the README. The mock replies `"Hello, streaming world!"`, or four SSE frames when `"stream": true`. Works with both servers.