{"id":50594248,"url":"https://github.com/conssslab/play","last_synced_at":"2026-06-05T13:00:46.757Z","repository":{"id":361917224,"uuid":"1256289567","full_name":"ConsssLab/play","owner":"ConsssLab","description":"ConSSS Wars: Echoes of Chainoa 鏈州英雄傳：鏈之迴響 — playable web build host (play.conssswars.com; Cloudflare Pages shell + GitHub Releases for wasm/pck)","archived":false,"fork":false,"pushed_at":"2026-06-03T08:52:08.000Z","size":134,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-03T09:26:31.019Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://play.conssswars.com","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ConsssLab.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-01T16:29:05.000Z","updated_at":"2026-06-03T07:58:56.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ConsssLab/play","commit_stats":null,"previous_names":["conssslabs/play","conssslab/play"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/ConsssLab/play","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ConsssLab%2Fplay","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ConsssLab%2Fplay/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ConsssLab%2Fplay/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ConsssLab%2Fplay/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ConsssLab","download_url":"https://codeload.github.com/ConsssLab/play/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ConsssLab%2Fplay/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33942436,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-05T02:00:06.157Z","response_time":120,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-05T13:00:27.794Z","updated_at":"2026-06-05T13:00:46.751Z","avatar_url":"https://github.com/ConsssLab.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003eConSSS Wars: Echoes of Chainoa\u003c/h1\u003e\n\u003ch3 align=\"center\"\u003e鏈州英雄傳：鏈之迴響\u003c/h3\u003e\n\n\u003cp align=\"center\"\u003e\n  A hand-drawn, turn-based tactics RPG set on the chain-continent of \u003cb\u003eChainoa\u003c/b\u003e —\n  where the battles you win become on-chain \u003cb\u003eChronicles\u003c/b\u003e and the sagas you write are\n  preserved on \u003cb\u003eWalrus\u003c/b\u003e.\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://play.conssswars.com\"\u003e\u003cb\u003e▶ Play now\u003c/b\u003e\u003c/a\u003e ·\n  \u003ca href=\"https://conssswars.com\"\u003eWebsite\u003c/a\u003e ·\n  \u003ca href=\"https://consss.wal.app\"\u003eLimited event\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg alt=\"engine\" src=\"https://img.shields.io/badge/engine-Godot%204.6-478cbf\"\u003e\n  \u003cimg alt=\"chain\" src=\"https://img.shields.io/badge/chain-Sui%20mainnet-6fbcf0\"\u003e\n  \u003cimg alt=\"storage\" src=\"https://img.shields.io/badge/storage-Walrus-1f6feb\"\u003e\n  \u003cimg alt=\"rpc\" src=\"https://img.shields.io/badge/rpc-Tatum-7c3aed\"\u003e\n  \u003cimg alt=\"host\" src=\"https://img.shields.io/badge/host-Cloudflare%20Pages-f38020\"\u003e\n\u003c/p\u003e\n\n\u003e This repository is the **deployment repo** for the playable web build at\n\u003e [play.conssswars.com](https://play.conssswars.com), **live on Sui mainnet**.\n\u003e The game source (Godot project, Move contracts) lives in sibling repos — see\n\u003e [Related repositories](#related-repositories).\n\n---\n\n## Overview\n\n**ConSSS Wars** is a browser-first, web3-second tactics RPG. The full game plays\nin any modern browser with no install and **no wallet required**. Connect a Sui\nwallet and your results stop being throwaway: clear a battle and you can mint a\n**Chronicle** — a Sui NFT that records *which* battle you cleared, *how well* you\nfought (an on-chain tier), and a personal long-form saga you author, with the\nsaga JSON stored on **Walrus**.\n\nThis repo packages a [Godot 4.6](https://godotengine.org) HTML5 export together\nwith a vanilla-JS Sui + Walrus dApp bridge, and serves both from **Cloudflare\nPages** with a Pages Function handling the heavy lifting (binary streaming, a\nTatum-keyed RPC proxy, and an anti-cheat mint voucher).\n\n## Features\n\n**Gameplay**\n- Turn-based tactical combat with multi-phase boss fights and mid-battle events.\n- Painterly, hand-drawn art direction and an original score.\n- Fully playable in the browser — no install, no wallet required.\n\n**On-chain (optional, opt-in)**\n- **Connect any Sui wallet** — Slush, Sui Wallet, Suiet and other\n  [Wallet Standard](https://github.com/wallet-standard/wallet-standard) wallets.\n  First-party page, no popup blocking.\n- **Tiered Chronicle NFTs** — clearing a battle mints a Chronicle whose rank is\n  computed **on-chain** from a per-battle clear counter and your remaining HP.\n- **Mint integrity** — every mint is gated by an authority-signed ed25519\n  voucher, so a Chronicle cannot be granted by hand-crafting a transaction.\n- **Walrus sagas** — the chronicle JSON (battle log + long-form text) is uploaded\n  to Walrus; only its blob id is pinned on-chain as `metadata_blob_id`.\n\n## Hackathon integration points\n\nThis build integrates two of the track technologies as **core functionality**,\nnot decoration. The table maps each to exactly where it is used in this repo.\n\n| Technology | Role | Where (browser side) | Where (server side) | Endpoint(s) used |\n|------------|------|----------------------|---------------------|------------------|\n| **Walrus** (decentralized storage) | Stores the player's Chronicle metadata JSON off-chain; only the returned blob id is pinned on-chain. | `bridge/src/walrus.js` — `uploadString()` **PUTs** the chronicle JSON to a Walrus **publisher** and returns a `blobId`; `fetchString()` / `blobUrl()` resolve it back from a Walrus **aggregator** (also used for NFT Display `image_url`). Exposed as `window.consss.uploadToWalrus` / `readFromWalrus`. | None — the publisher pays for storage, so no server proxy and no extra wallet tx are needed. | Publisher `PUT {publisherUrl}/v1/blobs?epochs={n}` · Aggregator `GET {aggregatorUrl}/v1/blobs/{blobId}` |\n| **Tatum API** (Sui RPC gateway) | The track's RPC gateway for all Sui JSON-RPC reads (owned-object queries, etc.). | `bridge/src/sui-client.js` — `SuiClient` is pointed at the **same-origin** `/rpc` path (`config.rpcProxyPath`), never at Tatum directly. The Tatum key is never in the browser bundle. | `functions/[[path]].js` (`/rpc` branch) injects the **Tatum** API key as an `x-api-key` header server-side, forwards to the Tatum gateway, and **falls back to the public Sui fullnode** on any error. | `POST /rpc` → `https://sui-mainnet.gateway.tatum.io` (key from `TATUM_API_KEY`) → public fullnode fallback |\n\n**Mainnet Walrus endpoints** (from `bridge/config.public.js`): publisher\n`https://walrus-publisher.rubynodes.io`, aggregator\n`https://aggregator.walrus-mainnet.walrus.space`, `storageEpochs = 5`.\n\n\u003e **Why the community rubynodes publisher?** Mysten does not run a free public\n\u003e **publisher** on Walrus mainnet (writes cost storage), so the bridge uses the\n\u003e community **rubynodes** publisher for uploads. Reads use the standard Walrus\n\u003e mainnet **aggregator**. Both are plain HTTP — the documented Walrus Web API\n\u003e pattern — so no client-side encoding or extra wallet signature is required.\n\n## Architecture\n\nA Godot 4 HTML5 export produces two binaries far larger than Cloudflare Pages'\nper-file limit, so the build is split across hosts and stitched back together at\nthe edge. The browser only ever talks to `play.conssswars.com`.\n\n```\n                         play.conssswars.com  (Cloudflare Pages)\n   browser ──────────────────────────┬─────────────────────────────────\n                                      │\n   public/  (static shell, \u003c 2 MB)    │  functions/[[path]].js  (Pages Function)\n   • index.html                       │  • /index.wasm ┐ proxied + edge-cached\n   • index.js loader + audio worklets │  • /index.pck  ┘ from GitHub Releases\n   • dist/ wallet bridge bundle       │      (same-origin; fixes CORS + Content-Type)\n   • _headers · _redirects            │  • /rpc          → Tatum (server key) → public RPC\n                                      │  • /mint-voucher → ed25519 voucher (anti-cheat)\n                                      │\n            GitHub Releases (ConsssLab/play, releases/latest/download):\n                       index.pck (~385 MB) + index.wasm (~36 MB)\n\n   Walrus (no proxy): browser ── PUT ──▶ rubynodes publisher  (write saga JSON)\n                      browser ── GET ──▶ walrus-mainnet aggregator (read saga JSON)\n```\n\nWhat each piece does:\n\n- **Static shell on Pages** — `index.html`, the Godot loader (`index.js`), the\n  two audio worklets, the icon, and the built wallet bridge bundle.\n- **Engine binaries on GitHub Releases** — `index.pck` (~385 MB) and\n  `index.wasm` (~36 MB) exceed both Cloudflare Pages' 25 MiB/file limit and\n  GitHub's 100 MB git limit, so each deploy uploads them as Release assets.\n- **The Pages Function stitches it together** (`functions/[[path]].js`):\n  - **Binary proxy** — a plain redirect to GitHub fails CORS (release assets send\n    no `Access-Control-Allow-Origin`), so the Function fetches `index.wasm` /\n    `index.pck` from `releases/latest/download/` server-side and streams them back\n    **same-origin** with the correct `Content-Type` (so WASM streaming\n    compilation works). It **edge-caches** them under a version-stamped key that\n    the deploy script bumps per release, so repeat loads are fast and a new\n    release retires the old cached bytes automatically.\n  - **`/rpc`** — a Sui JSON-RPC proxy. The browser's `SuiClient` POSTs here; the\n    Function injects the **Tatum** API key (`x-api-key`) server-side and forwards,\n    falling back to the public Sui fullnode on any failure.\n  - **`/mint-voucher`** — signs an authority ed25519 voucher binding the mint to\n    `(registry, player, battle, hero, hp_pct, nonce, expiry)`. See\n    [Security model](#security-model).\n- **Single-threaded export** — cross-origin isolation is intentionally disabled\n  (`ensureCrossOriginIsolationHeaders: false`, no COOP/COEP), so no\n  SharedArrayBuffer setup is needed and the cross-origin engine load is not\n  blocked. *Keep the Godot export single-threaded or this hosting model breaks.*\n\n### dApp bridge\n\n`bridge/` builds a vanilla-JS (no React, no dapp-kit) bundle that exposes\n`window.consss = { connect, mint, uploadToWalrus, readFromWalrus,\ngetOwnedChronicles }` so the Godot HTML5 shell can drive Sui + Walrus via\n`JavaScriptBridge`. The mint flow is:\n\n1. Upload the chronicle JSON to **Walrus** (publisher HTTP API) → get a blob id.\n2. `POST /mint-voucher` with the clear report → get an authority-signed voucher.\n3. Build and sign `chronicle::chronicle::mint_chronicle` with the player's own\n   wallet, passing the report + voucher + `metadata_blob_id`; the tier is\n   computed on-chain. The server never holds player funds.\n\n`bridge/config.public.js` contains **only public on-chain data** (network,\npackage/registry IDs, Walrus endpoints, `/rpc` path) — no secrets — and is safe\nto commit and ship in the browser bundle.\n\n## Configuration\n\n### Public config (committed)\n\n`bridge/config.public.js` — network, RPC proxy path, on-chain IDs, and Walrus\nendpoints. Public data only; bundled into the browser. See\n[On-chain deployments](#on-chain-deployments).\n\n### Server-side secrets (Cloudflare only)\n\nSecrets live **only** as Cloudflare Pages environment variables / bindings on the\n`consss-play` project and are read solely inside the Pages Function. They are\nnever bundled, never committed, never shipped to the browser.\n\n| Variable | Required | Purpose |\n|----------|----------|---------|\n| `TATUM_API_KEY` | yes | Tatum Sui gateway key injected into `/rpc` (falls back to public RPC if unset/failing). |\n| `AUTHORITY_PRIVKEY_HEX` | yes | ed25519 private key that signs `/mint-voucher` vouchers. |\n| `CHRONICLE_REGISTRY_ID` | yes | Registry id the voucher is bound to (anti cross-deployment replay). |\n| `CHRONICLE_TYPE` | optional | Chronicle struct type (`0x\u003cpkg\u003e::chronicle::Chronicle`) — enables the progression gate. |\n| `MINT_KV` | optional | KV binding for the per-`(wallet, battle)` mint rate limit. |\n| `TATUM_RPC_URL` / `PUBLIC_RPC_URL` | optional | Override the default mainnet Tatum / public fullnode endpoints. |\n\n\u003e Deploying also requires a Cloudflare API token at `~/.cf_token` (Pages → Edit)\n\u003e and the account id via `CLOUDFLARE_ACCOUNT_ID` or `~/.cf_account`. These are\n\u003e local-only and never enter git. The token should be deleted/revoked after use.\n\n## Security model\n\nThis is an honest, scoped model: a fully client-side game can never make the\nclient untrusted by itself, so we are explicit about what the current defenses\n**do** guarantee, what they **don't yet**, and why that trade-off is acceptable\ntoday.\n\n### Defenses we have today\n\n- **No secrets in the browser.** The Tatum API key and the voucher authority\n  ed25519 signing key are Cloudflare **runtime secrets**, read only inside the\n  Pages Function. Local deploys read a Cloudflare API token from a deploy-only\n  `~/.cf_token`. None of these are ever in the committed browser bundle —\n  `config.public.js` holds public IDs/URLs only. Rotating a key is a dashboard\n  change, no rebuild.\n- **Same-origin `/rpc` proxy.** The browser POSTs to `/rpc`; the Function\n  attaches the Tatum key server-side and forwards, falling back to the public\n  fullnode. The key never reaches the browser and there is no CORS surface.\n- **Single mint chokepoint.** `/mint-voucher` is the *only* way to obtain a valid\n  voucher, and the on-chain contract **rejects any mint without one**. So even\n  though anyone can craft a Sui transaction, nobody can mint a Chronicle without\n  going through our server. It signs an ed25519 voucher over `(registry, player,\n  battle, hero, hp_pct, nonce, expiry)`; the contract verifies signature, sender,\n  nonce (anti-replay), expiry, and computes the tier itself. Players sign the\n  mint with their own wallet.\n- **Layer-1 anti-scripting guards** on `/mint-voucher`:\n  - **Origin check** — only requests from `https://play.conssswars.com` are\n    served (filters casual `curl` / foreign-site scripting).\n  - **KV rate limit** — a per-`(wallet, battle)` cooldown via the `MINT_KV`\n    binding, throttling automated re-requests.\n  - **Progression gate** — battle *N* requires already owning the battle *N-1*\n    Chronicle (queried via RPC), so the sequence can't be skipped.\n  - Rate limit and progression gate are **graceful** — they skip cleanly if\n    `MINT_KV` / `CHRONICLE_TYPE` are unset.\n- **Versioned edge caching** of the engine binaries, so a new release retires\n  old cached bytes by key bump (no broad cache-purge token scope needed).\n\n### What we do NOT (yet) do — stated plainly\n\nThe Layer-1 guards plus the voucher chokepoint give us **anti-scripting**: they\nstop casual automation and they stop hand-crafted-PTB mints that bypass the\nserver. They do **not** stop a determined player from driving the real client\nand reporting a *favorable* result, because `/mint-voucher` still **trusts the\nclient-reported `hp_pct` / `battle_id`**. There is no server-authoritative\nsimulation today, so that is the documented ceiling of the current design.\n\n**Why we accept this for now.** Every mint costs **real Sui mainnet gas**, paid\nby the attacker, and the reward is a collectible NFT — there is no token,\nyield, or marketplace value to extract. For a small project, the cost and effort\nof a sophisticated client-tampering exploit is high relative to that reward, so\nhardening past anti-scripting is not yet worth it. We chose to ship an honest\nchokepoint now rather than over-build.\n\n### Planned future anti-cheat (\"no real play, no mint\")\n\nThe next hardening step makes the result **server-verified** instead of\nclient-trusted, in two layers:\n\n1. **Server-authoritative validation** — the backend deterministically\n   **re-simulates / replays** the battle from a server-seeded start using the\n   recorded player inputs, and only issues a voucher if the replayed outcome\n   matches the claimed `hp_pct` / clear.\n2. **zk-proof of correct play** — a zero-knowledge proof that a valid play\n   session produced the claimed result, so the server can verify *without*\n   trusting (or even re-running) the raw client trace.\n\n**Importantly, neither requires a smart-contract change.** The voucher interface\n(`mint_chronicle` + the signed message format) stays exactly as it is today; only\nthe **backend's decision to sign** gets stricter. That keeps this upgrade path\nfully forward-compatible with the deployed mainnet package.\n\n## Local development\n\nPrerequisites: Node 18+ and (for serving Functions locally) `wrangler`.\n\n```bash\n# Build the wallet bridge bundle into public/dist/\ncd bridge\nnpm install\nnpm run build          # or: npm run build:watch\n\n# Serve the static shell + Pages Function locally\ncd ..\nnpx wrangler pages dev public\n```\n\nNotes:\n- The engine binaries (`index.wasm`, `index.pck`) are **not** in this repo; they\n  are served from GitHub Releases in production. To run the full game locally,\n  drop a fresh Godot Web export's `index.wasm` / `index.pck` into `public/`.\n- For the wallet/mint/RPC paths to work locally, provide the server env vars\n  above to `wrangler pages dev` (e.g. via `--binding` / a `.dev.vars` file).\n\n## Deployment\n\nDeploys are **manual by design** — a human gate against shipping a bad build, and\nthe Cloudflare token never lives in GitHub. One command runs the whole pipeline:\n\n```bash\n# 1. In Godot: export the \"Web\" preset (keep thread support OFF) to\n#    ../app/exports/web/  (override the path as an arg if different)\n\n# 2. From this repo root:\nscripts/deploy.sh [EXPORT_DIR]   # default EXPORT_DIR = ../app/exports/web\n```\n\n`scripts/deploy.sh` will:\n1. Copy the fresh export's loader + audio worklets into `public/` (the committed\n   `index.icon.png` is intentionally kept).\n2. Build the wallet bridge (no secrets baked in).\n3. Upload `index.wasm` + `index.pck` to a timestamped GitHub Release.\n4. Stamp the Function's edge-cache version with the release tag (automatic\n   cache-bust), then restore the working tree afterward.\n5. `wrangler pages deploy public` → the `consss-play` project.\n6. Verify `/`, `/index.wasm`, `/index.pck` over HTTPS.\n\n**One-time setup:** create the `consss-play` Pages project, set the server env\nvars from [Configuration](#configuration), and point the custom domain\n`play.conssswars.com` at the project. Remember to `rm -f ~/.cf_token` and revoke\nthe token when finished.\n\n## Repository layout\n\n```\npublic/                      Cloudflare Pages output (static shell)\n├── index.html               game shell (cross-origin isolation off; loads bridge + engine)\n├── index.js                 Godot Web loader\n├── index.audio*.worklet.js  audio worklets\n├── index.icon.png           committed favicon\n├── dist/                    built wallet bridge bundle (gitignored)\n├── _headers · _redirects    edge caching / routing policy\nfunctions/\n└── [[path]].js              Pages Function: binary proxy + /rpc (Tatum) + /mint-voucher\nbridge/                      Sui + Walrus wallet bridge (esbuild → public/dist)\n├── config.public.js         public IDs / URLs only — no secrets\n├── scripts/build.mjs        esbuild driver\n└── src/                     index · bridge · wallet · sui-client · walrus · mint · chronicles\nscripts/\n└── deploy.sh                one-command manual deploy\n```\n\nThe built bridge (`public/dist/`) and the engine binaries (`*.wasm`, `*.pck`)\nare gitignored: the bridge is built at deploy time, the binaries live in Releases.\n\n## On-chain deployments\n\n**Network: Sui mainnet.** IDs below are public on-chain data (mirrored in\n`bridge/config.public.js`).\n\n| Object | ID |\n|--------|----|\n| Chronicle package | `0x5760b2685d41bd45e2991dedc242e866b1aca9ff3c3a5e193445751c2b8dfe4b` |\n| Chronicle registry (shared) | `0x9ff1d9e50e8feca77ccddf5901bd774d3baa4732dac37ae261ca36b2352ced8b` |\n| Finale registry (shared) | `0x2c752d82144701e2b476cd35fd8c5482c9f3aabfe27e155729b657b369493d19` |\n| Walrus (mainnet) | publisher `walrus-publisher.rubynodes.io` · aggregator `aggregator.walrus-mainnet.walrus.space` · 5 epochs |\n\n## Tech stack\n\n| Layer | Tech |\n|-------|------|\n| Game engine | Godot 4.6 → HTML5 (single-threaded, WebGL2) |\n| Smart contracts | Sui Move (mainnet) — Chronicle NFT, tiers, mint voucher |\n| Decentralized storage | Walrus — chronicle saga blobs (HTTP publisher/aggregator) |\n| Wallet | `@mysten/wallet-standard` (vanilla JS, no React) |\n| Sui SDK | `@mysten/sui` · `@mysten/walrus` |\n| Hosting | Cloudflare Pages + Pages Functions + GitHub Releases (engine binaries) |\n| RPC | Tatum Sui gateway (server-side key) with public fullnode fallback |\n| Bundler | esbuild |\n\n## Related repositories\n\n| Site / repo | URL | Host |\n|-------------|-----|------|\n| Game source (Godot + Move) | [`ConsssLab/app`](https://github.com/ConsssLab/app), [`ConsssLab/contracts`](https://github.com/ConsssLab/contracts) | — |\n| `conssswars.com` — official site | [`official-website`](https://github.com/ConsssLab/official-website) | Cloudflare Pages |\n| `consss.wal.app` — limited event | [`official-limit-time-event`](https://github.com/ConsssLab/official-limit-time-event) | Walrus Sites |\n\n## License\n\n© ConsssLab. All rights reserved. Game art, music, and story are proprietary;\nsee the individual source repositories for any code-specific licensing.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fconssslab%2Fplay","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fconssslab%2Fplay","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fconssslab%2Fplay/lists"}