diff --git a/.gitignore b/.gitignore index ddce69b..ec5c120 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ node_modules/ dist/ +dist-testnet/ .astro/ +.cap/ +deploy/.env diff --git a/README.md b/README.md index 6528753..821c8db 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,9 @@ The static architecture is intentional. Most pages are generated from committed - A first-page overview of CellScript and its CKB contract workflow. - A registry browser backed by generated package metadata. +- A Registry submit flow whose compact chooser includes the complete official + CKB wallet directory: detected CCC CKB signers connect in-browser, while + other wallets hand off to the same verifiable external-signature contract. - A playground page with the current web-facing compiler assets. - Learning and documentation entry points. - Design and audit notes under `docs/`. @@ -46,6 +49,39 @@ docs can be embedded: CELLSCRIPT_REPO_ROOT=/path/to/CellScript npm run build ``` +The wallet directory is deliberately separate from runtime signer discovery. +All twelve official CKB wallet entries remain visible; a wallet is labelled as +direct only when CCC exposes a compatible CKB signer. Other entries open the +wallet's official surface and accept a complete `wallet-signature.json` +handoff. The Registry never accepts recovery phrases, and the API applies the +same public-key, canonical-challenge, and recoverable-signature checks to both +paths. Registry wallet authorisation is mainnet-only; the chooser does not +offer a network switch or construct a testnet CCC client. + +Wallet marks under `public/wallets/` are the official SVG assets published by +the Nervos CKB documentation wallet directory. They are committed locally so +the chooser does not depend on third-party favicon services or runtime image +hotlinks; provenance is recorded beside the assets. + +## Deploy To Production + +The checked-in nginx deployment serves the generated site from a read-only +mount and runs with a read-only root filesystem, bounded temporary filesystems, +health checks, log rotation, `no-new-privileges`, and conservative browser +security headers. It expects the external `stack-network` used by the TLS +reverse proxy. + +```bash +cp deploy/.env.example deploy/.env +# Set CELLSCRIPT_SITE_DIST to the absolute generated-site directory. +docker compose --env-file deploy/.env -f deploy/docker-compose.production.yml config +docker compose --env-file deploy/.env -f deploy/docker-compose.production.yml up -d +``` + +TLS terminates at the shared reverse proxy. The origin still emits HSTS and +anti-embedding/security headers so the HTTPS response retains them through the +proxy. + ## Registry Data The site includes generated registry metadata at: @@ -54,7 +90,14 @@ The site includes generated registry metadata at: src/data/registry-packages.json ``` -When this repository is checked out as a submodule inside the main CellScript repository, the generator can scan the parent checkout for package metadata. When this repository is used standalone and no package sources are present, the generator keeps the committed registry data instead of erasing it. +When this repository is checked out as a submodule inside the main CellScript +repository, the generator scans the parent checkout for package metadata. It +fails closed when no `registry.json` source exists, when the source has no git +revision, or when its origin is not a credential-free HTTPS URL; it never +silently retains an older generated file. Build-time CellScript packages are +classified as `profile_library` when `Cell.toml` declares `metadata.profile`, +otherwise as `source_library`. Source links are pinned to the package +repository revision rather than a moving branch. Manual regeneration: diff --git a/deploy/.env.example b/deploy/.env.example new file mode 100644 index 0000000..1f4169c --- /dev/null +++ b/deploy/.env.example @@ -0,0 +1,2 @@ +# Absolute path to the generated Astro dist directory on the deployment host. +# CELLSCRIPT_SITE_DIST=/data/cellscript/site diff --git a/deploy/docker-compose.production.yml b/deploy/docker-compose.production.yml new file mode 100644 index 0000000..592f230 --- /dev/null +++ b/deploy/docker-compose.production.yml @@ -0,0 +1,37 @@ +name: cellscript + +services: + site: + image: nginx:1.27-alpine + container_name: cellscript-site + restart: unless-stopped + environment: + VIRTUAL_HOST: cellscript.dev,www.cellscript.dev + VIRTUAL_PORT: "80" + volumes: + - ${CELLSCRIPT_SITE_DIST:-../dist}:/usr/share/nginx/html:ro + - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro + networks: + - stack-network + read_only: true + tmpfs: + - /var/cache/nginx:size=16m + - /var/run:size=1m + - /tmp:size=4m + security_opt: + - no-new-privileges:true + healthcheck: + test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://127.0.0.1/"] + interval: 15s + timeout: 5s + retries: 5 + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + +networks: + stack-network: + external: true + name: stack-network diff --git a/deploy/docker-compose.testnet.yml b/deploy/docker-compose.testnet.yml new file mode 100644 index 0000000..adc4c17 --- /dev/null +++ b/deploy/docker-compose.testnet.yml @@ -0,0 +1,30 @@ +name: cellscript-testnet-site + +services: + site: + image: nginx:1.27-alpine + container_name: cellscript-testnet-site + restart: unless-stopped + environment: + VIRTUAL_HOST: ${CELLSCRIPT_TESTNET_SITE_HOST:-testnet.registry.cellscript.dev} + VIRTUAL_PORT: "80" + volumes: + - ${CELLSCRIPT_TESTNET_SITE_DIST:?CELLSCRIPT_TESTNET_SITE_DIST is required}:/usr/share/nginx/html:ro + - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro + networks: [stack-network] + read_only: true + tmpfs: + - /var/cache/nginx:size=16m + - /var/run:size=1m + - /tmp:size=4m + security_opt: [no-new-privileges:true] + healthcheck: + test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://127.0.0.1/registry"] + interval: 15s + timeout: 5s + retries: 5 + +networks: + stack-network: + external: true + name: stack-network diff --git a/deploy/nginx.conf b/deploy/nginx.conf new file mode 100644 index 0000000..e26da97 --- /dev/null +++ b/deploy/nginx.conf @@ -0,0 +1,82 @@ +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + server_tokens off; + absolute_redirect off; + + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_comp_level 6; + gzip_types application/wasm; + + add_header Permissions-Policy "camera=(), geolocation=(), microphone=()" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header Strict-Transport-Security "max-age=31536000" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "DENY" always; + add_header X-Permitted-Cross-Domain-Policies "none" always; + + location = /docs/tutorial-09-action-model-and-0-13-syntax { + return 301 https://cellscript.dev/docs/tutorial-09-action-model-and-canonical-syntax/; + } + + location = /docs/tutorial-09-action-model-and-0-13-syntax/ { + return 301 https://cellscript.dev/docs/tutorial-09-action-model-and-canonical-syntax/; + } + + location = /docs/tutorial-13-agentic-loops-and-cellc-mcp { + return 301 https://cellscript.dev/docs/tutorial-13-agentic-loops-and-cellscript-mcp/; + } + + location = /docs/tutorial-13-agentic-loops-and-cellc-mcp/ { + return 301 https://cellscript.dev/docs/tutorial-13-agentic-loops-and-cellscript-mcp/; + } + + location = /registry/interface { + return 301 https://cellscript.dev/registry/LS-IDL; + } + + location = /registry/interface/ { + return 301 https://cellscript.dev/registry/LS-IDL; + } + + location = /registry/LS-IDL { + try_files /registry/LS-IDL/index.html =404; + } + + location = /registry/LS-IDL/ { + return 301 https://cellscript.dev/registry/LS-IDL; + } + + location / { + try_files $uri $uri/ /index.html; + } + + location ~* \.wasm$ { + expires 1y; + add_header Cache-Control "public, max-age=31536000, immutable" always; + add_header Permissions-Policy "camera=(), geolocation=(), microphone=()" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header Strict-Transport-Security "max-age=31536000" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "DENY" always; + add_header X-Permitted-Cross-Domain-Policies "none" always; + try_files $uri =404; + } + + location ~* \.(?:css|js|png|jpg|jpeg|gif|webp|svg|ico|woff2?)$ { + expires 7d; + add_header Cache-Control "public, max-age=604800" always; + add_header Permissions-Policy "camera=(), geolocation=(), microphone=()" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header Strict-Transport-Security "max-age=31536000" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "DENY" always; + add_header X-Permitted-Cross-Domain-Policies "none" always; + try_files $uri =404; + } +} diff --git a/package-lock.json b/package-lock.json index afb63e5..0fb76e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,17 +7,23 @@ "": { "name": "cellscript-website", "version": "0.1.0", + "engines": { + "node": ">=22 <23" + }, "dependencies": { "@astrojs/check": "^0.9.4", "@ckb-ccc/connector": "^1.1.0", + "@phosphor-icons/core": "^2.1.1", "astro": "^5.9.2", "buffer": "^6.0.3", "fflate": "^0.8.3", - "lucide-astro": "^0.556.0", "marked": "^18.0.5", "mermaid": "^11.15.0", "smol-toml": "^1.6.0", "typescript": "^5.8.3" + }, + "engines": { + "node": ">=22 <23" } }, "node_modules/@adraffy/ens-normalize": { @@ -1742,6 +1748,12 @@ "url": "https://github.com/sponsors/Boshen" } }, + "node_modules/@phosphor-icons/core": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/@phosphor-icons/core/-/core-2.1.1.tgz", + "integrity": "sha512-v4ARvrip4qBCImOE5rmPUylOEK4iiED9ZyKjcvzuezqMaiRASCHKcRIuvvxL/twvLpkfnEODCOJp5dM4eZilxQ==", + "license": "MIT" + }, "node_modules/@quansync/fs": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/@quansync/fs/-/fs-1.0.0.tgz", @@ -5781,15 +5793,6 @@ "node": "20 || >=22" } }, - "node_modules/lucide-astro": { - "version": "0.556.0", - "resolved": "https://registry.npmmirror.com/lucide-astro/-/lucide-astro-0.556.0.tgz", - "integrity": "sha512-ugMjPb45AMfkLCaduNSbyy5NQEKvB1TxVVMmUS4S6L807PMESnX0Qp+DIKHjbyjJmPXOyLRbrzvR3YikTK7brg==", - "license": "MIT", - "peerDependencies": { - "astro": ">=2.7.1" - } - }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", diff --git a/package.json b/package.json index fd15daa..961ea48 100644 --- a/package.json +++ b/package.json @@ -3,23 +3,42 @@ "version": "0.1.0", "private": true, "type": "module", + "engines": { + "node": ">=22 <23" + }, "scripts": { "prepare:registry": "node scripts/generate-registry-data.mjs", "data:github": "node scripts/fetch-github-data.mjs", "data:regen": "node scripts/regen-website-data.mjs", "dev": "npm run prepare:registry && astro dev --host 127.0.0.1", - "build": "npm run prepare:registry && astro check && astro build && npm run check:docs && npm run check:dist", + "build": "npm run prepare:registry && npm run build:ci", + "build:ci": "npm run test:registry-guidance && npm run test:registry-browse && npm run test:registry-visual-language && npm run test:session-storage && npm run test:submit-draft && npm run test:playground-focus && npm run test:playground-session && npm run test:playground-presentation && npm run check:visual && astro check && astro build && npm run check:homepage && npm run check:ls-idl && npm run test:site-preferences && PUBLIC_REGISTRY_ENVIRONMENT=testnet-sandbox astro build --outDir dist-testnet && node scripts/test-site-preferences.mjs dist-testnet && node scripts/check-ls-idl-regressions.mjs dist-testnet && node scripts/check-testnet-build.mjs && npm run check:registry-parity && npm run check:docs && npm run check:dist && npm run check:deploy", + "build:testnet": "npm run prepare:registry && npm run test:registry-guidance && npm run test:registry-browse && npm run test:registry-visual-language && npm run test:session-storage && npm run test:submit-draft && npm run test:playground-focus && npm run test:playground-session && npm run test:playground-presentation && npm run check:visual && PUBLIC_REGISTRY_ENVIRONMENT=testnet-sandbox astro check && PUBLIC_REGISTRY_ENVIRONMENT=testnet-sandbox astro build --outDir dist-testnet && node scripts/test-site-preferences.mjs dist-testnet && node scripts/check-ls-idl-regressions.mjs dist-testnet && node scripts/check-testnet-build.mjs", + "test:registry-guidance": "node scripts/test-registry-guidance.mjs", + "test:registry-browse": "node scripts/test-registry-browse-state.mjs", + "test:registry-visual-language": "node scripts/test-registry-visual-language.mjs", + "test:session-storage": "node scripts/test-authorisation-session-storage.mjs", + "test:submit-draft": "node scripts/test-registry-submit-draft.mjs", + "test:playground-focus": "node scripts/test-playground-focus.mjs", + "test:playground-session": "node scripts/test-playground-session.mjs", + "test:playground-presentation": "node scripts/test-playground-presentation.mjs", + "test:site-preferences": "node scripts/test-site-preferences.mjs", + "check:visual": "node scripts/check-visual-contract.mjs", + "check:homepage": "node scripts/check-homepage-regressions.mjs", + "check:ls-idl": "node scripts/check-ls-idl-regressions.mjs", + "check:registry-parity": "node scripts/check-registry-environment-parity.mjs", "check:docs": "node scripts/check-doc-links.mjs", "check:dist": "node scripts/check-dist-regressions.mjs", + "check:deploy": "node scripts/check-production-deploy.mjs", "preview": "astro preview --host 127.0.0.1" }, "dependencies": { "@astrojs/check": "^0.9.4", "@ckb-ccc/connector": "^1.1.0", + "@phosphor-icons/core": "^2.1.1", "astro": "^5.9.2", "buffer": "^6.0.3", "fflate": "^0.8.3", - "lucide-astro": "^0.556.0", "marked": "^18.0.5", "mermaid": "^11.15.0", "smol-toml": "^1.6.0", diff --git a/public/playground-worker.js b/public/playground-worker.js index 832e484..2741c7d 100644 --- a/public/playground-worker.js +++ b/public/playground-worker.js @@ -1,15 +1,41 @@ let wasmModulePromise; let wasmModule; -const COMPILER_ASSET_VERSION = "20260731-v0.22.0-9bb2d765"; +const COMPILER_ASSET_VERSION = "20260811-v0.23.0-fa369818"; const CELLSCRIPT_EDITION = "2026"; +const COMPILER_LOAD_TIMEOUT_MS = 12_000; const loadCompiler = async () => { if (!wasmModulePromise) { - wasmModulePromise = import(`/wasm/cellscript_wasm.js?v=${COMPILER_ASSET_VERSION}`).then(async (mod) => { - await mod.default({ module_or_path: `/wasm/cellscript_wasm_bg.wasm?v=${COMPILER_ASSET_VERSION}` }); - wasmModule = mod; - return mod; - }); + wasmModulePromise = (async () => { + const controller = new AbortController(); + let timedOut = false; + const timeout = setTimeout(() => { + timedOut = true; + controller.abort(); + }, COMPILER_LOAD_TIMEOUT_MS); + + try { + const mod = await import(`/wasm/cellscript_wasm.js?v=${COMPILER_ASSET_VERSION}`); + const response = await fetch(`/wasm/cellscript_wasm_bg.wasm?v=${COMPILER_ASSET_VERSION}`, { + cache: "force-cache", + signal: controller.signal, + }); + if (!response.ok) throw new Error(`compiler download failed with HTTP ${response.status}`); + await mod.default({ module_or_path: response }); + wasmModule = mod; + return mod; + } catch (error) { + wasmModulePromise = undefined; + if (timedOut) { + const timeoutError = new Error("compiler download timed out"); + timeoutError.code = "compiler_load_timeout"; + throw timeoutError; + } + throw error; + } finally { + clearTimeout(timeout); + } + })(); } return wasmModulePromise; }; @@ -61,6 +87,15 @@ self.addEventListener("message", async (event) => { payload, }); } catch (error) { + if (!wasmModule) { + self.postMessage({ + id, + type: "compiler-error", + code: error?.code || "compiler_load_failed", + message: error instanceof Error ? error.message : String(error), + }); + return; + } self.postMessage({ id, type: "result", diff --git a/public/wallets/README.md b/public/wallets/README.md new file mode 100644 index 0000000..b34a944 --- /dev/null +++ b/public/wallets/README.md @@ -0,0 +1,13 @@ +# Wallet Icon Provenance + +These twelve wallet marks are local copies of the SVG assets published by the +official Nervos CKB documentation wallet directory: + +```text +https://docs.nervos.org/docs/integrate-wallets/intro-to-wallets +https://docs.nervos.org/svg/logo-.svg +``` + +Retrieved on 2026-08-01. The filenames preserve the official documentation +asset names, with the `logo-` prefix removed. Keep them local rather than +hotlinking so the production chooser remains deterministic and auditable. diff --git a/public/wallets/ckbull.svg b/public/wallets/ckbull.svg new file mode 100644 index 0000000..49c9d54 --- /dev/null +++ b/public/wallets/ckbull.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/wallets/gate.svg b/public/wallets/gate.svg new file mode 100644 index 0000000..c8da2bd --- /dev/null +++ b/public/wallets/gate.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/wallets/imkey.svg b/public/wallets/imkey.svg new file mode 100644 index 0000000..a8df765 --- /dev/null +++ b/public/wallets/imkey.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/wallets/imtoken.svg b/public/wallets/imtoken.svg new file mode 100644 index 0000000..d0f1a3b --- /dev/null +++ b/public/wallets/imtoken.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/public/wallets/joyid.svg b/public/wallets/joyid.svg new file mode 100644 index 0000000..eed9949 --- /dev/null +++ b/public/wallets/joyid.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/wallets/ledger.svg b/public/wallets/ledger.svg new file mode 100644 index 0000000..eb46c41 --- /dev/null +++ b/public/wallets/ledger.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/wallets/neuron.svg b/public/wallets/neuron.svg new file mode 100644 index 0000000..856d37d --- /dev/null +++ b/public/wallets/neuron.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/wallets/onekey.svg b/public/wallets/onekey.svg new file mode 100644 index 0000000..7095772 --- /dev/null +++ b/public/wallets/onekey.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/public/wallets/quantumpurse.svg b/public/wallets/quantumpurse.svg new file mode 100644 index 0000000..348d972 --- /dev/null +++ b/public/wallets/quantumpurse.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/public/wallets/reiwallet.svg b/public/wallets/reiwallet.svg new file mode 100644 index 0000000..4602d94 --- /dev/null +++ b/public/wallets/reiwallet.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/wallets/safepal.svg b/public/wallets/safepal.svg new file mode 100644 index 0000000..57cd10d --- /dev/null +++ b/public/wallets/safepal.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/wallets/utxoglobal.svg b/public/wallets/utxoglobal.svg new file mode 100644 index 0000000..eeb412c --- /dev/null +++ b/public/wallets/utxoglobal.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/public/wasm/cellscript_wasm_bg.wasm b/public/wasm/cellscript_wasm_bg.wasm index 45478a9..18b34c8 100644 Binary files a/public/wasm/cellscript_wasm_bg.wasm and b/public/wasm/cellscript_wasm_bg.wasm differ diff --git a/scripts/check-dist-regressions.mjs b/scripts/check-dist-regressions.mjs index eac65b9..7901b41 100644 --- a/scripts/check-dist-regressions.mjs +++ b/scripts/check-dist-regressions.mjs @@ -1,6 +1,7 @@ import { existsSync, readFileSync, readdirSync } from "node:fs"; import { basename, resolve } from "node:path"; import { createHash } from "node:crypto"; +import { gzipSync } from "node:zlib"; const failures = []; @@ -31,27 +32,61 @@ const countContains = (value, needle) => value.split(needle).length - 1; const root = resolve("."); const dist = resolve(root, "dist"); const distIndex = resolve(dist, "index.html"); +const dist404 = resolve(dist, "404.html"); const distDocsIndex = resolve(dist, "docs", "index.html"); const distPlaygroundIndex = resolve(dist, "playground", "index.html"); +const distRegistryIndex = resolve(dist, "registry", "index.html"); +const distRegistrySubmitIndex = resolve(dist, "registry", "submit", "index.html"); +const distRegistryApiIndex = resolve(dist, "registry", "api", "index.html"); +const distRegistryInterfaceIndex = resolve(dist, "registry", "LS-IDL", "index.html"); +const distRegistryManageIndex = resolve(dist, "registry", "manage", "index.html"); const distPlaygroundWorker = resolve(dist, "playground-worker.js"); const distWasm = resolve(dist, "wasm", "cellscript_wasm_bg.wasm"); const docsSource = resolve(root, "src", "lib", "docs.ts"); +const registrySubmitSource = resolve(root, "src", "pages", "registry", "submit.astro"); +const registryBrowseSource = resolve(root, "src", "pages", "registry.astro"); +const registryLayoutSource = resolve(root, "src", "layouts", "RegistryLayout.astro"); +const registryPackageDetailSource = resolve(root, "src", "components", "RegistryPackageDetail.astro"); +const siteHeaderSource = resolve(root, "src", "components", "SiteHeader.astro"); const wikiRoot = resolve(root, "..", "docs", "wiki"); -const expectedReleaseTag = "v0.22.0"; -const expectedCompilerAssetVersion = "20260731-v0.22.0-9bb2d765"; -const expectedWasmSha256 = "9bb2d7651b96f0916f0bac98f9f1c71b4a792c682ab08b00cff536a2494d3e31"; +const expectedReleaseTag = "v0.23.0"; +const expectedCompilerAssetVersion = "20260811-v0.23.0-fa369818"; +const expectedWasmSha256 = "fa369818631532c657e73e970b6138e3a231d532a073d428dfe7f61686135dd5"; expectFile(distIndex); +expectFile(dist404); expectFile(distDocsIndex); expectFile(distPlaygroundIndex); +expectFile(distRegistryIndex); +expectFile(distRegistrySubmitIndex); +expectFile(distRegistryApiIndex); +expectFile(distRegistryInterfaceIndex); +expectFile(distRegistryManageIndex); expectFile(distPlaygroundWorker); expectFile(distWasm); expectFile(docsSource); +expectFile(registrySubmitSource); +expectFile(registryBrowseSource); +expectFile(registryLayoutSource); +expectFile(registryPackageDetailSource); +expectFile(siteHeaderSource); const indexHtml = existsSync(distIndex) ? read(distIndex) : ""; +const notFoundHtml = existsSync(dist404) ? read(dist404) : ""; const docsHtml = existsSync(distDocsIndex) ? read(distDocsIndex) : ""; +const playgroundHtml = existsSync(distPlaygroundIndex) ? read(distPlaygroundIndex) : ""; +const registryHtml = existsSync(distRegistryIndex) ? read(distRegistryIndex) : ""; +const registrySubmitHtml = existsSync(distRegistrySubmitIndex) ? read(distRegistrySubmitIndex) : ""; +const registryApiHtml = existsSync(distRegistryApiIndex) ? read(distRegistryApiIndex) : ""; +const registryInterfaceHtml = existsSync(distRegistryInterfaceIndex) ? read(distRegistryInterfaceIndex) : ""; +const registryManageHtml = existsSync(distRegistryManageIndex) ? read(distRegistryManageIndex) : ""; const playgroundWorker = existsSync(distPlaygroundWorker) ? read(distPlaygroundWorker) : ""; const docsSourceText = existsSync(docsSource) ? read(docsSource) : ""; +const registrySubmitSourceText = existsSync(registrySubmitSource) ? read(registrySubmitSource) : ""; +const registryBrowseSourceText = existsSync(registryBrowseSource) ? read(registryBrowseSource) : ""; +const registryLayoutSourceText = existsSync(registryLayoutSource) ? read(registryLayoutSource) : ""; +const registryPackageDetailSourceText = existsSync(registryPackageDetailSource) ? read(registryPackageDetailSource) : ""; +const siteHeaderSourceText = existsSync(siteHeaderSource) ? read(siteHeaderSource) : ""; const cssDir = resolve(dist, "_astro"); const cssText = existsSync(cssDir) @@ -75,11 +110,261 @@ expectContains("home", indexHtml, `href="https://github.com/CellScript-Labs/Cell expectContains("home", indexHtml, `${expectedReleaseTag}`); expectNotContains("home", indexHtml, '
${eyebrow}`); + expectContains(name, html, `>${heading}`); + expectContains(name, html, description); + expectContains(name, html, "data-registry-hero"); + expectContains(name, html, "registry-network-card"); + expectContains(name, html, "Persistent, production-facing records"); + expectNotContains(name, html, 'data-astro-transition-persist="registry-header"'); + expectContains(name, html, 'data-astro-transition-persist="registry-environment"'); + expectContains(name, html, 'data-astro-transition-persist="registry-tabs"'); + expectContains(name, html, 'data-i18n-aria-label="nav.registryBrowse"'); +} + +expectContains("registry interface", registryInterfaceHtml, 'data-registry-title-key="registry.nav.interface"'); +expectContains("registry interface", registryInterfaceHtml, 'data-i18n="registry.nav.interface">LS-IDL'); +expectNotContains("registry interface", registryInterfaceHtml, "registry-tool-back"); +const interfaceTab = registryInterfaceHtml.match(/]*href="\/registry\/LS-IDL"[^>]*>/)?.[0] ?? ""; +if (!interfaceTab.includes('class="active"') || !interfaceTab.includes('aria-current="page"')) { + fail("registry interface: LS-IDL tab must be the only active route affordance"); +} + +expectContains("registry", registryHtml, 'data-registry-title-key="registry.nav.browse"'); +expectContains("registry submit", registrySubmitHtml, 'data-registry-title-key="registry.nav.submit"'); +expectContains("registry api", registryApiHtml, 'data-registry-title-key="registry.nav.api"'); +expectNotContains("registry submit", registrySubmitHtml, 'id="registry-publish-entry-title"'); +expectNotContains("registry api", registryApiHtml, 'id="registry-api-title"'); +expectContains("registry api", registryApiHtml, 'class="registry-api-group-heading"'); +const registryStylesheets = (html) => [...html.matchAll(/ match[1]) + .sort(); +const registryStyleSignature = JSON.stringify(registryStylesheets(registryHtml)); +if (registryStyleSignature !== JSON.stringify(registryStylesheets(registrySubmitHtml))) { + fail("registry submit: stylesheet set differs from Registry"); +} +if (registryStyleSignature !== JSON.stringify(registryStylesheets(registryApiHtml))) { + fail("registry API: stylesheet set differs from Registry"); +} +if (registryStyleSignature !== JSON.stringify(registryStylesheets(registryInterfaceHtml))) { + fail("registry interface: stylesheet set differs from Registry"); +} + +for (const action of ["connect", "sign", "submit", "claim"]) { + const actionButton = registrySubmitHtml.match(new RegExp(`]*data-capability-action="${action}"[^>]*>`))?.[0] ?? ""; + if (!actionButton) fail(`registry submit: missing ${action} capability action`); + else if (!/\shidden(?:\s|=|>)/.test(actionButton)) fail(`registry submit: ${action} action must be progressive`); +} +expectNotContains("registry submit", registrySubmitHtml, "data-capability-primary"); +expectNotContains("registry submit", registrySubmitHtml, "data-capability-network"); +expectNotContains("registry submit", registrySubmitHtml, "data-submit-workspace"); +for (const [name, html] of [ + ["registry submit", registrySubmitHtml], + ["registry manage", registryManageHtml], +]) { + const patterns = [...html.matchAll(/\spattern="([^"]*)"/g)].map((match) => match[1]); + if (patterns.some((pattern) => pattern.includes("[a-z0-9_-]"))) { + fail(`${name}: HTML pattern contains an unescaped Unicode-v hyphen`); + } +} +expectContains("registry submit", registrySubmitHtml, "data-submit-form novalidate"); +expectContains("registry submit", registrySubmitHtml, "data-publish-entry"); +expectContains("registry submit", registrySubmitHtml, "cellc publish --authorise"); +expectContains("registry submit", registrySubmitHtml, "data-open-manual-publish"); +expectContains("registry submit", registrySubmitHtml, "data-manual-publish hidden"); +expectContains("registry submit", registrySubmitHtml, 'data-authorisation-session data-state="loading" hidden'); +expectContains("registry submit", registrySubmitHtml, 'data-session-stage="local" data-state="complete"'); +expectContains("registry submit", registrySubmitHtml, "data-session-retry"); +if (registrySubmitHtml.indexOf("data-publish-entry") > registrySubmitHtml.indexOf("data-submit-form")) { + fail("registry submit: the session-first entry must precede the advanced manual form"); +} +expectContains("registry submit", registrySubmitHtml, 'data-authorisation-mode="new"'); +expectContains("registry submit", registrySubmitHtml, 'data-authorisation-mode="existing"'); +expectContains("registry submit", registrySubmitHtml, "data-submit-create-capability"); +expectContains("registry submit", registrySubmitHtml, "data-existing-capability-check"); +expectContains("registry submit", registrySubmitHtml, "entering a key ID alone never unlocks anything."); +expectContains("registry submit bundle", jsText, "/v1/capabilities/"); +expectContains("registry submit bundle", jsText, "/check?"); +expectContains("registry submit bundle", jsText, "--capability-key-id"); +expectContains("registry submit", registrySubmitHtml, "This cellc session can continue only through CKB connectors detected in this browser."); +expectContains("registry submit source", registrySubmitSourceText, "if (!authorisationSessionMode && directory.length)"); +expectContains("registry submit source", registrySubmitSourceText, "await completeAuthorisationSession();"); +expectContains("registry submit source", registrySubmitSourceText, "? !authorisationSessionMode && newMode && signatureReady"); +expectContains("registry submit source", registrySubmitSourceText, "readAuthorisationSession(window.location.href, sessionStorage)"); +expectContains("registry submit source", registrySubmitSourceText, "clearAuthorisationSession(sessionStorage, authorisationSessionId)"); +expectContains("registry submit source", registrySubmitSourceText, "clearStoredAuthorisationSession();"); +expectContains("registry submit source", registrySubmitSourceText, "const setupSubmitPage = () =>"); +expectContains("registry submit source", registrySubmitSourceText, 'document.addEventListener("astro:page-load", setupSubmitPage)'); +expectContains("registry submit source", registrySubmitSourceText, "registrySubmitDraftFieldNames"); +expectContains("registry submit source", registrySubmitSourceText, "response = await attempt(6_500)"); +expectContains("registry submit source", registrySubmitSourceText, "response = await attempt(4_000)"); +expectContains("registry submit source", registrySubmitSourceText, "clearSignature(true)"); +expectContains("registry submit source", registrySubmitSourceText, "Preserve an unchanged signed request"); +expectContains("registry submit", registrySubmitHtml, "data-workflow-guide"); +expectContains("registry source", registryBrowseSourceText, "/v1/artifacts"); +expectNotContains("registry", registryHtml, "/v1/packages"); +expectContains("registry", registryHtml, 'data-state="loading" data-source="loading"'); +expectContains("registry", registryHtml, 'data-registry-skeleton aria-hidden="true" hidden'); +expectContains("registry", registryHtml, 'data-registry-empty role="status" aria-live="polite" aria-atomic="true"'); +expectContains("registry", registryHtml, "data-registry-empty-submit"); +expectContains("registry", registryHtml, 'href="/registry/LS-IDL"'); +expectContains("registry", registryHtml, ">Browse"); +expectContains("registry", registryHtml, ">LS-IDL"); +expectNotContains("registry", registryHtml, "registry-index-strip"); +expectNotContains("registry", registryHtml, "registry-cell-blueprint"); +expectNotContains("registry", registryHtml, "registry-browse-tools"); +expectContains("registry", registryHtml, "data-registry-clear"); +expectContains("registry", registryHtml, "data-registry-intent"); +expectContains("registry", registryHtml, "data-registry-filter-trigger"); +expectContains("registry", registryHtml, "data-registry-filter-menu"); +expectContains("registry", registryHtml, "Any use state"); +expectContains("registry source", registryBrowseSourceText, "const setViewState"); +expectContains("registry source", registryBrowseSourceText, "__cellscriptRegistryBrowseCache"); +expectContains("registry source", registryBrowseSourceText, "const SKELETON_DELAY_MS = 250"); +expectContains("registry source", registryBrowseSourceText, "const PRIMARY_DEADLINE_MS = 5400"); +expectContains("registry source", registryBrowseSourceText, "const RETRY_DEADLINE_MS = 1900"); +expectContains("registry source", registryBrowseSourceText, "requestGeneration"); +expectContains("registry source", registryBrowseSourceText, "showTerminalFailure"); +expectContains("registry source", registryBrowseSourceText, "registryBrowseStateUrl"); +expectContains("registry source", registryBrowseSourceText, "matchesIntent"); +expectContains("registry source", registryBrowseSourceText, "closeFilterMenus"); +expectContains("registry source", registryBrowseSourceText, '["ArrowDown", "ArrowUp", "Home", "End"]'); +expectContains("registry source", registryBrowseSourceText, 'document.addEventListener("astro:page-load", setupRegistryBrowse)'); +expectContains("registry source", registryBrowseSourceText, 'search.set("availability", "deprecated")'); +expectContains("registry layout", registryLayoutSourceText, 'transition:name="registry-header"'); +expectContains("registry layout", registryLayoutSourceText, 'document.addEventListener("astro:after-swap", syncRegistryShell)'); +expectContains("registry layout", registryLayoutSourceText, 'tabs.dataset.indicatorReady = "true"'); +expectContains("registry package detail", registryPackageDetailSourceText, "const setupRegistryPackageDetail = () =>"); +expectContains("registry package detail", registryPackageDetailSourceText, 'document.addEventListener("astro:page-load", setupRegistryPackageDetail)'); +expectContains("registry package detail", registryPackageDetailSourceText, "renderGuidanceText();\n load();"); +expectContains("registry package detail", registryPackageDetailSourceText, "data-package-guidance"); +expectContains("registry package detail", registryPackageDetailSourceText, "data-package-maintainer-action"); +expectContains("registry package detail", registryPackageDetailSourceText, "openEvidenceJson"); +expectContains("registry manage", registryManageHtml, "data-manage-current-title"); +expectContains("registry manage", registryManageHtml, "data-manage-task-menu"); +expectContains("registry CSS", cssText, "::view-transition-old(registry-route)"); +expectContains("registry CSS", cssText, "::view-transition-new(registry-route)"); +expectContains("registry CSS", cssText, "::view-transition-old(registry-header)"); +expectContains("registry CSS", cssText, "::view-transition-new(registry-header)"); +expectContains("registry CSS", cssText, ".registry-tabs[data-indicator-ready"); +expectContains("registry source", registryBrowseSourceText, '"no-results"'); +expectContains("registry source", registryBrowseSourceText, '"mirror-empty"'); +expectNotContains("registry", registryHtml, "Live production index"); + +expectContains("site header", siteHeaderSourceText, "@phosphor-icons/core/regular"); +expectContains("site header", siteHeaderSourceText, "data-menu-toggle"); +expectContains("site header", siteHeaderSourceText, "data-nav-drawer"); +expectContains("site header", siteHeaderSourceText, 'role="dialog"'); +expectContains("site header", siteHeaderSourceText, "closeDrawer({ restoreFocus: true })"); +expectContains("site header", siteHeaderSourceText, "event.key === \"Escape\""); +expectContains("site header", siteHeaderSourceText, "data-theme-current"); +expectContains("site header", siteHeaderSourceText, 'class="nav-tooltip"'); +expectContains("site header", siteHeaderSourceText, 'class="language-short"'); +expectContains("site header styles", cssText, ".nav-source:hover .nav-tooltip"); +expectContains("site header styles", cssText, ".nav-source:focus-visible .nav-tooltip"); +expectContains("site header styles", cssText, "@media(min-width:841px)and (max-width:960px)"); +expectNotContains("site header", siteHeaderSourceText, "theme-toggle-track"); + +for (const wallet of [ + "Neuron", + "JoyID", + "imToken", + "CKBull", + "SafePal", + "Ledger", + "imKey", + "OneKey", + "UTXO Global", + "Rei Wallet", + "Gate", + "QuantumPurse", +]) { + expectContains("registry wallet bundle", jsText, wallet); +} +expectContains("registry wallet bundle", jsText, "errorExternalSignature"); +expectContains("registry wallet bundle", jsText, "wallet-signature.json"); +expectContains("registry wallet bundle", jsText, "/wallets/"); + +for (const icon of [ + "neuron.svg", + "joyid.svg", + "imtoken.svg", + "ckbull.svg", + "safepal.svg", + "ledger.svg", + "imkey.svg", + "onekey.svg", + "utxoglobal.svg", + "reiwallet.svg", + "gate.svg", + "quantumpurse.svg", +]) { + expectFile(resolve(dist, "wallets", icon)); + expectContains("registry wallet bundle", jsText, `\"${icon.replace(".svg", "")}\"`); +} + +for (const stage of ["wallet", "scope", "authorise"]) { + expectContains("registry submit", registrySubmitHtml, `data-workflow-stage="${stage}"`); +} +for (const retiredStage of ["payload", "signature", "capability", "namespace"]) { + expectNotContains("registry submit", registrySubmitHtml, `data-workflow-stage="${retiredStage}"`); +} +expectContains("registry submit", registrySubmitHtml, 'form="registry-submit-form"'); +expectContains("registry submit", registrySubmitHtml, 'data-publish-step data-state="locked" aria-labelledby="registry-publish-heading" hidden'); expectContains("playground bundle", jsText, expectedCompilerAssetVersion); -expectContains("playground bundle", jsText, 'cellscript_version = "0.22.0"'); +expectContains("playground bundle", jsText, 'cellscript_version = "0.23.0"'); expectNotContains("playground bundle", jsText, 'cellscript_version = "0.20.0-rc.1"'); expectContains("playground worker", playgroundWorker, `const COMPILER_ASSET_VERSION = "${expectedCompilerAssetVersion}"`); +expectContains("playground", playgroundHtml, "data-pg-studio"); +expectContains("playground", playgroundHtml, "data-focus-toggle"); +expectContains("playground", playgroundHtml, "cellscript-playground-focus-mode"); +expectContains("playground focus bundle", jsText, "cellscript-playground-focus-mode"); +expectContains("playground", playgroundHtml, 'data-output-panel="flow"'); +expectContains("playground", playgroundHtml, "data-inspector"); +expectContains("playground", playgroundHtml, "data-output-stale"); +expectContains("playground", playgroundHtml, "data-guide"); +expectContains("playground session bundle", jsText, "cellscript-playground-session-v1"); +expectContains("playground guide bundle", jsText, "cellscript-playground-guide-v1"); +expectContains("playground worker recovery bundle", jsText, "retryCompiler"); +expectContains("playground worker", playgroundWorker, "COMPILER_LOAD_TIMEOUT_MS = 12_000"); +expectContains("playground worker", playgroundWorker, 'type: "compiler-error"'); +expectContains("playground worker", playgroundWorker, 'cache: "force-cache"'); +expectContains("playground worker recovery bundle", jsText, "compiler_load_timeout"); +expectNotContains("playground", playgroundHtml.toLowerCase(), "command palette"); + +const playgroundI18nAssignments = countContains(playgroundHtml, "window.__CELLSCRIPT_I18N__ ="); +if (playgroundI18nAssignments !== 1) { + fail(`playground: expected one i18n payload, found ${playgroundI18nAssignments}`); +} +const playgroundHtmlGzipBytes = gzipSync(playgroundHtml).byteLength; +if (playgroundHtmlGzipBytes > 70_000) { + fail(`playground: compressed HTML ${playgroundHtmlGzipBytes} bytes exceeds the 70000-byte startup budget`); +} if (existsSync(distWasm)) { const wasmSha256 = createHash("sha256").update(readFileSync(distWasm)).digest("hex"); @@ -100,7 +385,14 @@ for (const token of [ ".value-card-copy", ".landing-example-copy", "@media(max-width:840px)", - ".theme-toggle,.language-toggle,.nav-link{width:44px;min-width:44px;padding:0}", + ".nav-drawer-backdrop", + ".nav-menu-toggle", + ".registry-skeleton-row", + ".registry-wallet-dialog-head:has(.registry-wallet-back[hidden])", + ".registry-shell-bar", + ".registry-hero", + ".registry-network-card", + ".registry-tool-route", "text-shadow:none", "text-wrap:normal", ]) { diff --git a/scripts/check-homepage-regressions.mjs b/scripts/check-homepage-regressions.mjs new file mode 100644 index 0000000..3ff32e2 --- /dev/null +++ b/scripts/check-homepage-regressions.mjs @@ -0,0 +1,73 @@ +import { existsSync, readFileSync } from "node:fs"; +import { resolve } from "node:path"; + +const failures = []; + +const fail = (message) => { + failures.push(message); +}; + +const requireMatch = (name, text, pattern) => { + if (!pattern.test(text)) fail(`missing homepage contract: ${name}`); +}; + +const indexPath = resolve("dist", "index.html"); +const cssPath = resolve("src", "styles", "global.css"); +const activityPath = resolve("src", "data", "github-activity.json"); + +if (!existsSync(indexPath)) fail("dist/index.html is missing; run astro build before homepage regression checks"); +if (!existsSync(cssPath)) fail("src/styles/global.css is missing"); +if (!existsSync(activityPath)) fail("src/data/github-activity.json is missing"); + +const indexHtml = existsSync(indexPath) ? readFileSync(indexPath, "utf-8") : ""; +const css = existsSync(cssPath) ? readFileSync(cssPath, "utf-8") : ""; +const activity = existsSync(activityPath) + ? JSON.parse(readFileSync(activityPath, "utf-8")) + : { releases: [] }; +const release = activity.releases?.[0]; + +if (!release?.url) { + fail("latest GitHub release URL is missing from src/data/github-activity.json"); +} else { + const releaseTags = [...indexHtml.matchAll(/]*class="hero-release-tag"[^>]*>/g)].map((match) => match[0]); + const releaseTag = releaseTags.find((tag) => tag.includes(`href="${release.url}"`)); + if (!releaseTag) { + fail(`release badge must link to ${release.url}`); + } else { + if (!releaseTag.includes('target="_blank"')) fail("release badge link must open in a new tab"); + if (!releaseTag.includes('rel="noopener noreferrer"')) fail("release badge link must use noopener noreferrer"); + } +} + +requireMatch( + "value-card caption container stays in foreground", + css, + /\.value-card-copy\s*\{[^}]*z-index:\s*var\(--z-elevated\)/s, +); +requireMatch( + "value-card captions stay in foreground", + css, + /\.value-card h3,\s*\.value-card p\s*\{[^}]*z-index:\s*var\(--z-elevated\)/s, +); +requireMatch( + "value-card index stays in foreground", + css, + /\.value-index\s*\{[^}]*z-index:\s*var\(--z-elevated\)/s, +); +requireMatch( + "playground example caption container stays in foreground", + css, + /\.landing-example-copy\s*\{[^}]*z-index:\s*var\(--z-elevated\)/s, +); +requireMatch( + "playground example captions stay in foreground", + css, + /\.landing-example-card h3,\s*\.landing-example-card p\s*\{[^}]*z-index:\s*var\(--z-elevated\)/s, +); + +if (failures.length) { + console.error(failures.join("\n")); + process.exit(1); +} + +console.log("homepage regression checks ok"); diff --git a/scripts/check-ls-idl-regressions.mjs b/scripts/check-ls-idl-regressions.mjs new file mode 100644 index 0000000..5187434 --- /dev/null +++ b/scripts/check-ls-idl-regressions.mjs @@ -0,0 +1,64 @@ +import { existsSync, readFileSync } from "node:fs"; +import { resolve } from "node:path"; + +const dist = resolve(process.argv[2] || "dist"); +const files = { + browse: resolve(dist, "registry", "index.html"), + interface: resolve(dist, "registry", "LS-IDL", "index.html"), + legacyRedirect: resolve(dist, "registry", "interface", "index.html"), + api: resolve(dist, "registry", "api", "index.html"), + lookup: resolve("src", "components", "RegistryLsIdlLookup.astro"), + detail: resolve("src", "components", "RegistryPackageDetail.astro"), + library: resolve("src", "lib", "registry.ts"), + styles: resolve("src", "styles", "registry.css"), +}; + +for (const [name, file] of Object.entries(files)) { + if (!existsSync(file)) throw new Error(`LS-IDL ${name} contract file is missing: ${file}`); +} + +const browse = readFileSync(files.browse, "utf8"); +const interfacePage = readFileSync(files.interface, "utf8"); +const legacyRedirect = readFileSync(files.legacyRedirect, "utf8"); +const api = readFileSync(files.api, "utf8"); +const lookup = readFileSync(files.lookup, "utf8"); +const detail = readFileSync(files.detail, "utf8"); +const library = readFileSync(files.library, "utf8"); +const styles = readFileSync(files.styles, "utf8"); + +for (const [name, text, tokens] of [ + ["browse", browse, ["data-registry-browser", 'href="/registry/LS-IDL"', 'data-i18n="registry.nav.interface">LS-IDL']], + ["interface", interfacePage, ["data-ls-idl-lookup", "data-ls-idl-data-hash-field hidden", "interfaces/ls-idl", "application/vnd.ckb.ls-idl+json", "x-ls-idl-verification", "schema-and-suffix-bound"]], + ["API", api, ["/v1/ckb/scripts/:code_hash/interfaces/ls-idl", "/idl/:code_hash", "byte-preserving"]], + ["detail", detail, ["data-package-ls-idl", "data-package-ls-idl-download", "lsIdlBoundary"]], + ["library", library, ["cellscript-registry-ls-idl-interface-v1", "code-cell-data-suffix-32", "artifact ls-idl fetch"]], +]) { + for (const token of tokens) { + if (!text.includes(token)) throw new Error(`LS-IDL ${name} contract is missing: ${token}`); + } +} + +if (!legacyRedirect.includes('url=/registry/LS-IDL') || !legacyRedirect.includes('href="https://cellscript.dev/registry/LS-IDL"')) { + throw new Error("The legacy Registry interface route must redirect to /registry/LS-IDL"); +} + +if (browse.includes("data-ls-idl-lookup")) { + throw new Error("LS-IDL lookup must not compete with Registry browsing"); +} +if (browse.includes("registry-browse-tools")) { + throw new Error("The LS-IDL tab must not be repeated as a Browse-page utility row"); +} +if (!/\.registry-tool-route\s*\{[^}]*width:\s*100%/s.test(styles) || /\.registry-tool-route\s*\{[^}]*max-width:/s.test(styles)) { + throw new Error("The LS-IDL route must align to the full-width Browse surface"); +} +if (interfacePage.includes("registry-ls-idl-disclosure") || interfacePage.includes("]*class="[^"]*primary/.test(interfacePage) && !/class="[^"]*primary[^"]*"[^>]*data-ls-idl-submit/.test(interfacePage)) { + throw new Error("The dedicated LS-IDL lookup must expose one clear primary action"); +} +if (!lookup.includes('hashTypeSelect.value === "type"') || !lookup.includes("dataHashField.hidden = !needsDataHash")) { + throw new Error("LS-IDL data hash must be disclosed only for type-hash lookup"); +} + +console.log("LS-IDL website regression checks ok"); diff --git a/scripts/check-production-deploy.mjs b/scripts/check-production-deploy.mjs new file mode 100644 index 0000000..0e86028 --- /dev/null +++ b/scripts/check-production-deploy.mjs @@ -0,0 +1,50 @@ +import { readFile } from "node:fs/promises"; + +const compose = await readFile(new URL("../deploy/docker-compose.production.yml", import.meta.url), "utf8"); +const nginx = await readFile(new URL("../deploy/nginx.conf", import.meta.url), "utf8"); + +function requireText(source, value, label) { + if (!source.includes(value)) { + throw new Error(`production deployment is missing ${label}: ${value}`); + } +} + +for (const [value, label] of [ + [":/usr/share/nginx/html:ro", "read-only site mount"], + [":/etc/nginx/conf.d/default.conf:ro", "read-only nginx config"], + ["read_only: true", "read-only container root"], + ["/var/cache/nginx:size=16m", "bounded cache tmpfs"], + ["/var/run:size=1m", "bounded runtime tmpfs"], + ["/tmp:size=4m", "bounded temporary tmpfs"], + ["no-new-privileges:true", "no-new-privileges"], + ["healthcheck:", "health check"], + ["max-size: \"10m\"", "log size rotation"], + ["max-file: \"3\"", "log file rotation"], +]) { + requireText(compose, value, label); +} + +requireText(nginx, "server_tokens off", "server-token suppression"); +requireText(nginx, "absolute_redirect off", "proxy-safe relative directory redirects"); +requireText(nginx, "gzip_types application/wasm", "WASM response compression"); +requireText(nginx, "location ~* \\.wasm$", "dedicated WASM asset policy"); +requireText(nginx, "public, max-age=31536000, immutable", "versioned WASM immutable caching"); +requireText(nginx, "location = /registry/interface", "legacy LS-IDL redirect"); +requireText(nginx, "return 301 https://cellscript.dev/registry/LS-IDL;", "canonical LS-IDL redirect target"); +requireText(nginx, "location = /registry/LS-IDL", "canonical LS-IDL route"); +requireText(nginx, "try_files /registry/LS-IDL/index.html =404;", "canonical LS-IDL document"); +for (const header of [ + "Permissions-Policy", + "Referrer-Policy", + "Strict-Transport-Security", + "X-Content-Type-Options", + "X-Frame-Options", + "X-Permitted-Cross-Domain-Policies", +]) { + const occurrences = nginx.split(`add_header ${header} `).length - 1; + if (occurrences < 3) { + throw new Error(`production nginx must preserve ${header} on HTML, WASM, and static assets`); + } +} + +console.log("production deployment contract ok"); diff --git a/scripts/check-registry-environment-parity.mjs b/scripts/check-registry-environment-parity.mjs new file mode 100644 index 0000000..ec7a14d --- /dev/null +++ b/scripts/check-registry-environment-parity.mjs @@ -0,0 +1,104 @@ +import { createHash } from "node:crypto"; +import { readFile, readdir } from "node:fs/promises"; +import path from "node:path"; + +const root = path.resolve(import.meta.dirname, ".."); +const productionRoot = path.resolve(root, process.argv[2] || "dist"); +const testnetRoot = path.resolve(root, process.argv[3] || "dist-testnet"); + +const routes = new Map([ + ["registry/index.html", [["data-registry-browser", "data-registry-query", "data-registry-filter-menu", "data-registry-empty"], true]], + ["registry/submit/index.html", [["registry-flow", "data-publish-entry", "data-authorisation-session", "data-submit-form"], true]], + ["registry/LS-IDL/index.html", [["registry-tool-route", "data-ls-idl-lookup", "data-ls-idl-form", "data-ls-idl-result"], true]], + ["registry/api/index.html", [["registry-api", "registry-api-endpoints", "registry-api-example"], true]], + ["registry/manage/index.html", [["registry-maintainer", "data-manage-app", "data-manage-form", "data-manage-command"], false]], + ["registry/package/index.html", [["registry-package-detail", "data-package-detail", "data-package-loading", "data-package-error"], true]], +]); + +const sharedShell = [ + 'id="registry-main"', + "data-registry-hero", + "registry-environment-bar", + "registry-network-card", + 'href="/registry"', +]; +const tabShell = [ + 'href="/registry/submit"', + 'href="/registry/LS-IDL"', + 'href="/registry/api"', + 'data-i18n="registry.nav.browse"', + 'data-i18n="registry.nav.submit"', + 'data-i18n="registry.nav.interface"', + 'data-i18n="registry.nav.api"', +]; + +const requireToken = (html, token, label) => { + if (!html.includes(token)) throw new Error(`${label} is missing shared Registry contract token: ${token}`); +}; + +const assetReferences = (html) => [...new Set( + [...html.matchAll(/(?:src|href)="(\/_astro\/[^"]+)"/g)].map((match) => match[1]), +)].sort(); + +for (const [relativePath, [routeTokens, showsTabs]] of routes) { + const [production, testnet] = await Promise.all([ + readFile(path.join(productionRoot, relativePath), "utf8"), + readFile(path.join(testnetRoot, relativePath), "utf8"), + ]); + requireToken(production, 'data-registry-environment="production"', `${relativePath} production`); + requireToken(production, 'data-registry-network="mainnet"', `${relativePath} production`); + requireToken(production, "Persistent, production-facing records", `${relativePath} production`); + requireToken(testnet, 'data-registry-environment="testnet-sandbox"', `${relativePath} testnet`); + requireToken(testnet, 'data-registry-network="testnet"', `${relativePath} testnet`); + requireToken(testnet, "Ephemeral records · hidden after 72 hours · test CKB only", `${relativePath} testnet`); + for (const token of [...sharedShell, ...(showsTabs ? tabShell : []), ...routeTokens]) { + requireToken(production, token, `${relativePath} production`); + requireToken(testnet, token, `${relativePath} testnet`); + } + const productionAssets = JSON.stringify(assetReferences(production)); + const testnetAssets = JSON.stringify(assetReferences(testnet)); + if (productionAssets !== testnetAssets) { + throw new Error(`${relativePath} loads different visual or interactive assets between production and testnet`); + } +} + +const listAssets = async (directory, prefix = "") => { + const entries = await readdir(directory, { withFileTypes: true }); + const files = []; + for (const entry of entries) { + const relative = path.posix.join(prefix, entry.name); + if (entry.isDirectory()) files.push(...await listAssets(path.join(directory, entry.name), relative)); + else files.push(relative); + } + return files.sort(); +}; + +const productionAssets = await listAssets(path.join(productionRoot, "_astro")); +const testnetAssets = await listAssets(path.join(testnetRoot, "_astro")); +if (JSON.stringify(productionAssets) !== JSON.stringify(testnetAssets)) { + throw new Error("production and testnet generated different Registry asset inventories"); +} + +for (const relativePath of productionAssets) { + const [production, testnet] = await Promise.all([ + readFile(path.join(productionRoot, "_astro", relativePath)), + readFile(path.join(testnetRoot, "_astro", relativePath)), + ]); + const digest = (bytes) => createHash("sha256").update(bytes).digest("hex"); + if (digest(production) !== digest(testnet)) { + throw new Error(`generated asset differs between production and testnet: ${relativePath}`); + } +} + +const [productionLookup, testnetLookup, productionApi, testnetApi] = await Promise.all([ + readFile(path.join(productionRoot, "registry/LS-IDL/index.html"), "utf8"), + readFile(path.join(testnetRoot, "registry/LS-IDL/index.html"), "utf8"), + readFile(path.join(productionRoot, "registry/api/index.html"), "utf8"), + readFile(path.join(testnetRoot, "registry/api/index.html"), "utf8"), +]); +requireToken(productionLookup, '', "production LS-IDL lookup"); +requireToken(testnetLookup, '', "testnet LS-IDL lookup"); +requireToken(productionApi, "interfaces/ls-idl?network=mainnet&hash_type=data1", "production API example"); +requireToken(testnetApi, "interfaces/ls-idl?network=testnet&hash_type=data1", "testnet API example"); + +console.log(`Registry environment parity ok (${routes.size} routes, ${productionAssets.length} byte-identical assets)`); diff --git a/scripts/check-testnet-build.mjs b/scripts/check-testnet-build.mjs new file mode 100644 index 0000000..27fc5f1 --- /dev/null +++ b/scripts/check-testnet-build.mjs @@ -0,0 +1,58 @@ +import { access, readFile } from "node:fs/promises"; +import path from "node:path"; + +const root = path.resolve(import.meta.dirname, ".."); +const dist = path.join(root, "dist-testnet"); +const apiOrigin = process.env.PUBLIC_REGISTRY_API_ORIGIN || "https://api.testnet.registry.cellscript.dev"; +const routes = new Map([ + ["Browse", ["registry/index.html", "data-registry-browser", true]], + ["Publish", ["registry/submit/index.html", "data-publish-entry", true]], + ["LS-IDL", ["registry/LS-IDL/index.html", "data-ls-idl-lookup", true]], + ["API", ["registry/api/index.html", "registry-api-endpoints", true]], + ["Manage", ["registry/manage/index.html", "data-manage-app", false]], + ["Artifact detail", ["registry/package/index.html", "data-package-detail", true]], +]); + +for (const [label, [relativePath, routeMarker, showsTabs]] of routes) { + const html = await readFile(path.join(dist, relativePath), "utf8"); + const requiredTokens = [ + 'data-registry-environment="testnet-sandbox"', + 'data-registry-network="testnet"', + 'name="robots" content="noindex,nofollow,noarchive"', + "data-registry-hero", + "registry-network-card", + "Pudge Testnet Sandbox", + "Ephemeral records · hidden after 72 hours · test CKB only", + apiOrigin, + 'href="/registry"', + routeMarker, + ]; + if (showsTabs) requiredTokens.push('href="/registry/submit"', 'href="/registry/LS-IDL"', 'href="/registry/api"'); + for (const required of requiredTokens) { + if (!html.includes(required)) throw new Error(`${label} testnet build is missing ${required}`); + } +} + +const lookup = await readFile(path.join(dist, "registry/LS-IDL/index.html"), "utf8"); +if (!/
- + + + - + + + - diff --git a/src/pages/registry.astro b/src/pages/registry.astro index 0451f0d..859442b 100644 --- a/src/pages/registry.astro +++ b/src/pages/registry.astro @@ -1,170 +1,778 @@ --- -import "../styles/global.css"; -import "../styles/registry.css"; -import SiteHeader from "../components/SiteHeader.astro"; -import { ClientRouter } from "astro:transitions"; -import { links } from "../data/site"; +import RegistryLayout from "../layouts/RegistryLayout.astro"; +import RegistryFilterMenu from "../components/RegistryFilterMenu.astro"; +import { packageHref, registryDateLabel, registryPackageDetailView, registryPackages, toRegistryPackageView } from "../lib/registry"; import { defaultLocale, translations } from "../i18n/translations"; -import { Check, CircleDot, Mail } from "lucide-astro"; +import searchIcon from "@phosphor-icons/core/regular/magnifying-glass.svg?raw"; +import arrowRightIcon from "@phosphor-icons/core/regular/arrow-right.svg?raw"; +import packageIcon from "@phosphor-icons/core/regular/package.svg?raw"; +import bracketsCurlyIcon from "@phosphor-icons/core/regular/brackets-curly.svg?raw"; +import stackIcon from "@phosphor-icons/core/regular/stack.svg?raw"; +import cpuIcon from "@phosphor-icons/core/regular/cpu.svg?raw"; +import cubeIcon from "@phosphor-icons/core/regular/cube.svg?raw"; +import copyIcon from "@phosphor-icons/core/regular/copy.svg?raw"; +import { isTestnetSandbox, registryRuntime } from "../lib/registry-runtime"; +import { registryEvidenceDepth, registryEvidenceDescriptionKey } from "../lib/registry-visual-language.mjs"; -const locale = defaultLocale; -const t = translations[locale]; -const phases = t.comingSoon.phases; -const phaseIcons = [Check, CircleDot, CircleDot]; -const title = `CellScript Registry — ${t.comingSoon.title}`; +const t = translations[defaultLocale]; +const apiOrigin = registryRuntime.apiOrigin; +const fallbackPackages = (isTestnetSandbox ? [] : registryPackages).map((pkg) => { + const view = toRegistryPackageView(pkg); + const detail = registryPackageDetailView(view, apiOrigin, registryRuntime.network); + return { + ...view, + keywords: pkg.keywords || [], + categories: pkg.categories || [], + href: packageHref(pkg), + source: "bundled", + guidance: detail.guidance, + latest_release_at: detail.firstRelease?.created_at, + }; +}); +const packageTranslations = t.registry.package as unknown as Record; +const pascalKey = (value: string) => value.split("_").map((part) => `${part[0]?.toUpperCase()}${part.slice(1)}`).join(""); +const guidanceTitle = (key: string) => packageTranslations[`guidance${pascalKey(key)}Title`] ?? key; +const kindIcons: Record = { + source_library: bracketsCurlyIcon, + profile_library: stackIcon, + runtime_verifier: cpuIcon, + deployable_contract: cubeIcon, + reproducible_binary: packageIcon, + template: copyIcon, +}; +const evidenceDescription = (pkg: (typeof fallbackPackages)[number]) => { + const key = registryEvidenceDescriptionKey(pkg) as keyof typeof t.registry.browse; + return String(t.registry.browse[key]); +}; +const evidenceAria = (pkg: (typeof fallbackPackages)[number]) => t.registry.browse.evidenceAria + .replaceAll("{label}", evidenceDescription(pkg)) + .replaceAll("{n}", String(registryEvidenceDepth(pkg))); +const fallbackPayload = JSON.stringify(fallbackPackages).replaceAll("<", "\\u003c"); +const kindFilters = [ + { value: "", label: t.registry.browse.allKinds, i18nKey: "registry.browse.allKinds" }, + { value: "source_library", label: t.registry.browse.kindSourceLibrary, i18nKey: "registry.browse.kindSourceLibrary" }, + { value: "profile_library", label: t.registry.browse.kindProfileLibrary, i18nKey: "registry.browse.kindProfileLibrary" }, + { value: "runtime_verifier", label: t.registry.browse.kindRuntimeVerifier, i18nKey: "registry.browse.kindRuntimeVerifier" }, + { value: "deployable_contract", label: t.registry.browse.kindDeployableContract, i18nKey: "registry.browse.kindDeployableContract" }, + { value: "reproducible_binary", label: t.registry.browse.kindReproducibleBinary, i18nKey: "registry.browse.kindReproducibleBinary" }, + { value: "template", label: t.registry.browse.kindTemplate, i18nKey: "registry.browse.kindTemplate" }, +]; +const intentFilters = [ + { value: "", label: t.registry.browse.allIntents, i18nKey: "registry.browse.allIntents" }, + { value: "ready", label: t.registry.browse.intentReady, i18nKey: "registry.browse.intentReady" }, + { value: "chain_verified", label: t.registry.browse.intentChainVerified, i18nKey: "registry.browse.intentChainVerified" }, + { value: "verified", label: t.registry.browse.intentVerified, i18nKey: "registry.browse.intentVerified" }, + { value: "source_only", label: t.registry.browse.intentSourceOnly, i18nKey: "registry.browse.intentSourceOnly" }, + { value: "needs_evidence", label: t.registry.browse.intentNeedsEvidence, i18nKey: "registry.browse.intentNeedsEvidence" }, + { value: "deprecated", label: t.registry.browse.intentDeprecated, i18nKey: "registry.browse.intentDeprecated" }, +]; --- - - - - - - - - - - - - - {title} - - - - - - - -
-
-
-

{t.comingSoon.eyebrow}

-

{t.comingSoon.title}

-

{t.comingSoon.lead}

- -
- {phases.map((phase, index) => { - const PhaseIcon = phaseIcons[index]; - const state = index === 0 ? "done" : index === 1 ? "active" : "pending"; - return ( -
- - {phase.label} - {phase.title} -

{phase.caption}

-
- ); - })} -
+ +
+ - -
+ + + -
-
- - - - + - - + if (fallbackPackages.length) { + localMirror(); + return; + } + packages = []; + nextOffset = null; + if (count) count.textContent = ""; + if (list) list.hidden = true; + if (empty) empty.hidden = true; + if (facets) facets.hidden = true; + if (more) more.hidden = true; + setServiceState("error"); + setViewState("error"); + }; + const load = async ({ append = false, background = false }: { append?: boolean; background?: boolean } = {}) => { + request?.abort(); + const generation = ++requestGeneration; + const requestedQuery = query; + const requestedKind = kind; + const requestedIntent = intent; + const requestedCacheKey = cacheKey(); + const offset = append && nextOffset != null ? nextOffset : 0; + if (!append && !background) { + app.setAttribute("aria-busy", "true"); + setViewState("loading"); + skeleton.hidden = true; + list.hidden = true; + empty.hidden = true; + if (facets) facets.hidden = true; + setServiceState("loading"); + } else if (more) { + more.disabled = true; + more.textContent = message("registry.browse.loadingMore"); + } + const search = new URLSearchParams({ limit: "50", offset: String(offset) }); + if (requestedQuery) search.set("q", requestedQuery); + if (requestedKind) search.set("kind", requestedKind); + if (requestedIntent === "ready" || requestedIntent === "verified") search.set("verification", "verified"); + if (requestedIntent === "chain_verified") search.set("deployment", "chain_verified"); + if (requestedIntent === "needs_evidence") search.set("verification", "evidence_required"); + if (requestedIntent === "deprecated") search.set("availability", "deprecated"); + const skeletonTimer = !append && !background ? window.setTimeout(() => { + if (generation !== requestGeneration) return; + skeleton.hidden = false; + }, SKELETON_DELAY_MS) : 0; + const slowTimer = !append && !background ? window.setTimeout(() => { + if (generation !== requestGeneration) return; + setServiceState("slow"); + }, SLOW_NOTICE_MS) : 0; + const fetchAttempt = async (deadlineMs: number): Promise => { + const controller = new AbortController(); + request = controller; + let timedOut = false; + const deadline = window.setTimeout(() => { + timedOut = true; + controller.abort(); + }, deadlineMs); + try { + const response = await fetch(`${apiOrigin}/v1/artifacts?${search}`, { headers: { accept: "application/json" }, signal: controller.signal }); + if (!response.ok) throw new Error("unavailable"); + return await response.json(); + } catch (cause) { + if (cause instanceof Error && cause.name === "AbortError" && !timedOut) throw cause; + const error = new Error(timedOut ? "timeout" : "unavailable"); + error.cause = cause; + throw error; + } finally { + window.clearTimeout(deadline); + } + }; + try { + let payload: any; + try { + payload = await fetchAttempt(PRIMARY_DEADLINE_MS); + } catch (cause) { + if ((cause instanceof Error && cause.name === "AbortError") || generation !== requestGeneration) throw cause; + if (navigator.onLine === false) throw cause; + setServiceState("retrying"); + await new Promise((resolve) => window.setTimeout(resolve, RETRY_DELAY_MS + Math.floor(Math.random() * 90))); + if (generation !== requestGeneration) return; + payload = await fetchAttempt(RETRY_DEADLINE_MS); + } + if (payload?.schema !== "cellscript-registry-artifact-index" || !Array.isArray(payload.artifacts)) throw new Error("unsupported_schema"); + if (generation !== requestGeneration || requestedQuery !== query || requestedKind !== kind || requestedIntent !== intent) return; + const incoming = payload.artifacts.map((pkg: BrowsePackage) => ({ ...pkg, source: "live" })); + if (append) { + const merged = new Map(packages.map((pkg) => [pkg.coordinate, pkg])); + for (const pkg of incoming) merged.set(pkg.coordinate, pkg); + packages = [...merged.values()]; + } else { + packages = incoming; + } + nextOffset = Number.isInteger(payload.next_offset) ? payload.next_offset : null; + setServiceState("live"); + renderRows(); + cache.set(requestedCacheKey, { + packages: packages.map((pkg) => ({ ...pkg })), + nextOffset, + updatedAt: Date.now(), + }); + } catch (cause) { + if (cause instanceof Error && cause.name === "AbortError") return; + if (generation === requestGeneration) showTerminalFailure({ background: background || append }); + } finally { + window.clearTimeout(skeletonTimer); + window.clearTimeout(slowTimer); + if (more) { + more.disabled = false; + more.textContent = message("registry.browse.loadMore"); + } + } + }; - - + queryInput?.addEventListener("input", () => { + query = queryInput.value.trim(); + updateUrl(); + request?.abort(); + window.clearTimeout(searchTimer); + searchTimer = window.setTimeout(() => source === "mirror" ? localMirror() : load(), 240); + }, { signal }); + kindInput?.addEventListener("change", () => { + kind = kindInput.value; + updateUrl(); + source === "mirror" ? localMirror() : load(); + }, { signal }); + intentInput?.addEventListener("change", () => { + intent = intentInput.value; + updateUrl(); + source === "mirror" ? localMirror() : load(); + }, { signal }); + more?.addEventListener("click", () => load({ append: true }), { signal }); + retry?.addEventListener("click", () => load({ background: packages.length > 0 }), { signal }); + clearFilters?.addEventListener("click", () => { + query = ""; + kind = ""; + intent = ""; + if (queryInput) queryInput.value = ""; + if (kindInput) kindInput.value = ""; + if (intentInput) intentInput.value = ""; + syncFilterMenus(); + updateUrl(); + source === "mirror" ? localMirror() : load(); + }, { signal }); + window.addEventListener("cellscript:locale-change", () => { + setServiceState(source); + renderRows(); + window.requestAnimationFrame(syncFilterMenus); + }, { signal }); + window.addEventListener("online", () => { + if (["mirror", "error", "stale"].includes(source)) load({ background: packages.length > 0 }); + }, { signal }); + document.addEventListener("visibilitychange", () => { + if (document.visibilityState === "visible" && source === "stale") load({ background: true }); + }, { signal }); + signal.addEventListener("abort", () => request?.abort(), { once: true }); + const cached = cache.get(cacheKey()); + if (cached && Date.now() - cached.updatedAt < 300_000 && Array.isArray(cached.packages)) { + packages = cached.packages.map((pkg: BrowsePackage) => ({ ...pkg })); + nextOffset = Number.isInteger(cached.nextOffset) ? cached.nextOffset : null; + setServiceState("live"); + renderRows(); + load({ background: true }); + } else { + load(); + } + }; + document.addEventListener("astro:page-load", setupRegistryBrowse); + + diff --git a/src/pages/registry/LS-IDL.astro b/src/pages/registry/LS-IDL.astro new file mode 100644 index 0000000..7f3f16e --- /dev/null +++ b/src/pages/registry/LS-IDL.astro @@ -0,0 +1,23 @@ +--- +import RegistryLsIdlLookup from "../../components/RegistryLsIdlLookup.astro"; +import RegistryLayout from "../../layouts/RegistryLayout.astro"; +import { defaultLocale, translations } from "../../i18n/translations"; +import { registryRuntime } from "../../lib/registry-runtime"; + +const t = translations[defaultLocale]; +--- + + +
+ +
+
diff --git a/src/pages/registry/api.astro b/src/pages/registry/api.astro new file mode 100644 index 0000000..9be3d50 --- /dev/null +++ b/src/pages/registry/api.astro @@ -0,0 +1,108 @@ +--- +import RegistryLayout from "../../layouts/RegistryLayout.astro"; +import { defaultLocale, translations } from "../../i18n/translations"; +import { registryRuntime } from "../../lib/registry-runtime"; +import arrowSquareOutIcon from "@phosphor-icons/core/regular/arrow-square-out.svg?raw"; + +const t = translations[defaultLocale]; +const apiOrigin = registryRuntime.apiOrigin; +const registryNetwork = registryRuntime.network; +const readEndpoints = [ + { method: "GET", path: "/v1/artifacts", description: t.registry.api.listArtifacts, key: "listArtifacts", href: `${apiOrigin}/v1/artifacts?limit=20` }, + { method: "GET", path: "/v1/artifacts/:namespace/:name", description: t.registry.api.artifactDetail, key: "artifactDetail" }, + { method: "GET", path: "/v1/artifacts/:namespace/:name/releases/:release/evidence", description: t.registry.api.releaseEvidence, key: "releaseEvidence" }, + { method: "GET", path: "/v1/artifacts/:namespace/:name/releases/:release/commitment", description: t.registry.api.releaseCommitment, key: "releaseCommitment" }, + { method: "GET", path: "/v1/ckb/scripts/:code_hash/interfaces/ls-idl", description: t.registry.api.lsIdlLookup, key: "lsIdlLookup" }, + { method: "GET", path: "/idl/:code_hash", description: t.registry.api.lsIdlCompatibility, key: "lsIdlCompatibility" }, + { method: "GET", path: "/v1/capabilities/:key_id/check?namespace=:namespace&name=:name", description: t.registry.api.capabilityCheck, key: "capabilityCheck" }, +]; +const writeEndpoints = [ + { method: "POST", path: "/v1/artifacts/:namespace/:name/releases", description: t.registry.api.publishRelease, key: "publishRelease" }, + { method: "POST", path: "/v1/artifacts/:namespace/:name/releases/:release/deployments", description: t.registry.api.publishDeployment, key: "publishDeployment" }, + { method: "POST", path: "/v1/artifacts/:namespace/:name/releases/:release/availability", description: t.registry.api.publishAvailability, key: "publishAvailability" }, +]; +--- + + +
+
+
+

{t.registry.api.readTitle}

+ + {t.registry.api.health} + + +
+
+ {readEndpoints.map((endpoint) => ( +
+ {endpoint.method} + {endpoint.path} +

{endpoint.description}

+ {endpoint.href && ( + + + + )} +
+ ))} +
+
+ +
+

{t.registry.api.writeTitle}

+
+ {writeEndpoints.map((endpoint) => ( +
+ {endpoint.method} + {endpoint.path} +

{endpoint.description}

+
+ ))} +
+
+ +
+
+

{t.registry.api.exampleTitle}

+

{t.registry.api.exampleBody}

+
+
+ {`curl --fail '${apiOrigin}/v1/artifacts?kind=deployable_contract&deployment=chain_verified'`} + +
+
+ +
+
+

LS-IDL

+

{t.registry.api.lsIdlExactBytes}

+
+
+ {`curl --fail '${apiOrigin}/v1/ckb/scripts/0x/interfaces/ls-idl?network=${registryNetwork}&hash_type=data1' --output idl.json`} + +
+
+
+
diff --git a/src/pages/registry/interface.astro b/src/pages/registry/interface.astro new file mode 100644 index 0000000..3ceef26 --- /dev/null +++ b/src/pages/registry/interface.astro @@ -0,0 +1,21 @@ +--- +// LS-IDL now has a route named after the interface itself. Keep this static +// redirect so existing bookmarks continue to work on every website host. +const destination = "/registry/LS-IDL"; +const canonical = "https://cellscript.dev/registry/LS-IDL"; +--- + + + + + + + CellScript Registry LS-IDL — moved + + + +

Moved to CellScript Registry LS-IDL.

+ + diff --git a/src/pages/registry/manage.astro b/src/pages/registry/manage.astro index 8c37159..a848e45 100644 --- a/src/pages/registry/manage.astro +++ b/src/pages/registry/manage.astro @@ -1,194 +1,611 @@ --- import RegistryLayout from "../../layouts/RegistryLayout.astro"; -import { registryPackages, shortHash } from "../../lib/registry"; +import { registryPackages, registryStatusTone, registryStatusTones } from "../../lib/registry"; import { defaultLocale, translations } from "../../i18n/translations"; +import { isTestnetSandbox, registryRuntime } from "../../lib/registry-runtime"; +import arrowLeftIcon from "@phosphor-icons/core/regular/arrow-left.svg?raw"; const t = translations[defaultLocale]; const description = t.registry.manage.description; -const first = registryPackages[0]; +const availablePackages = isTestnetSandbox ? [] : registryPackages; +const first = availablePackages[0]; +const initialCoordinate = first?.coordinate ?? ""; +const initialStatus = first + ? (t.registry.statuses as Record)[first.status] ?? first.status.replaceAll("_", " ") + : t.registry.manage.notRecorded; +const apiOrigin = registryRuntime.apiOrigin; --- - - {registryPackages.length > 0 ? ( -
- + +
+ + + {t.registry.manage.backToPackage} + -
-
-
- {t.registry.manage.workspaceLabel} -

{first.coordinate}

-

- {t.registry.manage.version} {first.latest_version || t.registry.manage.notRecorded} · {first.deployment.networks.join(", ") || t.registry.package.sourceOnly} · {shortHash(first.latest?.source_hash)} -

-
-
+
+
+ {t.registry.manage.packageContext} +

{initialCoordinate || t.registry.manage.packagePlaceholder}

+

+ {t.registry.manage.version} {first?.latest_version || t.registry.manage.notRecorded} · {initialStatus} +

+
+

{t.registry.manage.description}

+
-
-
+
+ +
+ {t.registry.manage.optionsSummary} +
+ + + {availablePackages.map((pkg) => )} + +
+
+ + +
+ {t.registry.manage.nextTask} + {t.registry.manage.taskPublish} +

{t.registry.manage.taskPublishBody}

+
+ +
+ {t.registry.manage.changeTask} +
+ {t.registry.manage.chooseTask} +
+
+ {t.registry.manage.primaryAction} +
+ + +
+
+
+ {t.registry.manage.readOnlyActions} +
+
+ + +
+
+ + +
+
+
+
+ {t.registry.manage.chainActions} +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ {t.registry.manage.dangerAction} +
+ + +
+
+
+
+
+ + + +
- ) : ( -
-

{t.registry.manage.emptyTitle}

-

{t.registry.manage.emptyBody}

+ + +
- )} - -
diff --git a/src/pages/registry/package/[namespace]/[name].astro b/src/pages/registry/package/[namespace]/[name].astro index df83c04..51bcc84 100644 --- a/src/pages/registry/package/[namespace]/[name].astro +++ b/src/pages/registry/package/[namespace]/[name].astro @@ -1,11 +1,11 @@ --- +import RegistryPackageDetail from "../../../../components/RegistryPackageDetail.astro"; import RegistryLayout from "../../../../layouts/RegistryLayout.astro"; -import { registryPackages, shortHash, type RegistryPackage } from "../../../../lib/registry"; -import { defaultLocale, translations } from "../../../../i18n/translations"; -import { Check, Copy } from "lucide-astro"; +import { registryPackages, toRegistryPackageView, type RegistryPackage } from "../../../../lib/registry"; +import { isTestnetSandbox, registryRuntime } from "../../../../lib/registry-runtime"; export function getStaticPaths() { - return registryPackages.map((pkg) => ({ + return (isTestnetSandbox ? [] : registryPackages).map((pkg) => ({ params: { namespace: pkg.namespace, name: pkg.name }, props: { pkg }, })); @@ -16,146 +16,14 @@ interface Props { } const { pkg } = Astro.props; -const t = translations[defaultLocale]; -const description = pkg.description || `CellScript registry package ${pkg.coordinate}`; -const activeDeployments = pkg.deployment.active; -const sourceHash = pkg.latest?.source_hash; +const description = pkg.description || `CellScript registry artifact ${pkg.coordinate}`; --- - -
- {/* Header: name + version/network/hash row (MVR SinglePackageHeader) */} -
- -
-

{pkg.coordinate}

-

- {t.registry.manage.version} {pkg.latest_version || {t.registry.package.notRecorded}} - - {pkg.deployment.networks.join(", ") || {t.registry.package.sourceOnly}} - - {shortHash(sourceHash)} - -

-

{description}

-
- {pkg.status} -
- -
-
-
-

{t.registry.package.versionsLabel}

-

{t.registry.package.versionsTitle}

-
- - - - - - - - - - - - - - {pkg.versions.map((version) => ( - - - - - - - - - - ))} - -
{t.registry.package.thVersion}{t.registry.package.thTag}{t.registry.package.thEdition}{t.registry.package.thSourceHash}{t.registry.package.thProfileHash}{t.registry.package.thStatus}{t.registry.package.thReleased}
{version.version}{version.tag}{version.edition}{shortHash(version.source_hash)}{shortHash(version.compatibility_profile_hash)}{version.yanked ? {t.registry.package.statusYanked} : {t.registry.package.statusAvailable}}{version.replaced_by ? ` -> ${version.replaced_by}` : ""}{version.released_at || {t.registry.package.notRecorded}}
-
-
- -
-

{t.registry.package.deploymentsLabel}

-

{t.registry.package.deploymentsTitle}

- {activeDeployments.length > 0 ? ( -
- - - - - - - - - - - - {activeDeployments.map((deployment) => ( - - - - - - - - ))} - -
{t.registry.package.thName}{t.registry.package.thNetwork}{t.registry.package.thOutPoint}{t.registry.package.thCodeHash}{t.registry.package.thCompiler}
{deployment.name || {t.registry.package.unnamed}}{deployment.network || {t.registry.package.notRecorded}}{deployment.out_point || {t.registry.package.notRecorded}}{shortHash(deployment.code_hash)}{deployment.compiler_version || {t.registry.package.notRecorded}}
-
- ) : ( -

{t.registry.package.noDeployment}

- )} -
-
- - {/* Sidebar: Install-first (MVR SinglePackageSidebar signature pattern) */} - -
-
+ + diff --git a/src/pages/registry/package/index.astro b/src/pages/registry/package/index.astro new file mode 100644 index 0000000..7a3da67 --- /dev/null +++ b/src/pages/registry/package/index.astro @@ -0,0 +1,18 @@ +--- +import RegistryPackageDetail from "../../../components/RegistryPackageDetail.astro"; +import { registryRuntime } from "../../../lib/registry-runtime"; +import RegistryLayout from "../../../layouts/RegistryLayout.astro"; +import { defaultLocale, translations } from "../../../i18n/translations"; + +const t = translations[defaultLocale]; +const apiOrigin = registryRuntime.apiOrigin; +--- + + + + diff --git a/src/pages/registry/submit.astro b/src/pages/registry/submit.astro index be7fd6c..b4dd6ec 100644 --- a/src/pages/registry/submit.astro +++ b/src/pages/registry/submit.astro @@ -1,210 +1,692 @@ --- import RegistryLayout from "../../layouts/RegistryLayout.astro"; import { defaultLocale, translations } from "../../i18n/translations"; +import { registryRuntime } from "../../lib/registry-runtime"; +import arrowLeftIcon from "@phosphor-icons/core/regular/arrow-left.svg?raw"; +import checkIcon from "@phosphor-icons/core/regular/check.svg?raw"; +import xIcon from "@phosphor-icons/core/regular/x.svg?raw"; const t = translations[defaultLocale]; -const description = t.registry.submit.description; +const description = t.registry.headers.submit.description; +const apiOrigin = registryRuntime.apiOrigin; --- - +
-
-

{t.registry.submit.builderLabel}

-

{t.registry.submit.builderHeading}

-

{t.registry.submit.builderBody}

-
- -
-
-
+ + +
+ + +
+ + +
+
+ +
+

{t.registry.submit.walletDialogLabel}

+

{t.registry.submit.walletChooseTitle}

+
+ +
+

{t.registry.submit.walletChooseBody}

+
+ + +
+
+

{t.registry.submit.walletPrivacy}

+
+
+
diff --git a/src/styles/global.css b/src/styles/global.css index e80b0bb..5e60586 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -15,9 +15,12 @@ --page-grid-y: oklch(0.38 0.018 180 / 0.3); --page-grid-x: oklch(0.38 0.018 180 / 0.24); --page-glow: oklch(0.28 0.004 180 / 0.24); - --noise-dot: oklch(1 0 0 / 0.16); - --noise-opacity: 0.16; + --noise-dot: oklch(1 0 0); + --noise-opacity: 0.05; --noise-blend: overlay; + /* Static grain mask (fractal noise, alpha-driven). Shared by both themes; + --noise-dot tints it, --noise-blend sets the material mixing. */ + --noise-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)'/%3E%3C/svg%3E"); --panel: oklch(0.145 0.004 180); --panel-2: oklch(0.18 0.004 180 / 0.72); --panel-strong: oklch(0.12 0.004 180 / 0.86); @@ -25,21 +28,32 @@ --panel-soft: oklch(0.15 0.004 180 / 0.68); --panel-chip: oklch(0.12 0.004 180 / 0.72); --image-caption-bg: oklch(0.105 0.006 180 / 0.5); - --surface-header: oklch(0.105 0 0 / 0.82); - --surface-footer: oklch(0.08 0 0); - --surface-hover: oklch(0.18 0 0 / 0.5); - --surface-button: oklch(0.16 0 0 / 0.56); - --surface-tab-active: oklch(0.2 0 0 / 0.6); - --surface-select: oklch(0.13 0 0); - --surface-card-strong: oklch(0.14 0 0 / 0.82); - --surface-elevated-start: oklch(0.17 0 0 / 0.72); - --surface-elevated-end: oklch(0.12 0 0 / 0.82); - --surface-check-start: oklch(0.17 0 0 / 0.7); - --surface-check-end: oklch(0.12 0 0 / 0.86); - --surface-code-start: oklch(0.15 0 0); - --surface-code-end: oklch(0.105 0 0); - --line: oklch(0.32 0 0 / 0.58); - --line-soft: oklch(0.25 0 0 / 0.48); + --surface-header: oklch(0.105 0.006 180 / 0.82); + --surface-footer: oklch(0.08 0.006 180); + --surface-hover: oklch(0.18 0.006 180 / 0.5); + --surface-button: oklch(0.16 0.006 180 / 0.56); + --control-surface: var(--surface-button); + --control-surface-hover: var(--surface-hover); + --control-border: var(--line-soft); + --control-border-hover: var(--line); + --control-text: var(--text-soft); + --control-text-hover: var(--text); + --control-selected-surface: var(--accent-surface); + --control-selected-text: var(--accent-strong); + --control-primary: var(--accent); + --control-primary-hover: var(--accent-strong); + --control-primary-text: var(--primary-contrast); + --surface-tab-active: oklch(0.2 0.006 180 / 0.6); + --surface-select: oklch(0.13 0.006 180); + --surface-card-strong: oklch(0.14 0.006 180 / 0.82); + --surface-elevated-start: oklch(0.17 0.006 180 / 0.72); + --surface-elevated-end: oklch(0.12 0.006 180 / 0.82); + --surface-check-start: oklch(0.17 0.006 180 / 0.7); + --surface-check-end: oklch(0.12 0.006 180 / 0.86); + --surface-code-start: oklch(0.15 0.01 180); + --surface-code-end: oklch(0.105 0.01 180); + --line: oklch(0.32 0.014 180 / 0.58); + --line-soft: oklch(0.25 0.012 180 / 0.48); --line-inner: oklch(1 0 0 / 0.04); --line-inner-soft: oklch(1 0 0 / 0.03); --line-inner-table: oklch(1 0 0 / 0.035); @@ -51,37 +65,61 @@ shadows read as depth, not smudge. Apply via the tokens, e.g. box-shadow: var(--elev-2). */ --elev-1: 0 1px 2px var(--shadow-soft); - --elev-2: 0 6px 18px var(--shadow-soft), 0 2px 6px oklch(0.1 0.02 180 / 0.5); - --elev-3: 0 22px 80px var(--shadow-strong), 0 8px 24px var(--shadow-elevated); - --text: oklch(0.94 0 0); - --text-strong: oklch(0.88 0 0); - --text-section: oklch(0.85 0 0); - --text-soft: oklch(0.78 0 0); - --text-chip: oklch(0.74 0 0); - --muted: oklch(0.72 0 0); - --dim: oklch(0.7 0.01 0); - --line-number: oklch(0.66 0.005 0); + --elev-2: 0 10px 28px -18px var(--shadow-elevated), 0 2px 6px var(--shadow-soft); + --elev-3: 0 24px 64px -30px var(--shadow-strong), 0 10px 24px -20px var(--shadow-elevated); + --text: oklch(0.94 0.008 180); + --text-strong: oklch(0.88 0.008 180); + --text-section: oklch(0.85 0.008 180); + --text-soft: oklch(0.78 0.01 180); + --text-chip: oklch(0.74 0.01 180); + --muted: oklch(0.72 0.01 180); + --dim: oklch(0.7 0.012 180); + --line-number: oklch(0.66 0.01 180); --accent: oklch(0.78 0.11 158); --accent-strong: oklch(0.82 0.12 158); --accent-quiet: oklch(0.61 0.09 158); --accent-line: oklch(0.72 0.1 158 / 0.42); --accent-surface: oklch(0.2 0.035 158 / 0.36); - /* Secondary accent: warm amber for CTAs / hovers / state highlights. - Deliberately off the green so it reads as a distinct signal. */ + /* Decorative warm accent. Status meaning uses the semantic tokens below. */ --accent-warm: oklch(0.8 0.13 70); - --accent-warm-surface: oklch(0.24 0.05 70 / 0.34); - --accent-warm-line: oklch(0.74 0.11 70 / 0.4); /* Status palette: encoded semantics for registry state. */ --status-active: oklch(0.78 0.14 158); --status-active-surface: oklch(0.22 0.04 158 / 0.34); --status-info: oklch(0.78 0.1 230); --status-info-surface: oklch(0.22 0.04 230 / 0.34); + --status-warning-text: oklch(0.8 0.13 70); + --status-warning-surface: oklch(0.24 0.05 70 / 0.34); + --status-warning-line: oklch(0.74 0.11 70 / 0.4); + --status-danger-text: oklch(0.76 0.14 28); + --status-danger-surface: oklch(0.22 0.05 28 / 0.34); + --status-danger-line: oklch(0.68 0.13 28 / 0.44); --status-stable: oklch(0.78 0.08 100); --status-stable-surface: oklch(0.22 0.035 100 / 0.3); - --status-source: oklch(0.7 0.012 0); - --status-source-surface: oklch(0.2 0 0 / 0.42); - --primary-contrast: oklch(0.08 0 0); - --brand-mark-bg: oklch(0.16 0 0); + --status-source: oklch(0.7 0.012 180); + --status-source-surface: oklch(0.2 0.008 180 / 0.42); + --sandbox-marker: oklch(0.78 0.14 72); + --sandbox-text: oklch(0.8 0.11 72); + --sandbox-surface: oklch(0.21 0.035 72 / 0.36); + --sandbox-line: oklch(0.68 0.1 72 / 0.42); + /* Mermaid accepts hex values only. Keep its complete palette semantic and + theme-bound here instead of scattering literals through the renderer. */ + --diagram-background: #101714; + --diagram-primary: #0f1d18; + --diagram-primary-text: #eff8f4; + --diagram-primary-border: #4fc294; + --diagram-secondary: #17211e; + --diagram-secondary-text: #e3eee9; + --diagram-secondary-border: #568b75; + --diagram-tertiary: #151a18; + --diagram-tertiary-text: #d6e2dd; + --diagram-tertiary-border: #4d655b; + --diagram-line: #74d8ad; + --diagram-edge-label: #101713; + --diagram-note: #272215; + --diagram-note-text: #f1e3c3; + --diagram-note-border: #cfa55c; + --primary-contrast: oklch(0.08 0.006 180); + --brand-mark-bg: oklch(0.16 0.006 180); --brand-mark-inner: var(--accent-line); --hero-aurora-opacity: 0.9; --hero-aurora-saturate: 1.08; @@ -101,7 +139,7 @@ --spotlight-b: oklch(0.8 0.1 220 / 0.045); --scan-a: oklch(0.84 0.13 158 / 0.08); --scan-b: oklch(0.76 0.09 220 / 0.06); - --scrollbar-thumb: oklch(0.36 0 0); + --scrollbar-thumb: oklch(0.36 0.012 180); --token-keyword: oklch(0.85 0.05 260); --token-type-decl: oklch(0.8 0.08 200); --token-cell-kind: oklch(0.85 0.1 160); @@ -112,16 +150,16 @@ --token-builtin-type: oklch(0.8 0.06 250); --token-string: oklch(0.75 0.04 140); --token-number: oklch(0.8 0.06 80); - --token-comment: oklch(0.7 0.012 0); - --token-operator: oklch(0.68 0.006 0); - --token-punctuation: oklch(0.66 0.006 0); + --token-comment: oklch(0.7 0.014 180); + --token-operator: oklch(0.68 0.01 180); + --token-punctuation: oklch(0.66 0.01 180); --token-arrow: oklch(0.75 0.04 30); - --workflow-icon: oklch(0.72 0 0); - --workflow-icon-muted: oklch(0.62 0 0); - --workflow-arrow: oklch(0.55 0 0); + --workflow-icon: oklch(0.72 0.012 180); + --workflow-icon-muted: oklch(0.62 0.012 180); + --workflow-arrow: oklch(0.55 0.014 180); --boundary-rule: oklch(0.62 0.09 158 / 0.62); - --boundary-text: oklch(0.7 0 0); - --question-text: oklch(0.77 0 0); + --boundary-text: oklch(0.7 0.012 180); + --question-text: oklch(0.77 0.012 180); /* On-chain substrate aesthetics: machine products (hash/cycle/bytes). Deliberately a separate palette from brand green so colour @@ -135,16 +173,38 @@ --bar-track: oklch(0.16 0.004 180); --bar-fill: var(--accent); - --radius: 8px; - --radius-sm: 5px; - --max: 1180px; + /* Geometry contract: use only these tiers for interface surfaces. */ + --radius-data: 4px; + --radius-control: 6px; + --radius-panel: 8px; + --radius-overlay: 12px; + --radius-dialog: 16px; + --radius: var(--radius-panel); + --radius-sm: var(--radius-data); + --control-radius: var(--radius-control); + --control-radius-large: var(--radius-panel); + --focus-ring-width: 2px; + --focus-ring-offset: 3px; + --control-height-dense: 32px; + --control-height: 40px; + --control-height-workflow: 48px; + /* One shared alignment frame for global navigation and page shells. + Reading measures remain narrower inside the frame. */ + --frame-max: 1440px; + --max: var(--frame-max); + --workbench-max: 1920px; --narrow: 900px; - --site-gutter: 40px; + --page-pad-inline: clamp(20px, 2.5vw, 40px); + --site-gutter: calc(var(--page-pad-inline) * 2); + --docs-reading-max: 784px; + --docs-rail-max: 280px; + --docs-grid-gap: clamp(32px, 3vw, 48px); --layout-gap: 18px; --rail-width: 260px; --panel-pad: 22px; /* Modular type scale (1.125 ratio). Use these for new copy; --text-body is applied to as the site default. */ + --text-micro: 0.7rem; --text-xs: 0.75rem; --text-sm: 0.875rem; --text-body: 1.0625rem; @@ -176,8 +236,8 @@ --page-grid-y: oklch(0.72 0.015 180 / 0.34); --page-grid-x: oklch(0.72 0.015 180 / 0.25); --page-glow: oklch(0.9 0.03 170 / 0.58); - --noise-dot: oklch(0.42 0.015 180 / 0.13); - --noise-opacity: 0.1; + --noise-dot: oklch(0.42 0.015 180); + --noise-opacity: 0.03; --noise-blend: multiply; --panel: oklch(0.985 0.004 175); --panel-2: oklch(0.96 0.006 175 / 0.78); @@ -221,16 +281,39 @@ --accent-line: oklch(0.54 0.1 158 / 0.36); --accent-surface: oklch(0.87 0.045 158 / 0.38); --accent-warm: oklch(0.62 0.14 65); - --accent-warm-surface: oklch(0.9 0.05 65 / 0.42); - --accent-warm-line: oklch(0.68 0.11 65 / 0.4); --status-active: oklch(0.48 0.13 158); --status-active-surface: oklch(0.9 0.04 158 / 0.42); --status-info: oklch(0.5 0.11 230); --status-info-surface: oklch(0.9 0.04 230 / 0.4); + --status-warning-text: oklch(0.44 0.12 70); + --status-warning-surface: oklch(0.93 0.04 70 / 0.58); + --status-warning-line: oklch(0.58 0.11 70 / 0.44); + --status-danger-text: oklch(0.44 0.15 28); + --status-danger-surface: oklch(0.91 0.04 28 / 0.48); + --status-danger-line: oklch(0.56 0.14 28 / 0.42); --status-stable: oklch(0.5 0.1 100); --status-stable-surface: oklch(0.9 0.04 100 / 0.38); - --status-source: oklch(0.45 0.012 0); - --status-source-surface: oklch(0.92 0 0 / 0.55); + --status-source: oklch(0.45 0.012 180); + --status-source-surface: oklch(0.92 0.006 180 / 0.55); + --sandbox-marker: oklch(0.56 0.14 72); + --sandbox-text: oklch(0.43 0.1 72); + --sandbox-surface: oklch(0.92 0.04 72 / 0.55); + --sandbox-line: oklch(0.58 0.11 72 / 0.42); + --diagram-background: #f7faf8; + --diagram-primary: #eef8f3; + --diagram-primary-text: #13231e; + --diagram-primary-border: #087c57; + --diagram-secondary: #f5f8f6; + --diagram-secondary-text: #263d35; + --diagram-secondary-border: #7aa392; + --diagram-tertiary: #e8f0ec; + --diagram-tertiary-text: #2f463d; + --diagram-tertiary-border: #8ba79b; + --diagram-line: #087c57; + --diagram-edge-label: #f7faf8; + --diagram-note: #fff7e6; + --diagram-note-text: #50390f; + --diagram-note-border: #9a6a13; --primary-contrast: oklch(0.98 0.004 175); --brand-mark-bg: oklch(0.965 0.006 175); --brand-mark-inner: var(--accent-line); @@ -310,6 +393,7 @@ body { margin: 0; min-width: 0; overflow-x: clip; + isolation: isolate; background: radial-gradient(circle at 24% 0%, var(--page-glow), var(--color-transparent) 34rem), radial-gradient(circle at 88% 18%, var(--spotlight-b), var(--color-transparent) 30rem), @@ -335,8 +419,28 @@ body::before { mask-image: linear-gradient(to bottom, var(--mask-solid) 0, var(--color-transparent) 62vh); } +/* Site-wide substrate grain. Fixed, pointer-transparent, and blended with + the theme material so flat near-black/near-white regions keep a texture + instead of reading as raw document paper. */ body::after { - content: none; + content: ""; + position: fixed; + inset: 0; + z-index: -1; + pointer-events: none; + background-color: var(--noise-dot); + opacity: var(--noise-opacity); + mix-blend-mode: var(--noise-blend); + -webkit-mask-image: var(--noise-mask); + -webkit-mask-size: 200px 200px; + mask-image: var(--noise-mask); + mask-size: 200px 200px; +} + +@media (prefers-reduced-transparency: reduce) { + body::after { + content: none; + } } a { @@ -362,9 +466,12 @@ select { a:focus-visible, button:focus-visible, -select:focus-visible { - outline: 2px solid var(--accent); - outline-offset: 3px; +select:focus-visible, +input:focus-visible, +textarea:focus-visible, +summary:focus-visible { + outline: var(--focus-ring-width) solid var(--accent); + outline-offset: var(--focus-ring-offset); } .skip-link { @@ -389,21 +496,30 @@ select:focus-visible { position: sticky; top: 0; z-index: var(--z-header); - border-bottom: 1px solid var(--line-soft); + border: 0; background: var(--surface-header); - backdrop-filter: blur(18px); + box-shadow: inset 0 -1px var(--line-soft); + backdrop-filter: blur(16px) saturate(112%); + transition: + background-color var(--duration-med) var(--ease-standard), + box-shadow var(--duration-med) var(--ease-standard); +} + +.site-header[data-scrolled] { + background: color-mix(in oklch, var(--surface-header) 94%, var(--bg)); + box-shadow: inset 0 -1px var(--line), var(--elev-1); } .nav { box-sizing: border-box; - width: 100%; - height: 68px; - margin: 0; - padding: 0 clamp(18px, 2.2vw, 32px); - display: flex; + width: min(var(--max), calc(100% - var(--site-gutter))); + min-height: 64px; + margin: 0 auto; + padding: 0; + display: grid; + grid-template-columns: minmax(160px, 1fr) auto minmax(160px, 1fr); align-items: center; - justify-content: space-between; - gap: clamp(16px, 2vw, 28px); + gap: clamp(18px, 3vw, 44px); } .brand { @@ -413,7 +529,13 @@ select:focus-visible { min-height: 44px; font-weight: 700; font-size: 1.08rem; + letter-spacing: -0.018em; text-decoration: none; + transition: color var(--duration-fast) var(--ease-standard); +} + +.brand:hover { + color: var(--accent); } .brand-mark { @@ -478,15 +600,24 @@ select:focus-visible { mix-blend-mode: normal; } -.nav-links { +.nav-primary { min-width: 0; - margin-left: auto; display: flex; align-items: center; - gap: 6px; + justify-content: center; + gap: 4px; +} + +.nav-utilities { + min-width: 0; + display: flex; + align-items: center; + justify-content: flex-end; + gap: 4px; } .nav-link { + position: relative; appearance: none; font: inherit; text-decoration: none; @@ -497,40 +628,106 @@ select:focus-visible { .nav-link, .button, .language-toggle, -.theme-toggle { +.theme-toggle, +.nav-utility, +.nav-menu-toggle, +.nav-drawer-close { display: inline-flex; align-items: center; justify-content: center; gap: 8px; - min-height: 40px; + min-height: var(--control-height); padding: 0 12px; border: 1px solid var(--color-transparent); - border-radius: var(--radius); + border-radius: var(--control-radius); color: var(--muted); font-size: 0.92rem; font-weight: 500; } -.nav-link svg { - width: 18px; - height: 18px; - flex: 0 0 auto; - color: var(--accent); +.nav-link { + min-height: 40px; + padding-inline: 14px; + border-color: transparent; + color: var(--muted); + font-size: 0.91rem; + font-weight: 540; } -.nav-link svg :is(path, line, polyline, circle, rect) { - fill: none; - stroke: currentColor; - stroke-width: 1.7; - stroke-linecap: round; - stroke-linejoin: round; +.nav-link, +.language-toggle, +.theme-toggle, +.nav-utility, +.nav-menu-toggle, +.nav-drawer-close { + border-radius: var(--control-radius); +} + +.nav-utility { + appearance: none; + text-decoration: none; +} + +.nav-source { + position: relative; +} + +.nav-tooltip { + position: absolute; + top: calc(100% + 8px); + left: 50%; + z-index: 1; + width: max-content; + max-width: 180px; + padding: 6px 9px; + pointer-events: none; + border: 1px solid var(--line-soft); + border-radius: var(--radius-control); + color: var(--text-soft); + background: var(--panel-popover); + box-shadow: var(--elev-1); + font-family: var(--font-sans); + font-size: var(--text-xs); + font-weight: 560; + line-height: 1.2; + white-space: nowrap; + opacity: 0; + transform: translate(-50%, -3px); + transition: + opacity var(--duration-fast) var(--ease-standard), + transform var(--duration-fast) var(--ease-quick); +} + +.nav-source:hover .nav-tooltip, +.nav-source:focus-visible .nav-tooltip { + opacity: 1; + transform: translate(-50%, 0); +} + +.nav-link::after { + content: ""; + position: absolute; + left: 50%; + bottom: 3px; + width: 18px; + height: 2px; + border-radius: 999px; + background: var(--accent); + opacity: 0; + transform: translateX(-50%) scaleX(0.5); + transition: + opacity var(--duration-fast) var(--ease-standard), + transform var(--duration-fast) var(--ease-quick); } .nav-link:hover:not(:disabled), .language-toggle:hover, -.theme-toggle:hover { +.theme-toggle:hover, +.nav-utility:hover, +.nav-menu-toggle:hover, +.nav-drawer-close:hover { color: var(--text); - border-color: var(--line-soft); + border-color: transparent; background: var(--surface-hover); } @@ -546,16 +743,25 @@ select:focus-visible { color: var(--muted); } -.theme-toggle { - gap: 8px; +.theme-toggle, +.nav-utility, +.nav-menu-toggle, +.nav-drawer-close { + width: 40px; + min-width: 40px; + padding: 0; cursor: pointer; - background: var(--surface-button); + color: var(--text-soft); + background: transparent; } .language-toggle { + width: 76px; + min-width: 76px; + padding-inline: 12px; cursor: pointer; - color: var(--text); - background: var(--surface-button); + color: var(--text-soft); + background: transparent; } .language-short { @@ -622,63 +828,268 @@ select:focus-visible { } } -.theme-toggle-track { - width: 32px; +.nav-icon { + width: 18px; height: 18px; - padding: 2px; - border: 1px solid var(--line); - border-radius: 999px; - background: var(--panel-strong); + display: inline-grid; + place-items: center; + flex: 0 0 auto; + line-height: 0; } -.theme-toggle-thumb { +.nav-icon > svg { display: block; - width: 12px; - height: 12px; - border-radius: 50%; - background: var(--accent); - box-shadow: 0 0 0 2px var(--accent-line); - transform: translateX(0); + width: 100%; + height: 100%; +} + +.theme-toggle[aria-pressed="true"] { + color: var(--accent); + background: var(--accent-surface); +} + +.nav-menu-toggle { + display: none; +} + +.nav-menu-close-icon { + display: none; +} + +.nav-menu-toggle[aria-expanded="true"] .nav-menu-open-icon { + display: none; +} + +.nav-menu-toggle[aria-expanded="true"] .nav-menu-close-icon { + display: inline-grid; +} + +.nav-menu-toggle[aria-expanded="true"] { + visibility: hidden; + opacity: 0; +} + +.nav-drawer-backdrop { + position: fixed; + inset: 0; + z-index: var(--z-overlay); + padding-top: 60px; + background: color-mix(in oklch, var(--bg) 58%, transparent); + opacity: 0; + transition: opacity 180ms var(--ease-standard); +} + +.nav-drawer-backdrop[data-open] { + opacity: 1; +} + +.nav-drawer-backdrop[hidden] { + display: none; +} + +.nav-drawer { + width: min(100%, 520px); + max-height: calc(100dvh - 60px); + margin-left: auto; + padding: 16px 18px calc(20px + env(safe-area-inset-bottom)); + overflow-y: auto; + border-left: 1px solid var(--line-soft); + border-bottom: 1px solid var(--line-soft); + border-bottom-left-radius: 18px; + background: var(--panel-popover); + box-shadow: var(--elev-2); + backdrop-filter: blur(18px) saturate(112%); + opacity: 0; + transform: translateY(-8px); + transition: + opacity 180ms var(--ease-standard), + transform 180ms var(--ease-quick); +} + +.nav-drawer[data-open] { + opacity: 1; + transform: translateY(0); +} + +.nav-drawer-head, +.nav-drawer-preferences > button { + display: flex; + align-items: center; + justify-content: space-between; +} + +.nav-drawer-head { + min-height: 44px; + padding: 0 2px 12px 12px; + border-bottom: 1px solid var(--line-soft); + color: var(--dim); + font-family: var(--font-mono); + font-size: var(--text-xs); + letter-spacing: 0.045em; + text-transform: uppercase; +} + +.nav-drawer-links { + display: grid; + gap: 2px; + padding: 12px 0; +} + +.nav-drawer-link { + position: relative; + min-height: 50px; + display: grid; + grid-template-columns: 22px minmax(0, 1fr) auto; + align-items: center; + gap: 13px; + padding: 0 14px; + border-radius: var(--radius-control); + color: var(--text-soft); + font-weight: 560; + text-decoration: none; transition: background-color var(--duration-fast) var(--ease-standard), - box-shadow var(--duration-fast) var(--ease-standard), + color var(--duration-fast) var(--ease-standard), transform var(--duration-fast) var(--ease-quick); } -:root[data-theme="light"] .theme-toggle-thumb { - transform: translateX(14px); +.nav-drawer-link::before { + content: ""; + position: absolute; + inset: 12px auto 12px 0; + width: 2px; + border-radius: 999px; + background: var(--accent); + opacity: 0; + transform: scaleY(0.55); } -.button { +.nav-drawer-link:hover { color: var(--text); - border-color: var(--line); - background: var(--surface-button); + background: var(--surface-hover); +} + +.nav-drawer-link:active { + transform: translateY(1px); +} + +.nav-drawer-link.active { + color: var(--text); + background: var(--accent-surface); +} + +.nav-drawer-link.active::before { + opacity: 1; + transform: scaleY(1); +} + +.nav-drawer-brand-icon .brand-mark { + width: 18px; + height: 18px; +} + +.nav-external-mark { + width: 15px; + height: 15px; + color: var(--dim); +} + +.nav-drawer-preferences { + display: grid; + gap: 2px; + padding-top: 14px; + border-top: 1px solid var(--line-soft); +} + +.nav-drawer-section-label { + padding: 0 14px 8px; + color: var(--dim); + font-family: var(--font-mono); + font-size: var(--text-xs); + letter-spacing: 0.045em; + text-transform: uppercase; +} + +.nav-drawer-preferences > button { + min-height: 48px; + padding: 0 14px; + border: 0; + border-radius: var(--radius-control); + background: transparent; + color: var(--text-soft); + font: inherit; + cursor: pointer; +} + +.nav-drawer-preferences > button:hover { + color: var(--text); + background: var(--surface-hover); +} + +.nav-drawer-preferences strong { + color: var(--text); + font-weight: 600; +} + +body[data-menu-open="true"] { + overflow: hidden; +} + +.button { + appearance: none; + min-height: var(--control-height); + padding-inline: 15px; + border-color: var(--control-border); + border-radius: var(--control-radius); + background: var(--control-surface); + color: var(--control-text); + font-weight: 600; + cursor: pointer; + touch-action: manipulation; + box-shadow: inset 0 1px 0 var(--line-inner-soft); } .button.primary { - color: var(--primary-contrast); + color: var(--control-primary-text); border-color: var(--color-transparent); - background: var(--accent); + background: var(--control-primary); + box-shadow: inset 0 1px 0 color-mix(in oklch, white 24%, transparent); } .button.ghost { - color: var(--text-soft); - border-color: var(--line-soft); - background: var(--color-transparent); + color: var(--control-text); + border-color: var(--control-border); + background: var(--control-surface); } -.button.ghost:hover { - color: var(--text); - border-color: var(--line); - background: var(--panel-soft); +.button:hover { + color: var(--control-text-hover); + border-color: var(--control-border-hover); + background: var(--control-surface-hover); + transform: translateY(-1px); +} + +.button.primary:hover { + color: var(--control-primary-text); + border-color: var(--color-transparent); + background: var(--control-primary-hover); +} + +.button:active { + transform: translateY(1px); +} + +.button:disabled, +.button[aria-disabled="true"] { + cursor: not-allowed; + opacity: 0.46; + transform: none; } -.button:hover, .example-card:hover { transform: translateY(-1px); } -.button:active, .example-card:active { transform: translateY(0); } @@ -709,12 +1120,14 @@ body.home-body { .nav-link.active { color: var(--text); - border-color: var(--accent-line); - background: var(--accent-surface); + border-color: transparent; + background: transparent; + font-weight: 650; } -.nav-link.active svg { - color: var(--accent-strong); +.nav-link.active::after { + opacity: 1; + transform: translateX(-50%) scaleX(1); } .hero-status { @@ -734,12 +1147,10 @@ body.home-body { gap: 8px; padding: 0 11px; border: 1px solid var(--line-soft); - border-radius: 999px; + border-radius: var(--radius-control); color: var(--muted); - background: - linear-gradient(180deg, var(--line-inner-soft), var(--color-transparent)), - var(--panel-soft); - box-shadow: 0 10px 30px var(--shadow-soft); + background: var(--panel-soft); + box-shadow: none; text-decoration: none; transition: border-color var(--duration-fast) var(--ease-standard), @@ -750,14 +1161,12 @@ body.home-body { .hero-release-tag:hover, .hero-release-tag:focus-visible { border-color: var(--accent-line); - background: - linear-gradient(180deg, var(--accent-surface), var(--color-transparent)), - var(--panel-soft); + background: var(--accent-surface); color: var(--accent-strong); - outline: none; } -.hero-release-tag svg { +.hero-release-icon, +.hero-release-icon svg { width: 16px; height: 16px; flex: 0 0 auto; @@ -766,10 +1175,11 @@ body.home-body { .hero-release-tag small { color: var(--dim); - font-size: 0.68rem; - font-weight: 750; + font-family: var(--font-sans); + font-size: var(--text-xs); + font-weight: 600; line-height: 1; - text-transform: uppercase; + text-transform: none; } .hero-release-tag strong { @@ -809,7 +1219,7 @@ body.home-body { overflow: visible; min-height: clamp(620px, 76svh, 760px); display: grid; - grid-template-columns: minmax(0, 0.92fr) minmax(460px, 1.08fr); + grid-template-columns: minmax(0, 1fr) minmax(460px, 1fr); align-items: start; gap: clamp(44px, 5vw, 76px); padding: clamp(132px, 11.5svh, 150px) 0 clamp(74px, 9svh, 104px); @@ -843,8 +1253,8 @@ body.home-body { z-index: -1; overflow: hidden; pointer-events: none; - opacity: var(--hero-aurora-opacity); - filter: saturate(var(--hero-aurora-saturate)); + opacity: calc(var(--hero-aurora-opacity) * 0.62); + filter: saturate(0.82); mask-image: radial-gradient(ellipse at 55% 45%, var(--mask-solid) 0%, var(--mask-solid) 38%, var(--color-transparent) 74%); transform: translateX(-50%); } @@ -859,37 +1269,19 @@ body.home-body { height: 40%; left: 24%; top: 24%; - border-radius: 999px; - background: - linear-gradient(105deg, var(--color-transparent) 8%, var(--hero-aurora-a), var(--color-transparent) 42%), - linear-gradient(145deg, var(--color-transparent) 28%, var(--hero-aurora-b), var(--color-transparent) 68%); - filter: blur(28px); - transform: rotate(-13deg) skewX(-14deg); - animation: aurora-drift 12s var(--ease-standard) infinite alternate; + border-radius: 50%; + background: radial-gradient(ellipse at center, var(--hero-aurora-a), var(--color-transparent) 70%); + filter: blur(44px); + transform: rotate(-8deg) translate3d(0, 0, 0); + animation: aurora-drift 18s var(--ease-standard) infinite alternate; } .hero-aurora span:nth-child(2) { - width: 52%; - height: 48%; - left: 42%; - top: 11%; - background: - linear-gradient(100deg, var(--color-transparent) 12%, var(--hero-aurora-c), var(--color-transparent) 58%), - linear-gradient(70deg, var(--color-transparent) 20%, var(--hero-aurora-d), var(--color-transparent) 70%); - animation-duration: 15s; - animation-delay: -4s; + display: none; } .hero-aurora span:nth-child(3) { - width: 44%; - height: 42%; - left: 9%; - top: 37%; - background: - linear-gradient(115deg, var(--color-transparent) 15%, var(--hero-aurora-e), var(--color-transparent) 60%), - linear-gradient(55deg, var(--color-transparent) 28%, var(--hero-aurora-f), var(--color-transparent) 72%); - animation-duration: 18s; - animation-delay: -8s; + display: none; } .hero-field { @@ -901,7 +1293,7 @@ body.home-body { z-index: -1; overflow: hidden; pointer-events: none; - opacity: 0.78; + opacity: 0.52; mask-image: radial-gradient(ellipse at 58% 43%, var(--mask-solid) 0%, var(--mask-solid) 42%, var(--color-transparent) 78%); transform: translateX(-50%); } @@ -915,23 +1307,15 @@ body.home-body { .hero-field::before { inset: 7% 2% 9%; border: 1px solid var(--hero-aurora-border); - border-radius: 38px; + border-radius: var(--radius-dialog); background: linear-gradient(90deg, var(--page-grid-x) 1px, var(--color-transparent) 1px) 0 0 / 84px 84px, - linear-gradient(180deg, var(--page-grid-y) 1px, var(--color-transparent) 1px) 0 0 / 84px 84px, - radial-gradient(circle at 65% 28%, var(--accent-surface), var(--color-transparent) 30%); + linear-gradient(180deg, var(--page-grid-y) 1px, var(--color-transparent) 1px) 0 0 / 84px 84px; transform: rotate(-5deg) translate3d(0, 0, 0); } .hero-field span { - position: absolute; - left: -12%; - width: 42%; - height: 1px; - border-radius: 999px; - background: linear-gradient(90deg, var(--color-transparent), var(--accent-line), var(--color-transparent)); - opacity: 0.2; - transform: rotate(-5deg) translate3d(-18%, 0, 0); + display: none; } .hero-field span:nth-child(1) { top: 18%; animation-delay: -1s; } @@ -1293,7 +1677,8 @@ body.home-body { outline-offset: 2px; } -.playground-link svg { +.playground-link-icon, +.playground-link-icon svg { width: 16px; height: 16px; } @@ -1306,12 +1691,12 @@ body.home-body { .code-dot { display: inline-block; - width: 8px; - height: 8px; + width: 2px; + height: 12px; margin-right: 8px; - border-radius: 50%; - background: var(--accent); - box-shadow: 0 0 0 3px var(--accent-line); + border-radius: 0; + background: var(--accent-quiet); + box-shadow: none; } .copy-btn { @@ -1319,13 +1704,13 @@ body.home-body { display: inline-flex; align-items: center; justify-content: center; - width: 28px; - height: 28px; + width: var(--control-height-dense); + height: var(--control-height-dense); padding: 0; - border: 1px solid var(--line-soft); - border-radius: 6px; - background: var(--surface-button); - color: var(--muted); + border: 1px solid var(--control-border); + border-radius: var(--control-radius); + background: var(--control-surface); + color: var(--control-text); cursor: pointer; flex-shrink: 0; touch-action: manipulation; @@ -1336,18 +1721,30 @@ body.home-body { } .copy-btn:hover { - color: var(--text); - border-color: var(--line); - background: var(--surface-hover); + color: var(--control-text-hover); + border-color: var(--control-border-hover); + background: var(--control-surface-hover); +} + +.copy-btn:active { + transform: translateY(1px); } .copy-btn .check-icon { display: none; } +.copy-btn .copy-icon, +.copy-btn .check-icon { + width: 16px; + height: 16px; + line-height: 0; +} + .copy-btn.is-copied { - color: var(--accent); + color: var(--control-selected-text); border-color: var(--accent-line); + background: var(--control-selected-surface); } .copy-btn.is-copied .copy-icon { @@ -1355,7 +1752,7 @@ body.home-body { } .copy-btn.is-copied .check-icon { - display: block; + display: inline-flex; } .copy-btn svg { @@ -1377,13 +1774,16 @@ body.home-body { .docs-shell { box-sizing: border-box; - width: 100%; - margin: 0; - padding: clamp(44px, 6vw, 74px) clamp(18px, 2.2vw, 32px) clamp(72px, 8vw, 112px); + width: min(var(--max), calc(100% - var(--site-gutter))); + margin: 0 auto; + padding: clamp(44px, 6vw, 74px) 0 clamp(72px, 8vw, 112px); display: grid; - grid-template-columns: minmax(220px, 290px) minmax(0, 840px) minmax(190px, 280px); - gap: clamp(28px, 3.4vw, 64px); - justify-content: space-between; + grid-template-columns: + minmax(220px, var(--docs-rail-max)) + minmax(0, var(--docs-reading-max)) + minmax(220px, var(--docs-rail-max)); + gap: var(--docs-grid-gap); + justify-content: center; align-items: start; } @@ -1396,14 +1796,11 @@ body.home-body { } .docs-sidebar { + width: 100%; border: 1px solid var(--line-soft); border-radius: var(--radius); - background: - linear-gradient(180deg, var(--line-inner-soft), var(--color-transparent)), - var(--panel-soft); - box-shadow: - 0 20px 66px var(--shadow-soft), - inset 0 1px 0 var(--line-inner-soft); + background: var(--panel-soft); + box-shadow: var(--elev-1); } .docs-sidebar-head { @@ -1417,7 +1814,8 @@ body.home-body { font-weight: 700; } -.docs-sidebar-head svg { +.docs-sidebar-icon, +.docs-sidebar-icon svg { width: 18px; height: 18px; color: var(--accent); @@ -1433,7 +1831,7 @@ body.home-body { display: block; padding: 10px 11px; border: 1px solid var(--color-transparent); - border-radius: 7px; + border-radius: var(--control-radius); color: var(--text-soft); text-decoration: none; font-size: 0.88rem; @@ -1453,7 +1851,7 @@ body.home-body { .docs-hero { position: relative; - min-height: 220px; + min-height: 184px; display: grid; align-content: end; padding: clamp(26px, 4vw, 42px); @@ -1461,12 +1859,9 @@ body.home-body { border-radius: var(--radius); overflow: hidden; background: - radial-gradient(circle at 12% 8%, var(--accent-surface), var(--color-transparent) 36%), - radial-gradient(circle at 88% 18%, var(--spotlight-b), var(--color-transparent) 42%), - linear-gradient(135deg, var(--surface-elevated-start), var(--surface-elevated-end)); - box-shadow: - 0 28px 86px var(--shadow-elevated), - inset 0 1px 0 var(--line-inner); + radial-gradient(circle at 12% 8%, var(--accent-surface), var(--color-transparent) 46%), + var(--panel-soft); + box-shadow: var(--elev-1); } .docs-hero::before { @@ -1474,7 +1869,7 @@ body.home-body { position: absolute; inset: 0; pointer-events: none; - opacity: 0.42; + opacity: 0.14; background: linear-gradient(90deg, var(--page-grid-x) 1px, var(--color-transparent) 1px) 0 0 / 72px 72px, linear-gradient(180deg, var(--page-grid-y) 1px, var(--color-transparent) 1px) 0 0 / 72px 72px; @@ -1489,20 +1884,20 @@ body.home-body { .docs-eyebrow { margin: 0 0 16px; color: var(--accent); - font-family: var(--font-mono); - font-size: 0.75rem; - font-weight: 700; - letter-spacing: 0.04em; - text-transform: uppercase; + font-family: var(--font-sans); + font-size: var(--text-sm); + font-weight: 650; + letter-spacing: 0; + text-transform: none; } .docs-hero h1 { max-width: 720px; margin: 0; color: var(--text); - font-size: clamp(2.4rem, 5vw, 4.6rem); - line-height: 1.02; - letter-spacing: 0; + font-size: clamp(2.25rem, 4vw, 3.8rem); + line-height: 1.04; + letter-spacing: -0.025em; } .docs-hero p:not(.docs-eyebrow) { @@ -1635,10 +2030,14 @@ body.home-body { background: var(--accent-surface); } -.docs-callout-warning, +.docs-callout-warning { + border-left-color: var(--status-warning-line); + background: var(--status-warning-surface); +} + .docs-callout-caution { - border-left-color: var(--accent-warm); - background: var(--accent-warm-surface); + border-left-color: var(--status-danger-line); + background: var(--status-danger-surface); } .docs-callout-important { @@ -1663,6 +2062,12 @@ body.home-body { pointer-events: none; } +.docs-search-icon svg { + display: block; + width: 100%; + height: 100%; +} + .docs-search-input { width: 100%; padding: 8px 10px 8px 30px; @@ -1676,7 +2081,6 @@ body.home-body { } .docs-search-input:focus { - outline: none; border-color: var(--accent-line); } @@ -1696,11 +2100,11 @@ body.home-body { .docs-toc-mobile summary { cursor: pointer; - font-family: var(--font-mono); - font-size: 0.72rem; - font-weight: 700; - letter-spacing: 0.08em; - text-transform: uppercase; + font-family: var(--font-sans); + font-size: var(--text-sm); + font-weight: 650; + letter-spacing: 0; + text-transform: none; color: var(--dim); } @@ -1785,6 +2189,8 @@ body.home-body { } .docs-toc { + width: min(100%, 220px); + justify-self: end; padding: 4px 0; } @@ -1792,11 +2198,11 @@ body.home-body { display: block; margin-bottom: 12px; color: var(--dim); - font-family: var(--font-mono); - font-size: 0.72rem; - font-weight: 700; - letter-spacing: 0.04em; - text-transform: uppercase; + font-family: var(--font-sans); + font-size: var(--text-xs); + font-weight: 650; + letter-spacing: 0.01em; + text-transform: none; } .docs-toc nav { @@ -1846,7 +2252,8 @@ body.home-body { background: var(--accent-surface); } -.docs-page-card svg { +.docs-page-icon, +.docs-page-icon svg { width: 18px; height: 18px; flex: 0 0 auto; @@ -2244,7 +2651,7 @@ body.home-body { position: absolute; top: 22px; right: 24px; - z-index: var(--z-base); + z-index: var(--z-elevated); color: var(--accent); font-family: var(--font-mono); font-size: 0.78rem; @@ -2278,6 +2685,8 @@ body.home-body { .value-card h3, .value-card p { + position: relative; + z-index: var(--z-elevated); text-shadow: none; } @@ -2398,11 +2807,8 @@ body.home-body { padding: 26px 24px; border: 1px solid var(--line-soft); border-radius: var(--radius); - background: - linear-gradient(180deg, var(--surface-check-start), var(--surface-check-end)); - box-shadow: - inset 0 1px 0 var(--line-inner), - 0 22px 70px var(--shadow-soft); + background: var(--panel-soft); + box-shadow: none; } .workflow-checks::before, @@ -2988,6 +3394,8 @@ tr:last-child td { .landing-example-card h3, .landing-example-card p { + position: relative; + z-index: var(--z-elevated); text-shadow: none; } @@ -3024,9 +3432,8 @@ tr:last-child td { padding: 6px; border: 1px solid var(--line-soft); border-radius: var(--radius); - background: - linear-gradient(180deg, var(--surface-button), var(--panel-soft)); - box-shadow: inset 0 1px 0 var(--line-inner-soft); + background: var(--panel-soft); + box-shadow: none; } .example-tabs button { @@ -3111,10 +3518,7 @@ tr:last-child td { .tabbar button[aria-selected="true"], .model-tabs button[aria-selected="true"] { - box-shadow: - inset 0 1px 0 var(--line-inner-soft), - inset 0 -2px 0 var(--accent), - 0 8px 18px var(--shadow-soft); + box-shadow: inset 0 -2px 0 var(--accent); } .tool-tabs button[aria-selected="true"], @@ -3352,13 +3756,16 @@ tr:last-child td { .step-num { display: inline-flex; - width: 28px; - height: 28px; + width: auto; + min-width: 28px; + height: 24px; align-items: center; - justify-content: center; + justify-content: flex-start; margin-bottom: 22px; - border: 1px solid var(--line); - border-radius: 50%; + padding-left: 8px; + border: 0; + border-left: 2px solid var(--accent-line); + border-radius: 0; color: var(--accent); font-family: var(--font-mono); font-size: 0.82rem; @@ -3366,7 +3773,14 @@ tr:last-child td { .footer { border-top: 1px solid var(--line-soft); - background: var(--surface-footer); + background: + radial-gradient(circle at 50% 0%, var(--page-glow), var(--color-transparent) 30rem), + linear-gradient( + 180deg, + color-mix(in oklch, var(--accent-line) 28%, transparent), + var(--color-transparent) 96px + ), + var(--surface-footer); } .footer-inner { @@ -3413,36 +3827,21 @@ tr:last-child td { justify-content: center; align-items: center; padding: 5px 0 7px; - border-bottom: 1px solid var(--line-soft); + border-bottom: 1px solid transparent; color: var(--muted); font-size: 0.9rem; } -@media (max-width: 1100px) { - .hero-status, - .hero-status-compact { - justify-content: flex-start; - } +.footer-links a:hover, +.footer-links a:focus-visible { + border-bottom-color: var(--accent-line); + color: var(--text); +} +@media (max-width: 1199px) { .docs-shell { - width: min(100% - var(--site-gutter), var(--max)); - margin: 0 auto; - padding-inline: 0; - grid-template-columns: minmax(0, 1fr); - } - - .docs-sidebar, - .docs-toc { - position: static; - max-height: none; - } - - .docs-sidebar { - order: -1; - } - - .docs-nav { - grid-template-columns: repeat(2, minmax(0, 1fr)); + grid-template-columns: minmax(220px, 260px) minmax(0, 1fr); + gap: clamp(28px, 3.5vw, 44px); } .docs-toc { @@ -3452,6 +3851,13 @@ tr:last-child td { .docs-toc-mobile { display: block; } +} + +@media (max-width: 1100px) { + .hero-status, + .hero-status-compact { + justify-content: flex-start; + } .landing-example-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); @@ -3528,6 +3934,28 @@ tr:last-child td { } @media (max-width: 900px) { + .docs-shell { + grid-template-columns: minmax(0, 1fr); + } + + .docs-sidebar, + .docs-toc { + position: static; + max-height: none; + } + + .docs-sidebar { + order: -1; + max-height: min(420px, 48dvh); + overflow: auto; + overscroll-behavior: contain; + scrollbar-gutter: stable; + } + + .docs-nav { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + .steps { grid-template-columns: repeat(2, minmax(0, 1fr)); } @@ -3611,37 +4039,54 @@ tr:last-child td { } } +@media (min-width: 841px) and (max-width: 960px) { + .language-toggle { + width: 48px; + min-width: 48px; + padding-inline: 0; + } + + .language-toggle [data-language-label] { + display: none; + } + + .language-toggle .language-short { + display: inline; + } +} + @media (max-width: 840px) { .nav { - gap: 10px; + min-height: 60px; + grid-template-columns: minmax(0, 1fr) auto; + gap: 14px; } - .nav-links { - gap: 4px; + .nav-primary, + .nav-utilities > :not(.nav-menu-toggle) { + display: none; } - .theme-toggle, - .language-toggle, - .nav-link { + .nav-menu-toggle { + display: inline-flex; width: 44px; min-width: 44px; + min-height: 44px; padding: 0; } - .nav-link span, - .language-toggle [data-language-label], - .theme-toggle [data-theme-label] { - display: none; + .nav-drawer-backdrop { + padding-top: 60px; } - .language-short { - display: inline; + .nav-drawer { + width: min(100%, 480px); } } @media (max-width: 720px) { :root { - --site-gutter: 28px; + --page-pad-inline: 14px; --layout-gap: 14px; } @@ -3750,7 +4195,7 @@ tr:last-child td { .hero-field::before { inset: 0 -24% 20%; - border-radius: 26px; + border-radius: var(--radius-dialog); background-size: 56px 56px, 56px 56px, auto; } @@ -3931,30 +4376,6 @@ tr:last-child td { } @media (max-width: 420px) { - :root { - --site-gutter: 12px; - } - - .nav { - gap: 4px; - } - - .site-header .brand { - gap: 0; - } - - .site-header .brand span:last-child { - display: none; - } - - .nav-link-disabled { - display: none; - } - - .home-body .nav-source { - display: none; - } - .hero h1 { font-size: 2.72rem; } @@ -3970,6 +4391,13 @@ tr:last-child td { } } +@media (max-width: 340px) { + .brand { + gap: 9px; + font-size: 1rem; + } +} + @media (prefers-reduced-motion: reduce) { *, *::before, @@ -4003,8 +4431,9 @@ tr:last-child td { /* ========================================================================= Playground page (/playground) - Three-column IDE: source editor | compile output | provenance rail. - No body padding-right here (the rail is a column, not a fixed overlay). + The global header keeps the shared site frame. The compiler workbench uses + a wider, centred studio frame and becomes truly edge-to-edge only in the + explicit focus mode. ========================================================================= */ .playground-body { @@ -4012,12 +4441,39 @@ tr:last-child td { display: flex; flex-direction: column; overflow: hidden; + background: var(--bg); } .playground-body .site-header { flex-shrink: 0; } +.pg-stage { + flex: 1 1 auto; + display: flex; + min-width: 0; + min-height: 0; + padding: 12px var(--page-pad-inline) 14px; +} + +.pg-studio-frame { + position: relative; + flex: 1 1 auto; + display: flex; + flex-direction: column; + width: min(100%, var(--workbench-max)); + max-width: var(--workbench-max); + min-width: 0; + min-height: 0; + margin-inline: auto; + overflow: hidden; + border: 1px solid var(--line-soft); + border-radius: var(--radius-panel); + background: var(--panel-strong); + box-shadow: var(--elev-1), inset 0 1px 0 var(--line-inner-soft); + view-transition-name: playground-studio; +} + .pg-toolbar { position: relative; z-index: var(--z-sticky); @@ -4027,7 +4483,7 @@ tr:last-child td { align-items: center; column-gap: 18px; min-height: 52px; - padding: 0 24px; + padding: 0 14px; border-bottom: 1px solid var(--line-soft); background: var(--surface-header); backdrop-filter: blur(18px); @@ -4036,16 +4492,54 @@ tr:last-child td { .pg-toolbar-title { min-width: 0; margin: 0; - font-family: var(--font-mono); - font-size: 0.78rem; - font-weight: 600; - letter-spacing: 0.04em; - color: var(--dim); + display: inline-flex; + align-items: center; + gap: 9px; + font-family: var(--font-sans); + font-size: 0.82rem; + font-weight: 700; + letter-spacing: 0; + color: var(--text-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.pg-toolbar-context { + min-width: 0; + display: flex; + align-items: center; + gap: 9px; +} + +.pg-target-chip, +.pg-save-state { + flex: 0 0 auto; + font-family: var(--font-mono); + font-size: var(--text-micro); + line-height: 1; +} + +.pg-target-chip { + padding: 4px 6px; + border: 1px solid var(--line-soft); + border-radius: 4px; + color: var(--accent-strong); + background: var(--accent-surface); +} + +.pg-save-state { + color: var(--dim); +} + +.pg-save-state[data-state="dirty"] { + color: var(--substrate-accent-dim); +} + +.pg-save-state[data-state="unavailable"] { + color: var(--boundary-text); +} + .pg-toolbar-actions { display: flex; align-items: center; @@ -4056,21 +4550,113 @@ tr:last-child td { .pg-action-btn { flex: 0 0 auto; - min-height: 36px; - padding: 5px 12px; - border: 1px solid var(--accent-line); - border-radius: 6px; - background: var(--accent-surface); - color: var(--accent-strong); + min-height: var(--control-height); + padding: 5px 13px; + border: 1px solid var(--control-border); + border-radius: var(--control-radius); + background: var(--control-surface); + color: var(--control-text); font-size: 0.78rem; font-weight: 600; cursor: pointer; touch-action: manipulation; + box-shadow: inset 0 1px 0 var(--line-inner-soft); } .pg-action-btn:hover { - background: var(--accent); - color: var(--primary-contrast); + border-color: var(--control-border-hover); + background: var(--control-surface-hover); + color: var(--control-text-hover); + transform: translateY(-1px); +} + +.pg-action-btn:active { + transform: translateY(1px); +} + +.pg-action-btn:focus-visible { + outline: var(--focus-ring-width) solid var(--accent); + outline-offset: var(--focus-ring-offset); +} + +.pg-focus-toggle { + display: inline-flex; + align-items: center; + gap: 7px; + border-color: var(--control-border); + background: var(--control-surface); + color: var(--control-text); +} + +.pg-guide-trigger { + display: inline-flex; + align-items: center; + gap: 6px; + border-color: var(--control-border); + background: var(--control-surface); + color: var(--control-text); +} + +.pg-guide-trigger svg, +.pg-worker-retry svg { + width: 14px; + height: 14px; +} + +.pg-focus-toggle:hover { + border-color: var(--control-border-hover); + background: var(--control-surface-hover); + color: var(--control-text-hover); +} + +.pg-focus-icon { + display: inline-flex; + width: 15px; + height: 15px; + flex: 0 0 auto; +} + +.pg-focus-icon svg { + display: block; + width: 100%; + height: 100%; +} + +.pg-focus-icon-exit { + display: none; +} + +:root[data-playground-focus="true"] .pg-focus-icon-enter { + display: none; +} + +:root[data-playground-focus="true"] .pg-focus-icon-exit { + display: inline-flex; +} + +@media (min-width: 820px) { + :root[data-playground-focus="true"] .playground-body .site-header { + display: none; + } + + :root[data-playground-focus="true"] .pg-stage { + padding: 0; + } + + :root[data-playground-focus="true"] .pg-studio-frame { + width: 100%; + max-width: none; + border: 0; + border-radius: 0; + box-shadow: none; + } +} + +::view-transition-old(playground-studio), +::view-transition-new(playground-studio) { + animation-duration: 180ms; + animation-timing-function: var(--ease-quick); + mix-blend-mode: normal; } .pg-example-menu { @@ -4107,6 +4693,13 @@ tr:last-child td { transition: transform var(--duration-fast) var(--ease-standard); } +.pg-example-menu-chevron svg, +.pg-file-delete-icon svg { + display: block; + width: 100%; + height: 100%; +} + .pg-example-menu[open] .pg-example-menu-chevron { transform: rotate(180deg); } @@ -4134,23 +4727,48 @@ tr:last-child td { display: inline-flex; align-items: center; gap: 6px; + min-width: 104px; + border-color: var(--color-transparent); + background: var(--control-primary); + color: var(--control-primary-text); + box-shadow: inset 0 1px 0 color-mix(in oklch, white 24%, transparent); +} + +.pg-compile-btn:hover { + border-color: var(--color-transparent); + background: var(--control-primary-hover); + color: var(--control-primary-text); } .pg-compile-icon { - display: block; + display: inline-flex; width: 14px; height: 14px; flex: 0 0 auto; - stroke-width: 2.25; transform: translateY(-1px); } +.pg-compile-icon svg { + display: block; + width: 100%; + height: 100%; +} + +.pg-compile-spinner { + display: none; +} + .pg-compile-btn[data-state="compiling"] { cursor: progress; - opacity: 0.85; + opacity: 0.78; +} + +.pg-compile-btn[data-state="compiling"] .pg-compile-play { + display: none; } -.pg-compile-btn[data-state="compiling"] .pg-compile-icon { +.pg-compile-btn[data-state="compiling"] .pg-compile-spinner { + display: inline-flex; animation: pg-compile-spin 0.8s linear infinite; } @@ -4159,19 +4777,23 @@ tr:last-child td { } .pg-compile-btn[data-state="ready"] { - border-color: var(--accent); - background: var(--accent-surface); - color: var(--accent-strong); + border-color: var(--color-transparent); + background: var(--control-primary); + color: var(--control-primary-text); } .pg-compile-btn[data-state="error"] { - border-color: var(--boundary-rule); - background: var(--status-source-surface); - color: var(--boundary-text); + border-color: var(--color-transparent); + background: var(--control-primary); + color: var(--control-primary-text); } @media (prefers-reduced-motion: reduce) { - .pg-compile-btn[data-state="compiling"] .pg-compile-icon { + .pg-compile-btn[data-state="compiling"] .pg-compile-spinner { + animation: none; + } + + .pg-output-panel[data-reveal="true"] .pg-flow-connector { animation: none; } } @@ -4188,7 +4810,7 @@ tr:last-child td { .pg-example-lesson span { font-family: var(--font-mono); - font-size: 0.58rem; + font-size: var(--text-micro); font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; @@ -4221,20 +4843,20 @@ tr:last-child td { .pg-mobile-switch button { flex: 1 1 0; - min-height: 40px; - border: 1px solid var(--line-soft); - border-radius: 6px; - background: var(--surface-button); - color: var(--muted); + min-height: var(--control-height); + border: 1px solid var(--control-border); + border-radius: var(--control-radius); + background: var(--control-surface); + color: var(--control-text); font-size: 0.74rem; - font-weight: 650; + font-weight: 600; cursor: pointer; } .pg-mobile-switch button.active { border-color: var(--accent-line); - background: var(--accent-surface); - color: var(--accent-strong); + background: var(--control-selected-surface); + color: var(--control-selected-text); } .pg-main { @@ -4351,7 +4973,7 @@ tr:last-child td { white-space: nowrap; color: var(--text-soft); font-family: var(--font-mono); - font-size: 0.66rem; + font-size: var(--text-micro); letter-spacing: 0; text-transform: none; } @@ -4362,10 +4984,10 @@ tr:last-child td { gap: 5px; min-height: 26px; padding: 3px 7px; - border: 1px solid var(--accent-line); - border-radius: 6px; - background: var(--accent-surface); - color: var(--accent-strong); + border: 1px solid var(--control-border); + border-radius: var(--control-radius); + background: var(--control-surface); + color: var(--control-text); font-family: var(--font-sans); font-size: 0.68rem; font-weight: 700; @@ -4374,6 +4996,12 @@ tr:last-child td { cursor: pointer; } +.pg-entry-action:hover:not(:disabled) { + border-color: var(--control-border-hover); + background: var(--control-surface-hover); + color: var(--control-text-hover); +} + .pg-entry-action:disabled { border-color: var(--line-soft); background: var(--panel-soft); @@ -4384,7 +5012,6 @@ tr:last-child td { .pg-entry-action svg { width: 13px; height: 13px; - stroke-width: 2.4; } .pg-source-body { @@ -4427,7 +5054,7 @@ tr:last-child td { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - font-size: 0.64rem; + font-size: var(--text-micro); } .pg-workspace-actions { @@ -4442,27 +5069,28 @@ tr:last-child td { display: inline-flex; align-items: center; justify-content: center; - width: 30px; - height: 30px; - border: 1px solid var(--line-soft); - border-radius: 6px; - background: var(--surface-button); - color: var(--muted); + width: var(--control-height-dense); + height: var(--control-height-dense); + border: 1px solid var(--control-border); + border-radius: var(--control-radius); + background: var(--control-surface); + color: var(--control-text); cursor: pointer; } -.pg-icon-btn:hover, -.pg-icon-btn:focus-visible { - border-color: var(--accent-line); - background: var(--accent-surface); - color: var(--accent-strong); - outline: none; +.pg-icon-btn:hover { + border-color: var(--control-border-hover); + background: var(--control-surface-hover); + color: var(--control-text-hover); +} + +.pg-icon-btn:active { + transform: translateY(1px); } .pg-icon-btn svg { width: 15px; height: 15px; - stroke-width: 2.2; } .pg-file-tree { @@ -4506,7 +5134,6 @@ tr:last-child td { .pg-file-item:focus-visible { border-color: var(--accent-line); color: var(--text); - outline: none; } .pg-file-row.active .pg-file-item { @@ -4535,18 +5162,18 @@ tr:last-child td { border-color: var(--line-soft); background: var(--surface-button); color: var(--boundary-text); - outline: none; } .pg-file-delete:disabled { - opacity: 0.35; + border-color: var(--line-soft); + background: var(--panel-soft); + color: var(--line-number); cursor: not-allowed; } .pg-file-delete-icon { width: 14px; height: 14px; - stroke-width: 2.25; } .pg-file-name { @@ -4562,7 +5189,7 @@ tr:last-child td { border-radius: 4px; background: var(--panel-chip); color: var(--substrate-accent); - font-size: 0.58rem; + font-size: var(--text-micro); font-weight: 700; text-transform: uppercase; } @@ -4581,7 +5208,7 @@ tr:last-child td { .pg-example-group h2 { margin: 0; color: var(--dim); - font-size: 0.62rem; + font-size: var(--text-micro); font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; @@ -4597,27 +5224,27 @@ tr:last-child td { width: 100%; min-height: 30px; padding: 5px 8px; - border: 1px solid var(--line-soft); - border-radius: 6px; - background: var(--surface-button); - color: var(--muted); + border: 1px solid var(--control-border); + border-radius: var(--control-radius); + background: transparent; + color: var(--control-text); text-align: left; font-size: 0.72rem; - font-weight: 650; + font-weight: 600; cursor: pointer; } .pg-example-item:hover, .pg-example-item:focus-visible { - border-color: var(--accent-line); - color: var(--text); - outline: none; + border-color: var(--control-border-hover); + background: var(--control-surface-hover); + color: var(--control-text-hover); } .pg-example-item.active { - border-color: var(--accent); - background: var(--accent-surface); - color: var(--accent-strong); + border-color: var(--accent-line); + background: var(--control-selected-surface); + color: var(--control-selected-text); } .pg-tabs { @@ -4634,11 +5261,11 @@ tr:last-child td { border: none; background: transparent; color: var(--muted); - font-family: var(--font-mono); - font-size: 0.7rem; + font-family: var(--font-sans); + font-size: 0.76rem; font-weight: 600; - letter-spacing: 0.06em; - text-transform: uppercase; + letter-spacing: 0; + text-transform: none; cursor: pointer; touch-action: manipulation; white-space: nowrap; @@ -4674,11 +5301,11 @@ tr:last-child td { height: 16px; margin-left: 5px; padding: 0 5px; - border-radius: 999px; + border-radius: var(--radius-data); background: var(--panel-chip); color: var(--substrate-accent); font-family: var(--font-mono); - font-size: 0.6rem; + font-size: var(--text-micro); font-weight: 600; letter-spacing: 0; line-height: 1; @@ -4717,19 +5344,6 @@ tr:last-child td { opacity: 0.8; } -.pg-editor::after { - content: ""; - position: absolute; - top: 0; - right: 0; - bottom: 0; - z-index: var(--z-elevated); - width: 28px; - pointer-events: none; - background: linear-gradient(90deg, var(--color-transparent), var(--panel-strong)); - opacity: 0.76; -} - .pg-editor:focus-within { box-shadow: inset 0 0 0 2px var(--accent); } @@ -4761,12 +5375,12 @@ tr:last-child td { display: block; flex: 0 0 auto; /* Same font-size / line-height as .pg-textarea below. */ - height: calc(0.82rem * 1.6); + height: calc(0.875rem * 1.55); padding-right: 12px; text-align: right; font-family: var(--font-mono); - font-size: 0.82rem; - line-height: 1.6; + font-size: 0.875rem; + line-height: 1.55; color: var(--line-number); cursor: pointer; transition: background-color 160ms ease, color 160ms ease; @@ -4813,8 +5427,8 @@ tr:last-child td { padding: 14px 16px 14px 60px; border: 0; font-family: var(--font-mono); - font-size: 0.82rem; - line-height: 1.6; + font-size: 0.875rem; + line-height: 1.55; letter-spacing: 0; tab-size: 2; white-space: pre; @@ -4967,6 +5581,51 @@ tr:last-child td { overflow: hidden; } +.pg-output-stale { + position: absolute; + z-index: var(--z-elevated); + inset: 0 0 auto; + height: 48px; + display: flex; + align-items: center; + padding: 8px 16px; + border-bottom: 1px solid var(--status-stable); + background: var(--status-stable-surface); + color: var(--text-soft); + font-size: 0.72rem; + line-height: 1.35; + gap: 10px; +} + +.pg-output-stale span { + flex: 1 1 auto; +} + +.pg-output-stale button { + flex: 0 0 auto; + min-height: 30px; + padding: 4px 8px; + border: 1px solid var(--status-stable); + border-radius: var(--control-radius); + background: var(--control-surface); + color: var(--control-text); + font-size: 0.68rem; + font-weight: 600; + cursor: pointer; +} + +.pg-output-stale[hidden] { + display: none; +} + +.pg-output-stale:not([hidden]) ~ .pg-output-panel { + top: 48px; +} + +.pg-output-panel[data-stale="true"] { + opacity: 0.68; +} + .pg-output-panel { position: absolute; inset: 0; @@ -5020,6 +5679,172 @@ tr:last-child td { font-size: 0.82rem; } +/* ---- Cell Flow ---- */ + +.pg-cell-flow { + min-width: 460px; + display: flex; + flex-direction: column; +} + +.pg-flow-row { + display: grid; + grid-template-columns: minmax(100px, 1fr) 22px minmax(140px, 0.9fr) 22px minmax(100px, 1fr); + align-items: stretch; + min-height: 112px; + padding: 14px 0; + border-bottom: 1px solid var(--line-soft); +} + +.pg-flow-row:first-child { + padding-top: 2px; +} + +.pg-flow-row:last-child { + border-bottom: 0; +} + +.pg-flow-side { + min-width: 0; + display: flex; + flex-direction: column; + justify-content: center; + gap: 6px; +} + +.pg-flow-side-label, +.pg-flow-action-kicker { + font-family: var(--font-sans); + font-size: var(--text-xs); + font-weight: 600; + letter-spacing: 0; + text-transform: none; + color: var(--dim); +} + +.pg-flow-cell, +.pg-flow-action, +.pg-action-head, +.pg-type-head { + appearance: none; + width: 100%; + border: 0; + background: var(--color-transparent); + color: inherit; + text-align: left; + cursor: pointer; +} + +.pg-flow-cell { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 8px; + align-items: center; + min-height: 34px; + padding: 6px 8px; + border: 1px solid var(--line-soft); + border-radius: var(--radius-data); + background: var(--panel-soft); + font-size: 0.7rem; +} + +.pg-flow-cell:hover, +.pg-flow-cell:focus-visible { + border-color: var(--accent-line); + background: var(--accent-surface); +} + +.pg-flow-cell span { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: var(--muted); +} + +.pg-flow-cell code { + color: var(--accent-strong); + font-family: var(--font-mono); + font-size: 0.68rem; +} + +.pg-flow-connector { + align-self: center; + width: 100%; + height: 1px; + background: var(--line); + transform-origin: left; +} + +.pg-output-panel[data-reveal="true"] .pg-flow-connector { + animation: pg-flow-reveal 220ms var(--ease-quick) both; + animation-delay: calc(var(--flow-index, 0) * 45ms); +} + +@keyframes pg-flow-reveal { + from { transform: scaleX(0); opacity: 0; } + to { transform: scaleX(1); opacity: 1; } +} + +.pg-flow-action { + align-self: center; + display: flex; + flex-direction: column; + justify-content: center; + gap: 7px; + min-height: 82px; + padding: 11px 12px; + border: 1px solid var(--line-soft); + border-radius: var(--radius-control); + background: var(--panel-soft); +} + +.pg-flow-action:hover, +.pg-flow-action:focus-visible, +.pg-flow-row.is-selected .pg-flow-action { + border-color: var(--accent-line); + background: var(--accent-surface); + box-shadow: inset 3px 0 0 var(--accent); +} + +.pg-flow-action code { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + font-family: var(--font-mono); + font-size: 0.78rem; + font-weight: 700; + color: var(--text); +} + +.pg-flow-action > span:last-child { + color: var(--substrate-accent-dim); + font-family: var(--font-mono); + font-size: var(--text-micro); +} + +.pg-flow-empty { + color: var(--dim); + font-size: 0.7rem; +} + +.pg-flow-empty-state { + max-width: 420px; + margin: 32px auto; + text-align: center; +} + +.pg-flow-empty-state strong { + color: var(--text); +} + +.pg-flow-empty-state p { + margin: 8px 0 0; + color: var(--muted); + font-size: 0.78rem; + line-height: 1.55; +} + /* ---- Action list ---- */ .pg-action-list, @@ -5029,14 +5854,19 @@ tr:last-child td { padding: 0; display: flex; flex-direction: column; - gap: 10px; + gap: 0; + border-top: 1px solid var(--line-soft); } .pg-action-item { - padding: 12px; - border: 1px solid var(--line-soft); - border-radius: 8px; - background: var(--panel-soft); + padding: 12px 4px; + border-bottom: 1px solid var(--line-soft); +} + +.pg-action-item.is-selected, +.pg-type-item.is-selected { + box-shadow: inset 3px 0 0 var(--accent); + background: var(--accent-surface); } .pg-action-head { @@ -5045,6 +5875,7 @@ tr:last-child td { justify-content: space-between; gap: 8px; margin-bottom: 8px; + padding: 0; } .pg-action-name { @@ -5059,7 +5890,7 @@ tr:last-child td { padding: 2px 8px; border-radius: 4px; font-family: var(--font-mono); - font-size: 0.64rem; + font-size: var(--text-micro); font-weight: 600; letter-spacing: 0.04em; } @@ -5157,10 +5988,8 @@ tr:last-child td { /* ---- Type list ---- */ .pg-type-item { - padding: 10px 12px; - border: 1px solid var(--line-soft); - border-radius: 8px; - background: var(--panel-soft); + padding: 10px 4px; + border-bottom: 1px solid var(--line-soft); } .pg-type-head { @@ -5169,6 +5998,7 @@ tr:last-child td { justify-content: space-between; gap: 8px; margin-bottom: 6px; + padding: 0; } .pg-type-name { @@ -5180,7 +6010,7 @@ tr:last-child td { .pg-type-kind { font-family: var(--font-mono); - font-size: 0.64rem; + font-size: var(--text-micro); color: var(--accent); text-transform: uppercase; letter-spacing: 0.06em; @@ -5197,14 +6027,14 @@ tr:last-child td { border-radius: 3px; background: var(--panel-chip); font-family: var(--font-mono); - font-size: 0.64rem; + font-size: var(--text-micro); color: var(--text-soft); } .pg-type-size { margin-top: 5px; font-family: var(--font-mono); - font-size: 0.66rem; + font-size: var(--text-micro); color: var(--substrate-accent-dim); } @@ -5305,7 +6135,7 @@ tr:last-child td { border-radius: 999px; color: var(--substrate-dim); font-family: var(--font-mono); - font-size: 0.64rem; + font-size: var(--text-micro); font-weight: 700; line-height: 1; } @@ -5386,7 +6216,7 @@ tr:last-child td { display: block; margin: 0 0 8px; font-family: var(--font-mono); - font-size: 0.64rem; + font-size: var(--text-micro); font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; @@ -5462,7 +6292,7 @@ tr:last-child td { border: 1px solid var(--color-transparent); border-radius: 4px; background: var(--color-transparent); - font-size: 0.62rem; + font-size: var(--text-micro); } .pg-rail-action-detail { @@ -5478,7 +6308,7 @@ tr:last-child td { grid-template-columns: 54px minmax(0, 1fr); gap: 8px; font-family: var(--font-mono); - font-size: 0.66rem; + font-size: var(--text-micro); color: var(--substrate-dim); } @@ -5515,7 +6345,7 @@ tr:last-child td { .pg-rail-type-kind { font-family: var(--font-mono); - font-size: 0.62rem; + font-size: var(--text-micro); color: var(--substrate-dim); } @@ -5524,6 +6354,162 @@ tr:last-child td { font-size: 0.7rem; } +.pg-inspector { + display: flex; + flex-direction: column; + gap: 12px; + padding-top: 2px; +} + +.pg-inspector-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + color: var(--substrate-dim); + font-family: var(--font-mono); + font-size: var(--text-micro); + letter-spacing: 0.07em; + text-transform: uppercase; +} + +.pg-inspector-head button, +.pg-inspector-source, +.pg-worker-retry, +.pg-worker-failure button { + appearance: none; + border: 1px solid var(--control-border); + border-radius: var(--control-radius); + background: var(--control-surface); + color: var(--control-text); + cursor: pointer; +} + +.pg-inspector-head button { + padding: 4px 6px; + font: inherit; + letter-spacing: 0; + text-transform: none; +} + +.pg-inspector-title { + color: var(--substrate-text); + font-family: var(--font-mono); + font-size: 0.82rem; + font-weight: 700; + overflow-wrap: anywhere; +} + +.pg-inspector-summary { + color: var(--substrate-text); + font-size: 0.82rem; + line-height: 1.4; +} + +.pg-inspector > p, +.pg-inspector-used p { + margin: 0; + color: var(--substrate-dim); + font-size: 0.7rem; + line-height: 1.5; +} + +.pg-inspector-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 1px; + margin: 0; + border: 1px solid var(--substrate-rule); + background: var(--substrate-rule); +} + +.pg-inspector-grid > div { + min-width: 0; + padding: 8px; + background: var(--substrate-bg); +} + +.pg-inspector-grid dt, +.pg-inspector-runtime > span, +.pg-inspector-used > span { + display: block; + margin-bottom: 4px; + color: var(--substrate-dim); + font-family: var(--font-mono); + font-size: var(--text-micro); + text-transform: uppercase; + letter-spacing: 0.06em; +} + +.pg-inspector-grid dd { + min-width: 0; + margin: 0; + color: var(--substrate-text); + font-family: var(--font-mono); + font-size: 0.68rem; + overflow-wrap: anywhere; +} + +.pg-inspector-runtime { + display: flex; + flex-wrap: wrap; + gap: 5px; +} + +.pg-inspector-runtime > span { + flex-basis: 100%; +} + +.pg-inspector-runtime code, +.pg-inspector-used code { + padding: 2px 5px; + border-radius: 3px; + background: var(--panel-chip); + color: var(--substrate-text); + font-family: var(--font-mono); + font-size: var(--text-micro); +} + +.pg-inspector-source { + min-height: 34px; + padding: 6px 9px; + font-size: 0.7rem; + font-weight: 600; +} + +.pg-inspector-source:hover, +.pg-inspector-source:focus-visible, +.pg-worker-retry:hover, +.pg-worker-retry:focus-visible, +.pg-worker-failure button:hover, +.pg-worker-failure button:focus-visible { + border-color: var(--control-border-hover); + background: var(--control-surface-hover); + color: var(--control-text-hover); +} + +.pg-rail-section[data-rail-view="loading"]:not([hidden]) { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; +} + +.pg-worker-retry { + width: 100%; + min-height: 36px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + padding: 7px 9px; + font-size: 0.7rem; +} + +.pg-worker-retry[hidden] { + display: none; +} + .pg-error-inline { color: var(--substrate-accent); font-family: var(--font-mono); @@ -5531,6 +6517,133 @@ tr:last-child td { word-break: break-word; } +.pg-worker-failure { + display: grid; + justify-items: center; + gap: 12px; +} + +.pg-worker-failure button { + min-height: 38px; + padding: 7px 11px; + font-size: 0.74rem; + font-weight: 600; +} + +/* ---- Optional guided lab ---- */ + +.pg-guide { + position: absolute; + z-index: var(--z-overlay); + left: 14px; + bottom: 44px; + width: min(360px, calc(100% - 28px)); + padding: 14px; + border: 1px solid var(--accent-line); + border-radius: 8px; + background: var(--panel-popover); + box-shadow: var(--elev-2); +} + +.pg-guide[hidden] { + display: none; +} + +.pg-guide-head, +.pg-guide-actions { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.pg-guide-progress { + color: var(--accent-strong); + font-family: var(--font-sans); + font-size: var(--text-xs); + font-weight: 650; + letter-spacing: 0; + text-transform: none; +} + +.pg-guide-close { + width: var(--control-height-dense); + height: var(--control-height-dense); + display: inline-flex; + align-items: center; + justify-content: center; + border: 0; + border-radius: var(--control-radius); + background: var(--color-transparent); + color: var(--muted); + cursor: pointer; +} + +.pg-guide-close:hover, +.pg-guide-close:focus-visible { + background: var(--surface-hover); + color: var(--text); +} + +.pg-guide-close svg { + width: 16px; + height: 16px; +} + +.pg-guide > strong { + display: block; + margin-top: 8px; + color: var(--text); + font-size: 0.94rem; +} + +.pg-guide > p { + margin: 7px 0 14px; + color: var(--muted); + font-size: 0.76rem; + line-height: 1.55; +} + +.pg-guide-actions { + justify-content: flex-end; +} + +.pg-guide-actions button { + min-height: var(--control-height); + padding: 6px 12px; + border: 1px solid var(--control-border); + border-radius: var(--control-radius); + background: var(--control-surface); + color: var(--control-text); + font-size: 0.72rem; + font-weight: 600; + cursor: pointer; +} + +.pg-guide-actions .pg-guide-next { + border-color: var(--color-transparent); + background: var(--control-primary); + color: var(--control-primary-text); +} + +.pg-guide-actions button:hover { + border-color: var(--control-border-hover); + background: var(--control-surface-hover); + color: var(--control-text-hover); +} + +.pg-guide-actions .pg-guide-next:hover { + border-color: var(--color-transparent); + background: var(--control-primary-hover); + color: var(--control-primary-text); +} + +:root[data-playground-guide-step="source"] .pg-source, +:root[data-playground-guide-step="flow"] .pg-output, +:root[data-playground-guide-step="inspector"] .pg-rail { + box-shadow: inset 0 0 0 2px var(--accent); +} + /* ---- Status bar ---- */ .pg-statusbar { @@ -5591,10 +6704,6 @@ tr:last-child td { The rail body switches to a horizontal 3-track layout to fit the wider, shorter footprint. */ @media (max-width: 1099px) { - .playground-body .pg-toolbar { - padding-left: 24px; - } - .pg-main { grid-template-columns: minmax(360px, var(--pg-source-col, 1fr)) @@ -5622,19 +6731,14 @@ tr:last-child td { } } -@media (min-width: 820px) and (max-width: 1320px) { - .pg-toolbar { - grid-template-columns: 1fr; - align-items: start; - row-gap: 8px; - min-height: 0; - padding-top: 8px; - padding-bottom: 8px; +@media (min-width: 820px) and (max-width: 1040px) { + .pg-focus-toggle { + width: 36px; + padding-inline: 9px; } - .pg-toolbar-actions { - flex-wrap: wrap; - justify-content: flex-start; + .pg-focus-label { + display: none; } } @@ -5644,6 +6748,17 @@ tr:last-child td { min-height: 100dvh; } + .pg-stage { + padding: 0; + } + + .pg-studio-frame { + width: 100%; + border: 0; + border-radius: 0; + box-shadow: none; + } + .pg-toolbar { grid-template-columns: 1fr; align-items: start; @@ -5659,6 +6774,18 @@ tr:last-child td { gap: 8px; } + .pg-toolbar-context { + width: 100%; + } + + .pg-save-state { + margin-left: auto; + } + + .pg-focus-toggle { + display: none; + } + .pg-example-menu { position: static; } @@ -5755,6 +6882,57 @@ tr:last-child td { .pg-output-body { min-height: min(340px, 44dvh); } + + .pg-output-stale { + height: 64px; + } + + .pg-output-stale:not([hidden]) ~ .pg-output-panel { + top: 64px; + } + + .pg-output-stale button { + min-height: 44px; + } + + .pg-example-item, + .pg-flow-cell { + min-height: 44px; + } + + .pg-cell-flow { + min-width: 0; + } + + .pg-flow-row { + grid-template-columns: 1fr; + gap: 8px; + min-height: 0; + } + + .pg-flow-connector { + justify-self: center; + width: 1px; + height: 18px; + transform-origin: top; + } + + .pg-output-panel[data-reveal="true"] .pg-flow-connector { + animation-name: pg-flow-reveal-y; + } + + @keyframes pg-flow-reveal-y { + from { transform: scaleY(0); opacity: 0; } + to { transform: scaleY(1); opacity: 1; } + } + + .pg-guide { + position: fixed; + left: 12px; + right: 12px; + bottom: 12px; + width: auto; + } } @media (max-width: 520px) { @@ -5770,6 +6948,10 @@ tr:last-child td { flex: 1 1 0; margin-left: 0; } + + .pg-save-state { + display: none; + } } /* ========================================================================= diff --git a/src/styles/registry.css b/src/styles/registry.css index 0ec9116..3a4f8c2 100644 --- a/src/styles/registry.css +++ b/src/styles/registry.css @@ -1,1828 +1,5920 @@ -.registry-home { - width: min(920px, calc(100% - var(--site-gutter))); +.registry-body { + background: var(--bg); +} + +.registry-body::before { + opacity: 0.32; +} + +.registry-app { + --registry-route-start: clamp(28px, 3.2vw, 42px); + --registry-rail: clamp(28px, 4.6vw, 64px); + --registry-grid-gap: clamp(16px, 2.2vw, 30px); + width: min(var(--max), calc(100% - var(--site-gutter))); margin: 0 auto; - padding: clamp(42px, 8vh, 82px) 0 88px; + padding: clamp(20px, 2.8vw, 34px) 0 80px; } -.registry-home-search { - min-height: min(720px, calc(100dvh - 56px)); - display: grid; - align-content: center; - justify-items: center; - gap: 18px; - text-align: center; +.registry-app-framed .registry-route { + padding-inline: var(--registry-rail); } -.registry-home-mark { - width: 56px; - height: 56px; - border: 1px solid var(--accent-line); - border-radius: var(--radius); - background: - linear-gradient(135deg, var(--accent-surface), transparent 54%), - var(--panel-strong); - box-shadow: 0 22px 80px var(--shadow-soft); +.registry-hero { position: relative; + overflow: clip; + border: 1px solid color-mix(in oklch, var(--line-soft) 82%, var(--accent-line)); + border-radius: clamp(18px, 2vw, 24px); + background: + radial-gradient(circle at 88% 0%, color-mix(in oklch, var(--accent) 7%, transparent), transparent 34%), + linear-gradient(135deg, color-mix(in oklch, var(--panel-strong) 96%, var(--accent-surface)), var(--panel-soft)); + box-shadow: inset 0 1px 0 color-mix(in oklch, var(--text) 4%, transparent); } -.registry-home-mark::before, -.registry-home-mark::after { +.registry-hero::before { content: ""; position: absolute; - inset: 12px; - border: 1px solid var(--accent); - border-radius: 5px; + inset: 0; + pointer-events: none; + background: + radial-gradient(ellipse 60% 80% at 100% 0%, color-mix(in oklch, var(--accent) 8%, transparent), transparent 60%), + repeating-linear-gradient(to right, var(--page-grid-x) 0 1px, transparent 1px 56px), + repeating-linear-gradient(to bottom, var(--page-grid-y) 0 1px, transparent 1px 56px); + -webkit-mask-image: linear-gradient(215deg, black 0%, transparent 62%); + mask-image: linear-gradient(215deg, black 0%, transparent 62%); + opacity: 0.55; } -.registry-home-mark::after { - inset: 22px; - background: var(--accent); +.registry-hero > * { + position: relative; + z-index: 1; } -.registry-home-search h1 { - margin: 0; - color: var(--text); - font-size: clamp(3.2rem, 8.5vw, 7.2rem); - line-height: 0.92; - letter-spacing: 0; - text-wrap: balance; +:root[data-registry-environment="testnet-sandbox"] .registry-hero { + border-color: color-mix(in oklch, var(--sandbox-line) 74%, var(--line-soft)); + background: + radial-gradient(circle at 84% 8%, color-mix(in oklch, var(--sandbox-marker) 10%, transparent), transparent 38%), + linear-gradient(135deg, color-mix(in oklch, var(--panel-strong) 95%, var(--sandbox-surface)), var(--panel-soft)); } -.registry-home-search > p { - max-width: 680px; - margin: 0; - color: var(--muted); - font-size: clamp(1rem, 2vw, 1.28rem); - line-height: 1.55; - text-wrap: pretty; +.registry-shell-bar { + display: grid; + grid-template-columns: repeat(12, minmax(0, 1fr)); + column-gap: var(--registry-grid-gap); + align-items: stretch; + padding: clamp(34px, 4.2vw, 58px) var(--registry-rail) clamp(27px, 3.4vw, 42px); } -.registry-home-search-box { - width: min(760px, 100%); - margin-top: 10px; - border: 1px solid var(--accent-line); - border-radius: 8px; - background: var(--panel-strong); - box-shadow: 0 14px 48px var(--shadow-elevated); - overflow: clip; +.registry-shell-bar-context-only { + grid-template-columns: auto; + justify-content: end; + min-height: 0; } -.registry-home-search-label { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; +.registry-environment-bar.registry-network-card { + display: grid; + grid-template-rows: auto auto minmax(0, 1fr) auto; + align-content: start; + justify-self: stretch; + gap: 10px; + width: 100%; + min-width: 0; + padding: 18px; + border: 1px solid color-mix(in oklch, var(--line-soft) 88%, var(--accent-line)); + border-radius: var(--radius-panel); + background: color-mix(in oklch, var(--panel-strong) 74%, transparent); + color: var(--muted); + box-shadow: inset 0 1px 0 color-mix(in oklch, var(--text) 3%, transparent); } -.registry-home-input-wrap { - display: grid; - grid-template-columns: 22px minmax(0, 1fr); - align-items: center; - gap: 12px; - min-height: 68px; - padding: 0 20px; - border-bottom: 1px solid var(--line-soft); - color: var(--dim); +.registry-hero:not(.registry-hero-no-header) .registry-network-card { + grid-column: 9 / -1; } -.registry-home-input-wrap svg { - width: 22px; - height: 22px; +.registry-network-label { + margin: 0; + color: var(--dim); + font-family: var(--font-mono); + font-size: 0.7rem; + font-weight: 600; + letter-spacing: 0.1em; + line-height: 1.4; + text-transform: uppercase; } -.registry-home-input-wrap input { - width: 100%; - min-width: 0; - border: 0; - outline: 0; - background: transparent; - color: var(--text); - font: inherit; - font-size: clamp(1rem, 2vw, 1.12rem); +.registry-network-heading { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; } -.registry-home-input-wrap input::placeholder { - color: var(--dim); - opacity: 1; +.registry-environment-identity { + display: inline-flex; + align-items: center; + gap: 10px; + min-width: 0; } -.registry-home-input-wrap:focus-within { - color: var(--accent); - box-shadow: inset 0 0 0 1px var(--accent-line); +.registry-environment-identity i { + width: 8px; + height: 8px; + flex: 0 0 auto; + border-radius: 999px; + background: var(--accent); + box-shadow: 0 0 0 5px color-mix(in oklch, var(--accent) 12%, transparent); } -.registry-home-results { - display: grid; - gap: 0; - text-align: left; +.registry-environment-bar strong { + color: var(--text); + font-size: 0.94rem; + font-weight: 600; + line-height: 1.35; } -.registry-home-result-head { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - min-height: 42px; - padding: 0 16px; - border-bottom: 1px solid var(--line-soft); +.registry-network-code { + flex: 0 0 auto; color: var(--dim); font-family: var(--font-mono); - font-size: 0.72rem; -} - -.registry-home-result-head a, -.registry-home-columns a { - color: var(--accent); - font-weight: 600; - text-decoration: none; + font-size: 0.7rem; + letter-spacing: 0.08em; } -.registry-home-result-list { - max-height: 274px; - overflow: auto; +.registry-network-body { + max-width: 32ch; + margin: 0; + color: var(--muted); + font-size: 0.8rem; + line-height: 1.6; } -.registry-home-result { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - gap: 14px; +.registry-network-switch { + display: inline-flex; align-items: center; - min-height: 76px; - padding: 14px 16px; - color: inherit; + justify-content: space-between; + gap: 10px; + padding-top: 12px; + border-top: 1px solid var(--line-soft); + color: var(--text-soft); + font-size: 0.78rem; + font-weight: 600; text-decoration: none; - border-bottom: 1px solid var(--line-soft); + transition: color var(--duration-fast) var(--ease-standard); } -.registry-home-result:last-child { - border-bottom: 0; +.registry-network-switch svg { + width: 15px; + height: 15px; + transition: transform var(--duration-fast) var(--ease-quick); } -.registry-home-result:hover, -.registry-home-result:focus-visible { - background: var(--accent-surface); - outline: 0; +.registry-network-switch:hover { + color: var(--text); } -.registry-home-result strong, -.registry-home-result small { - display: block; - min-width: 0; +.registry-network-switch:hover svg { + transform: translate(2px, -2px); } -.registry-home-result strong { - color: var(--text); - font-size: 1rem; - overflow-wrap: anywhere; +.registry-environment-bar.is-testnet { + border-color: var(--sandbox-line); + background: color-mix(in oklch, var(--panel-strong) 78%, var(--sandbox-surface)); } -.registry-home-result small { - margin-top: 4px; - color: var(--muted); - line-height: 1.35; +.registry-environment-bar.is-testnet .registry-environment-identity i { + background: var(--sandbox-marker); + box-shadow: 0 0 0 5px color-mix(in oklch, var(--sandbox-marker) 14%, transparent); } -.registry-home-result-meta { - display: flex; - flex-wrap: wrap; - justify-content: flex-end; - gap: 6px; +.registry-sandbox-expiry { + width: fit-content; + margin: 12px 0 0; + padding: 6px 9px; + border: 1px solid var(--sandbox-line); + border-radius: 7px; + background: var(--sandbox-surface); + color: var(--sandbox-text); + font-family: var(--font-mono); + font-size: 0.72rem; } -.registry-home-result-meta code { - padding: 5px 7px; - border: 1px solid var(--line-soft); - border-radius: 6px; - background: var(--panel-chip); - color: var(--text-soft); - font-size: 0.7rem; +.registry-topbar { + display: grid; + grid-template-columns: minmax(0, 1fr); + align-content: center; + gap: 22px; + min-width: 0; + padding: 2px 0; } -.registry-home-empty { - display: grid; - gap: 4px; - padding: 18px 16px; - color: var(--muted); +.registry-hero:not(.registry-hero-no-header) .registry-topbar { + grid-column: 1 / span 7; } -.registry-home-empty[hidden], -.registry-home-result[hidden] { - display: none; +.registry-topbar > div { + min-width: 0; } -.registry-home-empty strong { - color: var(--text); +.registry-topbar-copy { + display: grid; + align-content: center; + min-height: 0; } -.registry-home-actions { - display: flex; - flex-wrap: wrap; - justify-content: center; - gap: 10px; +.registry-topbar-kicker { + display: inline-flex; + align-items: center; + gap: 9px; + width: fit-content; + margin: 0 0 14px; + color: var(--accent); + font-size: 0.72rem; + font-weight: 600; + letter-spacing: 0.12em; + line-height: 1.25; + text-transform: uppercase; } -.registry-home-band { - display: grid; - grid-template-columns: minmax(0, 1fr) minmax(260px, 0.75fr); - gap: 16px; - align-items: stretch; - margin-top: 56px; - padding-top: 0; +.registry-topbar-kicker-icon { + display: inline-grid; + width: 24px; + height: 24px; + place-items: center; + border: 1px solid color-mix(in oklch, var(--accent) 20%, var(--line-soft)); + border-radius: 7px; + background: color-mix(in oklch, var(--accent) 7%, var(--panel-soft)); } -.registry-home-stats { - display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); - gap: 8px; - margin: 0; +.registry-topbar-kicker-icon svg { + width: 13px; + height: 13px; } -.registry-home-stats div, -.registry-home-source, -.registry-home-featured { - min-width: 0; - border: 1px solid var(--line-soft); - border-radius: var(--radius); - background: var(--panel-soft); +.registry-topbar-cta { + flex: 0 0 auto; + justify-self: start; } -.registry-home-stats div { - padding: 15px 16px; +.registry-eyebrow, +.registry-label { + margin: 0 0 8px; + color: var(--accent); + font-family: var(--font-sans); + font-size: var(--text-sm); + font-weight: 650; + letter-spacing: 0; + text-transform: none; } -.registry-home-stats div { - position: relative; - overflow: hidden; +.registry-topbar h1, +.registry-panel h2, +.registry-panel h3 { + margin: 0; + color: var(--text); + letter-spacing: 0; } -.registry-home-stats div::after { - content: ""; - position: absolute; - inset: 0 auto 0 0; - width: 2px; - background: var(--accent-line); - opacity: 0.7; +.registry-topbar h1 { + max-width: 22ch; + font-size: clamp(2.1rem, 3.35vw, 3.1rem); + line-height: 1.14; + letter-spacing: -0.035em; + text-wrap: balance; } -.registry-home-stats dt, -.registry-home-featured dt, -.registry-home-source span, -.registry-home-featured > div > span { - margin: 0 0 5px; - color: var(--dim); - font-family: var(--font-mono); - font-size: 0.66rem; - text-transform: uppercase; +.registry-topbar-compact { + align-content: start; } -.registry-home-stats dd, -.registry-home-featured dd { - margin: 0; - color: var(--text); - font-weight: 600; - font-size: 1.12rem; - font-variant-numeric: tabular-nums; +.registry-topbar-compact h1 { + font-size: clamp(1.85rem, 2.7vw, 2.35rem); + line-height: 1.16; } -.registry-home-source { - display: grid; - align-content: center; - gap: 7px; - padding: 13px; +.registry-topbar-compact p:not(.registry-eyebrow) { + max-width: 640px; + margin-top: 14px; } -.registry-home-source code { - color: var(--text-soft); - overflow-wrap: anywhere; +.registry-topbar p:not(.registry-eyebrow), +.registry-panel > p, +.registry-empty p, +.registry-body-copy { + max-width: 58ch; + margin: 14px 0 0; + color: var(--muted); + font-size: clamp(0.94rem, 1.1vw, 1rem); + line-height: 1.72; + text-wrap: pretty; } -.registry-home-columns { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 26px; - margin-top: 56px; +.registry-panel, +.registry-package-row, +.registry-side-card, +.registry-table-wrap { + min-width: 0; + border: 1px solid var(--line-soft); + border-radius: var(--radius); + background: var(--panel-soft); } -.registry-home-columns article { +.registry-tabs { + --registry-tab-indicator-width: 0px; + --registry-tab-indicator-x: 0px; position: relative; - min-width: 0; - padding: 18px 0 0; - border-top: 1px solid var(--line-soft); + display: flex; + gap: 4px; + min-height: 52px; + padding: 0 calc(var(--registry-rail) - 16px); + background: transparent; + border-top: 1px solid color-mix(in oklch, var(--line-soft) 86%, var(--accent-line)); } -.registry-home-columns article::before { +.registry-tabs::after { content: ""; position: absolute; + z-index: 2; + bottom: 0; left: 0; - top: -1px; - width: 28px; - height: 2px; - background: var(--accent); + width: var(--registry-tab-indicator-width); + height: 3px; + border-radius: 999px 999px 0 0; + background: var(--accent-quiet); + opacity: 0; + transform: translateX(var(--registry-tab-indicator-x)); + transition: + width 240ms var(--ease-quick), + transform 240ms var(--ease-quick), + opacity 120ms var(--ease-standard); + pointer-events: none; +} + +.registry-tabs[data-indicator-ready="true"]::after { + opacity: 1; +} + +.registry-tabs a { + position: relative; + z-index: 1; + display: inline-flex; + align-items: center; + min-height: 52px; + padding: 0 16px; + border: 0; + border-radius: var(--radius-control) var(--radius-control) 0 0; + color: var(--muted); + font-size: var(--text-sm); + font-weight: 600; + text-decoration: none; + transition: + color var(--duration-med) var(--ease-standard), + background-color var(--duration-fast) var(--ease-standard), + transform var(--duration-fast) var(--ease-quick); +} + +.registry-tabs a::after { + content: ""; + position: absolute; + right: 0; + bottom: 0; + left: 0; + height: 3px; + background: var(--accent-quiet); + opacity: 0; + transform: scaleX(0.65); + transform-origin: center; + transition: + opacity var(--duration-fast) var(--ease-standard), + transform var(--duration-med) var(--ease-quick); +} + +.registry-tabs a:hover, +.registry-tabs a.active { + color: var(--text); +} + +.registry-tabs a:hover { + background: color-mix(in oklch, var(--control-surface-hover) 48%, transparent); +} + +.registry-tabs a.active { + background: transparent; +} + +.registry-tabs a:active { + transform: translateY(1px); +} + +.registry-tabs a.active { + box-shadow: none; +} + +.registry-tabs a.active::after { + opacity: 1; + transform: scaleX(1); +} + +.registry-tabs[data-indicator-ready="true"] a::after { + opacity: 0; + transition: none; +} + +.registry-hero-no-header { + overflow: visible; + border: 0; + border-radius: 0; + background: transparent; + box-shadow: none; +} + +.registry-hero-no-header .registry-shell-bar { + padding: 0 0 14px; +} + +.registry-hero-no-header .registry-environment-bar { + display: inline-flex; + align-items: center; + gap: 10px; + width: auto; + min-height: 38px; + padding: 4px 5px 4px 13px; + border-radius: 999px; +} + +.registry-hero-no-header .registry-network-label, +.registry-hero-no-header .registry-network-body, +.registry-hero-no-header .registry-network-code { + display: none; +} + +.registry-hero-no-header .registry-environment-identity { + gap: 8px; +} + +.registry-hero-no-header .registry-environment-bar strong { + font-size: 0.76rem; +} + +.registry-hero-no-header .registry-network-switch { + gap: 7px; + padding: 6px 9px; + border: 0; + border-radius: 999px; + background: var(--control-surface); + font-size: 0.74rem; +} + +.registry-hero-no-header .registry-tabs { + min-height: 52px; + padding: 0; + border-top: 0; + border-bottom: 1px solid var(--line-soft); +} + +.registry-hero-no-header .registry-tabs a { + min-height: 52px; +} + +@media (max-width: 980px) { + .registry-shell-bar { + grid-template-columns: minmax(0, 1fr); + row-gap: 32px; + } + + .registry-hero:not(.registry-hero-no-header) .registry-topbar, + .registry-hero:not(.registry-hero-no-header) .registry-network-card { + grid-column: 1 / -1; + } + + .registry-environment-bar { + grid-template-columns: minmax(0, 1fr) auto; + grid-template-rows: auto auto auto; + min-height: 0; + } + + .registry-network-label { + grid-column: 1 / -1; + } + + .registry-network-heading, + .registry-network-body { + grid-column: 1; + } + + .registry-network-switch { + grid-column: 2; + grid-row: 2 / 4; + align-self: end; + min-width: 150px; + padding: 12px 14px; + border: 1px solid var(--line-soft); + border-radius: 11px; + } +} + +@media (max-width: 640px) { + .registry-app { + --registry-route-start: 24px; + --registry-rail: 22px; + padding-top: 14px; + } + + .registry-hero { + border-radius: 18px; + } + + .registry-shell-bar { + gap: 28px; + padding: 30px 22px 24px; + } + + .registry-topbar h1, + .registry-topbar-compact h1 { + max-width: none; + font-size: clamp(1.7rem, 8.5vw, 2.2rem); + line-height: 1.16; + } + + .registry-topbar p:not(.registry-eyebrow) { + margin-top: 12px; + font-size: 0.94rem; + line-height: 1.68; + } + + .registry-environment-bar { + grid-template-columns: minmax(0, 1fr); + grid-template-rows: auto; + gap: 12px; + padding: 19px; + } + + .registry-network-label, + .registry-network-heading, + .registry-network-body, + .registry-network-switch { + grid-column: 1; + grid-row: auto; + } + + .registry-network-switch { + min-width: 0; + margin-top: 2px; + } + + .registry-tabs { + gap: 0; + min-height: 52px; + padding: 0 8px; + overflow-x: auto; + overscroll-behavior-inline: contain; + scrollbar-width: none; + } + + .registry-tabs::-webkit-scrollbar { + display: none; + } + + .registry-tabs a { + min-height: 52px; + padding: 0 14px; + white-space: nowrap; + } + + .registry-hero-no-header .registry-shell-bar { + padding: 0 0 12px; + } + + .registry-hero-no-header .registry-environment-bar { + justify-self: start; + } + + .registry-hero-no-header .registry-tabs { + min-height: 52px; + padding: 0; + } + + .registry-hero-no-header .registry-tabs a { + min-height: 52px; + } +} + +::view-transition-old(root), +::view-transition-new(root) { + animation: none; + mix-blend-mode: normal; +} + +::view-transition-old(registry-header) { + animation: registry-header-out 120ms var(--ease-standard) both; + mix-blend-mode: normal; +} + +::view-transition-new(registry-header) { + animation: registry-header-in 220ms var(--ease-quick) both; + mix-blend-mode: normal; +} + +@keyframes registry-header-out { + to { + opacity: 0; + filter: blur(2px); + transform: translateY(-5px); + } +} + +@keyframes registry-header-in { + from { + opacity: 0; + filter: blur(3px); + transform: translateY(7px); + } +} + +::view-transition-old(registry-route) { + animation: registry-route-out 110ms var(--ease-standard) both; + mix-blend-mode: normal; +} + +::view-transition-new(registry-route) { + animation: registry-route-in 170ms var(--ease-quick) both; + mix-blend-mode: normal; +} + +@keyframes registry-route-out { + to { opacity: 0; transform: translateY(-3px); } +} + +@keyframes registry-route-in { + from { opacity: 0; transform: translateY(4px); } +} + +.registry-api { + display: grid; + gap: clamp(36px, 5vw, 58px); + padding-top: var(--registry-route-start); +} + +.registry-api-group-heading { + display: flex; + align-items: center; + justify-content: space-between; + gap: 24px; +} + +.registry-api-example > div > p { + max-width: 72ch; + margin: 14px 0 0; + color: var(--text-soft); + font-size: 0.96rem; + line-height: 1.7; +} + +.registry-api-health { + display: inline-flex; + align-items: center; + gap: 7px; + min-height: 28px; + color: var(--accent); + font-size: 0.8rem; + font-weight: 650; + text-decoration: none; +} + +.registry-api-health svg, +.registry-api-endpoint > a svg { + width: 15px; + height: 15px; + transition: transform var(--duration-fast) var(--ease-quick); +} + +.registry-api-health:hover svg, +.registry-api-endpoint > a:hover svg { + transform: translate(2px, -2px); +} + +.registry-api-group { + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: 16px; +} + +.registry-api-group h2, +.registry-api-example h2 { + margin: 0; + color: var(--text); + font-size: 0.94rem; + font-weight: 720; + line-height: 1.45; +} + +.registry-api-endpoints { + border-top: 1px solid var(--line-soft); +} + +.registry-api-endpoint { + display: grid; + grid-template-columns: repeat(12, minmax(0, 1fr)); + column-gap: var(--registry-grid-gap); + align-items: center; + min-height: 66px; + padding: 14px 0; + border-bottom: 1px solid var(--line-soft); +} + +.registry-api-method, +.registry-api-endpoint code { + font-family: var(--font-mono); + font-size: 0.77rem; +} + +.registry-api-method { + display: inline-flex; + align-items: center; + justify-self: start; + min-height: 22px; + padding: 0 8px; + border: 1px solid var(--accent-line); + border-radius: 999px; + background: color-mix(in oklch, var(--accent-surface) 45%, transparent); + font-size: 0.7rem; + font-weight: 700; + letter-spacing: 0.06em; + text-transform: uppercase; +} + +.registry-api-method[data-method="post"] { + border-color: color-mix(in oklch, var(--substrate-accent-dim) 42%, transparent); + background: color-mix(in oklch, var(--substrate-bg) 76%, transparent); + color: var(--substrate-accent); +} + +.registry-api-endpoint code { + color: var(--text); + line-height: 1.5; + overflow-wrap: anywhere; +} + +.registry-api-endpoint p { + margin: 0; + color: var(--text-soft); + font-size: 0.88rem; + line-height: 1.5; +} + +.registry-api-endpoint > a { + display: inline-flex; + justify-self: end; + color: var(--accent); +} + +@media (min-width: 901px) { + .registry-api-method { + grid-column: 1; + } + + .registry-api-endpoint code { + grid-column: 2 / span 5; + } + + .registry-api-endpoint p { + grid-column: 7 / span 5; + } + + .registry-api-endpoint > a { + grid-column: 12; + } +} + +.registry-api-example { + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: 18px; + align-items: start; + padding-top: 30px; + border-top: 1px solid var(--line-soft); +} + +.registry-api-example > div:first-child { + max-width: 68ch; +} + +.registry-api-example .registry-step-code { + max-width: 920px; +} + +.registry-browse { + padding-top: var(--registry-route-start); + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: 14px; +} + +.registry-tool-route { + display: grid; + width: 100%; + min-width: 0; + padding-top: var(--registry-route-start); +} + +.registry-inline-icon { + display: inline-flex; + width: 16px; + height: 16px; +} + +.registry-inline-icon svg { + width: 100%; + height: 100%; +} + +.registry-ls-idl-tools { + width: 100%; +} + +.registry-ls-idl-panel { + display: grid; + grid-template-columns: repeat(12, minmax(0, 1fr)); + column-gap: var(--registry-grid-gap); + row-gap: 28px; + padding: clamp(28px, 4vw, 46px); + border: 1px solid var(--line-soft); + border-radius: clamp(18px, 2vw, 24px); + background: + linear-gradient(135deg, color-mix(in oklch, var(--panel-strong) 92%, var(--accent-surface)), var(--panel-soft)); +} + +.registry-ls-idl-boundary { + grid-column: 1 / span 3; + max-width: 36ch; + margin: 0; + padding-left: 18px; + border-left: 2px solid color-mix(in oklch, var(--accent) 72%, var(--line-soft)); + color: var(--text-soft); + font-size: 0.9rem; + line-height: 1.72; +} + +.registry-ls-idl-lookup-form { + display: grid; + grid-column: 4 / -1; + grid-template-columns: minmax(130px, 1fr) minmax(130px, 1fr) auto; + gap: 14px; + align-items: end; +} + +.registry-ls-idl-lookup-form label { + display: grid; + gap: 8px; + color: var(--dim); + font-size: 0.78rem; + line-height: 1.4; +} + +.registry-ls-idl-lookup-form .registry-button { + grid-column: 3; + grid-row: 2; + min-height: 44px; + padding-inline: 20px; +} + +.registry-ls-idl-code-hash { + grid-column: 1 / -1; + grid-row: 1; +} + +.registry-ls-idl-hash-type { + grid-column: 1; + grid-row: 2; +} + +.registry-ls-idl-network { + grid-column: 2; + grid-row: 2; +} + +.registry-ls-idl-data-hash { + grid-column: 1 / -1; + grid-row: 3; +} + +.registry-ls-idl-data-hash[hidden] { + display: none; +} + +.registry-ls-idl-result { + grid-column: 1 / -1; + display: grid; + gap: 10px; + margin-top: 4px; + padding-top: 22px; + border-top: 1px solid var(--line-soft); + color: var(--text-soft); +} + +.registry-ls-idl-result[hidden] { + display: none; +} + +.registry-ls-idl-result a { + width: fit-content; + color: var(--accent); + font-weight: 650; +} + +.registry-ls-idl-result pre { + max-height: 320px; + margin: 0; + padding: 14px; + overflow: auto; + border-radius: var(--control-radius); + background: var(--surface-code-start); + color: var(--text); + font: 0.76rem/1.55 var(--font-mono); + white-space: pre-wrap; +} + +.registry-ls-idl-heading, +.registry-ls-idl-actions { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; +} + +.registry-ls-idl-heading h2 { + margin: 0; +} + +.registry-ls-idl-facts { + margin-top: 16px; +} + +.registry-ls-idl-facts code { + overflow-wrap: anywhere; +} + +.registry-ls-idl-actions { + justify-content: flex-start; + margin-top: 16px; +} + +.registry-ls-idl-actions > a:not(.registry-button) { + color: var(--accent); + font-size: 0.82rem; + font-weight: 650; +} + +.registry-ls-idl .registry-step-code { + margin-top: 14px; +} + +.registry-ls-idl > .registry-field-hint { + margin-top: 12px; +} + +@media (max-width: 1020px) { + .registry-ls-idl-panel { + grid-template-columns: minmax(0, 1fr); + gap: 26px; + } + + .registry-ls-idl-boundary, + .registry-ls-idl-lookup-form { + grid-column: 1; + } + + .registry-ls-idl-boundary { + max-width: 72ch; + } + + .registry-ls-idl-lookup-form { + grid-template-columns: minmax(130px, 1fr) minmax(130px, 1fr) auto; + } +} + +@media (max-width: 620px) { + .registry-ls-idl-panel { + gap: 22px; + padding: 22px 18px; + } + + .registry-ls-idl-lookup-form { + grid-template-columns: minmax(0, 1fr); + } + + .registry-ls-idl-code-hash, + .registry-ls-idl-hash-type, + .registry-ls-idl-network, + .registry-ls-idl-data-hash, + .registry-ls-idl-lookup-form .registry-button { + grid-column: 1; + } + + .registry-ls-idl-hash-type { + grid-row: 2; + } + + .registry-ls-idl-network { + grid-row: 3; + } + + .registry-ls-idl-data-hash { + grid-row: 4; + } + + .registry-ls-idl-lookup-form .registry-button { + grid-row: 5; + width: 100%; + } + + .registry-ls-idl-heading, + .registry-ls-idl-actions { + align-items: flex-start; + flex-direction: column; + } +} + +.registry-network-badge i { + width: 7px; + height: 7px; + flex: 0 0 auto; + border-radius: 50%; + background: var(--accent); + box-shadow: 0 0 0 4px var(--accent-surface); +} + +.registry-browse-search { + position: relative; + z-index: 3; + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; + padding: 6px; + border: 1px solid var(--line-soft); + border-radius: var(--radius-panel); + background: var(--panel-soft); + box-shadow: none; +} + +.registry-browse-input { + display: grid; + grid-template-columns: 20px minmax(0, 1fr); + align-items: center; + gap: 10px; + flex: 1 1 260px; + min-width: 0; + min-height: var(--control-height); + padding: 0 12px; + border: 1px solid transparent; + border-radius: var(--control-radius); + background: transparent; + color: var(--dim); + transition: + border-color var(--duration-fast) var(--ease-standard), + background-color var(--duration-fast) var(--ease-standard); +} + +.registry-browse-input svg { + width: 18px; + height: 18px; +} + +.registry-browse-input input { + width: 100%; + min-width: 0; + border: 0; + outline: 0; + background: transparent; + color: var(--text); + font: inherit; + font-size: 0.95rem; +} + +.registry-browse-input input::placeholder { + color: var(--dim); +} + +.registry-browse-input:focus-within { + border-color: var(--accent-line); + background: var(--surface-select); + color: var(--accent); + outline: var(--focus-ring-width) solid var(--accent); + outline-offset: var(--focus-ring-offset); + box-shadow: none; +} + +.registry-browse-filter-row { + display: flex; + align-items: center; + flex-wrap: wrap; + flex: 1 1 auto; + gap: 8px; + padding-left: 8px; + border-left: 1px solid var(--line-soft); +} + +.registry-browse-filter { + position: relative; + width: auto; + min-width: 168px; + max-width: 230px; +} + +.registry-browse-filter.registry-browse-intent { + width: auto; + min-width: 184px; +} + +@media (max-width: 1040px) { + .registry-browse-input { + flex-basis: 100%; + } + + .registry-browse-filter-row { + flex: 1 1 100%; + padding-left: 0; + border-left: 0; + } +} + +.registry-filter-trigger { + width: 100%; + min-height: var(--control-height); + padding: 0 32px 0 12px; + border: 1px solid transparent; + border-radius: var(--control-radius); + background: var(--surface-select); + color: var(--text-soft); + font: inherit; + font-size: 0.82rem; + font-weight: 500; + text-align: left; + cursor: pointer; + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + transition: + border-color var(--duration-fast) var(--ease-standard), + background-color var(--duration-fast) var(--ease-standard), + color var(--duration-fast) var(--ease-standard); +} + +.registry-filter-trigger:hover { + border-color: var(--control-border); + background: color-mix(in oklch, var(--surface-select) 82%, var(--control-surface-hover)); + color: var(--control-text-hover); +} + +.registry-filter-trigger:focus-visible { + outline: var(--focus-ring-width) solid var(--accent); + outline-offset: var(--focus-ring-offset); + border-color: var(--accent-line); + color: var(--text); +} + +.registry-browse-filter[data-open="true"] .registry-filter-trigger { + outline: 0; + border-color: var(--accent-line); + background: var(--control-selected-surface); + color: var(--control-selected-text); +} + +.registry-filter-trigger svg { + width: 16px; + height: 16px; + flex: 0 0 auto; + color: var(--dim); + transition: transform 160ms var(--ease-standard), color 160ms var(--ease-standard); +} + +.registry-browse-filter[data-open="true"] .registry-filter-trigger svg { + color: var(--accent); + transform: rotate(180deg); +} + +.registry-filter-menu { + position: absolute; + z-index: 5; + top: calc(100% + 7px); + left: 0; + width: max(100%, 232px); + max-height: min(330px, calc(100dvh - 150px)); + overflow-y: auto; + padding: 6px; + border: 1px solid var(--line); + border-radius: var(--radius-overlay); + background: var(--surface-select); + box-shadow: var(--elev-2); + animation: registry-filter-menu-in 150ms var(--ease-standard) both; + overscroll-behavior: contain; +} + +.registry-filter-menu[hidden] { + display: none; +} + +.registry-filter-menu button { + width: 100%; + min-height: 38px; + padding: 7px 9px; + border: 0; + border-radius: var(--control-radius); + background: transparent; + color: var(--text-soft); + font: inherit; + font-size: 0.82rem; + line-height: 1.2; + text-align: left; + cursor: pointer; + display: grid; + grid-template-columns: 15px minmax(0, 1fr); + align-items: center; + gap: 8px; + transition: background-color 120ms var(--ease-standard), color 120ms var(--ease-standard); +} + +.registry-filter-menu button:hover, +.registry-filter-menu button:focus-visible { + background: var(--control-surface-hover); + color: var(--control-text-hover); +} + +.registry-filter-menu button:focus-visible { + outline: 2px solid var(--accent); + outline-offset: -2px; +} + +.registry-filter-menu button[aria-checked="true"] { + background: var(--control-selected-surface); + color: var(--control-selected-text); + font-weight: 600; +} + +.registry-filter-menu button svg { + width: 14px; + height: 14px; + color: var(--accent); + opacity: 0; +} + +.registry-filter-menu button[aria-checked="true"] svg { + opacity: 1; +} + +@keyframes registry-filter-menu-in { + from { + opacity: 0; + transform: translateY(-4px) scale(0.985); + } + + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +.registry-browse-skeleton, +.registry-package-list, +.registry-empty, +.registry-load-more, +.registry-service-state { + grid-column: 1 / -1; +} + +.registry-browse-count { + flex: 1 0 auto; + min-width: 0; + margin-left: auto; + padding-right: 6px; + color: var(--dim); + font-family: var(--font-mono); + font-size: 0.75rem; + font-variant-numeric: tabular-nums; + text-align: right; + white-space: nowrap; +} + +.registry-facets { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px 14px; +} + +.registry-facets[hidden] { + display: none; +} + +.registry-facets-label { + margin: 0; + color: var(--dim); + font-family: var(--font-mono); + font-size: 0.7rem; + font-weight: 600; + letter-spacing: 0.1em; + text-transform: uppercase; +} + +.registry-facets-row { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 6px; + min-width: 0; +} + +.registry-facet { + display: inline-flex; + align-items: center; + gap: 7px; + min-height: 28px; + padding: 0 10px; + border: 1px solid var(--line-soft); + border-radius: 999px; + background: transparent; + color: var(--text-soft); + font: inherit; + font-size: 0.74rem; + font-weight: 500; + cursor: pointer; + transition: + border-color var(--duration-fast) var(--ease-standard), + background-color var(--duration-fast) var(--ease-standard), + color var(--duration-fast) var(--ease-standard); +} + +.registry-facet i { + width: 8px; + height: 8px; + flex: 0 0 auto; + border-radius: 999px; + background: var(--dim); +} + +.registry-facet[data-state="ready"] i { background: var(--status-active); } +.registry-facet[data-state="chain_verified"] i { background: var(--accent-strong); } +.registry-facet[data-state="verified"] i { background: var(--status-info); } +.registry-facet[data-state="source_only"] i { background: var(--substrate-accent-dim); } +.registry-facet[data-state="needs_evidence"] i { background: var(--status-warning-text); } +.registry-facet[data-state="deprecated"] i { background: var(--dim); } + +.registry-facet-count { + color: var(--dim); + font-family: var(--font-mono); + font-size: 0.7rem; + font-variant-numeric: tabular-nums; +} + +.registry-facet:hover { + border-color: var(--control-border-hover); + color: var(--control-text-hover); +} + +.registry-facet[aria-pressed="true"] { + border-color: var(--accent-line); + background: var(--control-selected-surface); + color: var(--control-selected-text); +} + +.registry-facet:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; +} + +.registry-panel { + padding: 18px; +} + +.registry-section-block { + margin-top: 18px; +} + +.registry-table-block { + margin-top: 16px; +} + +.registry-panel h2 { + font-size: 1.18rem; +} + +.registry-input, +.registry-select { + width: 100%; + min-height: 44px; + padding: 0 14px; + border: 1px solid var(--line-soft); + border-radius: var(--control-radius); + background: var(--surface-select); + color: var(--text); + font: inherit; +} + +.registry-input:focus, +.registry-select:focus { + outline: var(--focus-ring-width) solid var(--accent); + outline-offset: var(--focus-ring-offset); +} + +.registry-input[aria-invalid="true"] { + border-color: var(--status-danger-line); +} + +.registry-field-hint, +.registry-field-error { + display: block; + margin: 0; + color: var(--dim); + font-size: 0.76rem; + line-height: 1.45; +} + +.registry-service-state[data-mode="error"] { + border-left-color: var(--status-danger-line); + background: color-mix(in oklch, var(--status-danger-surface) 52%, transparent); + color: var(--status-danger-text); +} + +.registry-service-state[data-mode="stale"] { + border-left-color: var(--accent-line); + background: color-mix(in oklch, var(--accent-surface) 48%, transparent); +} + +.registry-field-error { + color: var(--status-danger-text); +} + +.registry-field-hint[hidden], +.registry-field-error[hidden] { + display: none; +} + +.registry-button, +.registry-copy { + min-height: var(--control-height); + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 0 12px; + border: 1px solid var(--control-border); + border-radius: var(--control-radius); + background: var(--control-surface); + color: var(--control-text); + font: inherit; + font-size: 0.86rem; + font-weight: 600; + text-decoration: none; + cursor: pointer; + touch-action: manipulation; + box-shadow: inset 0 1px 0 var(--line-inner-soft); + transition: + border-color var(--duration-fast) var(--ease-standard), + background-color var(--duration-fast) var(--ease-standard), + color var(--duration-fast) var(--ease-standard), + transform var(--duration-fast) var(--ease-quick), + opacity var(--duration-fast) var(--ease-standard); +} + +.registry-button.primary { + border-color: var(--color-transparent); + background: var(--control-primary); + color: var(--control-primary-text); + box-shadow: inset 0 1px 0 color-mix(in oklch, white 24%, transparent); +} + +.registry-button:disabled, +.registry-copy:disabled { + cursor: not-allowed; + opacity: 0.46; + transform: none; +} + +.registry-button:hover, +.registry-copy:hover { + border-color: var(--control-border-hover); + background: var(--control-surface-hover); + color: var(--control-text-hover); + transform: translateY(-1px); +} + +.registry-button.primary:hover { + border-color: var(--color-transparent); + background: var(--control-primary-hover); + color: var(--control-primary-text); +} + +.registry-copy.is-copied { + border-color: var(--accent-line); + background: var(--control-selected-surface); + color: var(--control-selected-text); +} + +.registry-button:focus-visible, +.registry-copy:focus-visible, +.registry-text-button:focus-visible, +.registry-intro-dismiss:focus-visible, +.registry-wallet-back:focus-visible, +.registry-wallet-close:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 3px; +} + +.registry-button:active, +.registry-copy:active { + transform: translateY(1px); +} + +.registry-copy.is-copy-failed { + border-color: var(--substrate-accent); + color: var(--substrate-accent); +} + +.registry-package-list { + display: grid; + gap: 0; + border-top: 1px solid var(--line-soft); +} + +.registry-package-row { + position: relative; + display: grid; + grid-template-columns: 40px minmax(0, 1fr) auto; + gap: 16px; + align-items: start; + padding: 16px 14px 16px 16px; + border: 0; + border-bottom: 1px solid var(--line-soft); + border-radius: 0; + background: transparent; + color: inherit; + text-decoration: none; + overflow: hidden; + transition: background-color var(--duration-fast) var(--ease-standard); +} + +.registry-row-copy { + min-width: 0; +} + +.registry-row-headline { + display: flex; + align-items: baseline; + flex-wrap: wrap; + gap: 4px 10px; + min-width: 0; +} + +.registry-artifact-mark { + position: relative; + width: 40px; + height: 40px; + display: grid; + place-items: center; + align-self: start; + overflow: hidden; + border: 1px solid var(--line-soft); + border-radius: var(--radius-control); + background: color-mix(in oklch, var(--panel-strong) 70%, transparent); + color: var(--text-soft); + transition: + border-color var(--duration-fast) var(--ease-standard), + background-color var(--duration-fast) var(--ease-standard); +} + +.registry-artifact-mark::before { + content: none; +} + +.registry-artifact-mark i { + display: none; +} + +.registry-artifact-glyph, +.registry-artifact-glyph svg { + width: 18px; + height: 18px; +} + +.registry-artifact-glyph svg { + display: block; +} + +.registry-artifact-mark[data-kind="source_library"], +.registry-artifact-mark[data-kind="profile_library"], +.registry-artifact-mark[data-kind="template"] { + border-color: color-mix(in oklch, var(--accent-line) 60%, var(--line-soft)); + background: color-mix(in oklch, var(--accent-surface) 42%, transparent); + color: var(--accent); +} + +.registry-artifact-mark[data-kind="deployable_contract"], +.registry-artifact-mark[data-kind="reproducible_binary"] { + border-color: color-mix(in oklch, var(--substrate-accent-dim) 32%, var(--line-soft)); + background: color-mix(in oklch, var(--substrate-bg) 72%, transparent); + color: var(--substrate-accent); +} + +.registry-artifact-mark[data-kind="runtime_verifier"] { + border-color: color-mix(in oklch, var(--status-info) 26%, var(--line-soft)); + background: color-mix(in oklch, var(--status-info-surface) 38%, transparent); + color: var(--status-info); +} + +.registry-package-row:hover .registry-artifact-mark { + border-color: color-mix(in oklch, var(--accent-line) 78%, var(--line-soft)); +} + +.registry-package-row:hover { + background: color-mix(in oklch, var(--accent-surface) 42%, transparent); + box-shadow: inset 0 0 0 1px color-mix(in oklch, var(--accent-line) 42%, transparent); +} + +.registry-browse[data-state="ready"] .registry-package-row { + animation: registry-row-enter 220ms var(--ease-standard) both; +} + +.registry-browse[data-state="ready"] .registry-package-row:nth-child(2) { animation-delay: 24ms; } +.registry-browse[data-state="ready"] .registry-package-row:nth-child(3) { animation-delay: 48ms; } +.registry-browse[data-state="ready"] .registry-package-row:nth-child(4) { animation-delay: 72ms; } +.registry-browse[data-state="ready"] .registry-package-row:nth-child(5) { animation-delay: 96ms; } +.registry-browse[data-state="ready"] .registry-package-row:nth-child(6) { animation-delay: 120ms; } + +@keyframes registry-row-enter { + from { + opacity: 0; + transform: translateY(5px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.registry-package-row[hidden] { + display: none; +} + +.registry-package-row strong { + color: var(--text); + overflow-wrap: anywhere; +} + +.registry-row-coordinate { + font-size: 1.02rem; + font-weight: 600; + letter-spacing: -0.01em; + line-height: 1.35; +} + +.registry-row-coordinate span { + color: var(--dim); + font-weight: 500; +} + +.registry-row-version { + color: var(--dim); + font-family: var(--font-mono); + font-size: 0.72rem; + font-weight: 500; + letter-spacing: 0.01em; + overflow-wrap: anywhere; +} + +.registry-package-row .registry-row-conclusion { + margin: 5px 0 0; + color: var(--text-soft); + font-size: 0.8rem; + font-weight: 600; + line-height: 1.45; +} + +.registry-package-row[data-usage-state="ready"] .registry-row-conclusion { + color: var(--status-active); +} + +.registry-package-row[data-usage-state="needs_evidence"] .registry-row-conclusion, +.registry-package-row[data-usage-state="source_only"] .registry-row-conclusion { + color: var(--status-warning-text); +} + +.registry-package-row[data-usage-state="hash_bound"] .registry-row-conclusion, +.registry-package-row[data-usage-state="deprecated"] .registry-row-conclusion { + color: var(--status-info); +} + +.registry-package-row[data-usage-state="unavailable"] .registry-row-conclusion { + color: var(--status-danger-text); +} + +.registry-package-row .registry-row-description { + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + margin: 5px 0 0; + overflow: hidden; + color: var(--muted); + font-size: 0.85rem; + line-height: 1.55; +} + +.registry-row-meta { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 8px 16px; + margin-top: 12px; +} + +.registry-row-meta .registry-badges { + margin-top: 0; +} + +.registry-row-meta time { + color: var(--dim); + font-family: var(--font-mono); + font-size: 0.72rem; + font-variant-numeric: tabular-nums; + white-space: nowrap; +} + +.registry-badges { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 6px; + margin-top: 10px; +} + +.registry-evidence { + display: inline-flex; + align-items: center; + gap: 6px; + min-height: 22px; + padding: 0 2px 0 4px; +} + +.registry-evidence-label { + color: var(--dim); + font-family: var(--font-mono); + font-size: 0.68rem; + font-weight: 560; + letter-spacing: 0.02em; +} + +.registry-evidence-bars { + display: inline-flex; + align-items: center; + gap: 3px; +} + +.registry-evidence-bars i { + width: 16px; + height: 4px; + border-radius: 2px; + background: var(--bar-track); +} + +.registry-evidence[data-level="1"] .registry-evidence-bars i:nth-child(-n + 1), +.registry-evidence[data-level="2"] .registry-evidence-bars i:nth-child(-n + 2), +.registry-evidence[data-level="3"] .registry-evidence-bars i:nth-child(-n + 3), +.registry-evidence[data-level="4"] .registry-evidence-bars i:nth-child(-n + 4) { + background: var(--accent-quiet); +} + +.registry-evidence[data-level="4"] .registry-evidence-bars i:nth-child(-n + 4) { + background: var(--status-active); +} + +.registry-badge, +.registry-status { + display: inline-flex; + align-items: center; + min-height: 22px; + padding: 0 8px; + border: 1px solid var(--line-soft); + border-radius: var(--radius-data); + color: var(--text-soft); + font-family: var(--font-sans); + font-size: 0.72rem; + font-weight: 500; +} + +.registry-status { + border-color: var(--accent-line); + color: var(--accent); + text-transform: none; + white-space: nowrap; +} + +/* Semantic status colors: encode registry state by colour so a single + package's identity is readable at a glance. */ +.registry-badge[data-kind="production"] { + border-color: var(--status-active); + color: var(--status-active); + background: var(--status-active-surface); +} + +.registry-badge[data-kind="network"] { + border-color: var(--status-info); + color: var(--status-info); + background: transparent; +} + +.registry-badge[data-kind="usage"] { + border-color: var(--accent-line); + color: var(--accent); +} + +.registry-badge[data-kind="usage"][data-usage-state="needs_evidence"], +.registry-badge[data-kind="usage"][data-usage-state="source_only"] { + border-color: var(--status-warning-line); + color: var(--status-warning-text); +} + +.registry-badge[data-kind="usage"][data-usage-state="unavailable"] { + border-color: var(--status-danger-line); + color: var(--status-danger-text); +} + +.registry-badge[data-kind="usage"][data-usage-state="hash_bound"], +.registry-badge[data-kind="usage"][data-usage-state="deprecated"] { + border-color: var(--status-info); + color: var(--status-info); +} + +.registry-badge[data-kind="license"] { + border-color: var(--line-soft); + color: var(--text-soft); +} + +.registry-badge[data-kind="version"] { + border-color: var(--line-soft); + color: var(--text-soft); +} + +.registry-badge[data-kind="artifact"] { + border-color: var(--line-soft); + color: var(--text-soft); + background: transparent; + text-transform: none; +} + +.registry-status[data-state="active"] { + border-color: var(--status-active); + color: var(--status-active); + background: var(--status-active-surface); +} + +.registry-status[data-state="source"] { + border-color: var(--line-soft); + color: var(--status-source); + background: var(--status-source-surface); +} + +.registry-status[data-state="info"] { + border-color: var(--line-soft); + color: var(--text-soft); + background: transparent; +} + +.registry-status[data-state="warning"] { + border-color: var(--status-warning-line); + color: var(--status-warning-text); + background: var(--status-warning-surface); +} + +.registry-status[data-state="danger"] { + border-color: var(--status-danger-line); + color: var(--status-danger-text); + background: var(--status-danger-surface); +} + +.registry-meta { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 8px; + margin: 16px 0 0; +} + +.registry-meta div { + min-width: 0; + padding: 12px; + border: 1px solid var(--line-soft); + border-radius: 7px; + background: var(--panel-strong); +} + +.registry-command-line { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 10px; + align-items: center; + margin-top: 16px; +} + +.registry-command-line code { + min-width: 0; + padding: 10px 12px; + border: 1px solid var(--line-soft); + border-radius: 7px; + background: var(--panel-strong); + color: var(--substrate-accent); + font-family: var(--font-mono); + font-size: 0.78rem; + overflow-wrap: anywhere; +} + +.registry-table-wrap { + overflow: auto; +} + +.registry-table { + width: 100%; + min-width: 720px; + border-collapse: collapse; + font-size: 0.9rem; +} + +.registry-table th, +.registry-table td { + padding: 12px; + border-bottom: 1px solid var(--line-soft); + text-align: left; + vertical-align: top; +} + +.registry-table th { + color: var(--dim); + font-family: var(--font-sans); + font-size: var(--text-xs); + font-weight: 650; + text-transform: none; +} + +.registry-table td { + color: var(--text-soft); +} + +.registry-table td > span { + vertical-align: middle; +} + +.registry-cell-code { + font-family: var(--font-mono); + overflow-wrap: anywhere; + white-space: nowrap; +} + +.registry-cell-nowrap { + white-space: nowrap; +} + +.registry-value-copy { + min-height: var(--control-height-dense); + margin-left: 8px; + padding-inline: 8px; + font-size: 0.7rem; +} + +.registry-side-card { + padding: 18px; +} + +.registry-side-card + .registry-side-card { + margin-top: 12px; +} + +.registry-side-card h3 { + margin: 0; + color: var(--text); + font-size: 1rem; +} + +.registry-side-card p { + margin: 10px 0 0; + color: var(--muted); + line-height: 1.5; +} + +/* ===== Submission flow ===== */ +.registry-flow { + width: 100%; + padding-top: 0; + margin-inline: 0; + display: grid; + gap: 0; +} + +.registry-publish-entry { + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: 34px; + align-items: start; + width: min(100%, 1040px); + min-height: 0; + padding: var(--registry-route-start) 0 46px; +} + +.registry-session-approval { + display: grid; + grid-template-columns: minmax(0, 1.28fr) minmax(300px, 0.72fr); + gap: clamp(48px, 6vw, 84px); + align-items: start; + min-height: 0; + padding: clamp(48px, 5vw, 68px) 0 72px; + animation: registry-panel-enter 260ms var(--ease-quick) both; +} + +.registry-publish-entry[hidden], +.registry-session-approval[hidden], +.registry-manual-publish[hidden] { + display: none; +} + +.registry-publish-entry-main, +.registry-session-main { + min-width: 0; +} + +.registry-publish-entry-main { + max-width: 820px; +} + +.registry-context-label { + margin: 0 0 13px; + color: var(--accent); + font-size: 0.82rem; + font-weight: 700; + line-height: 1.45; +} + +.registry-session-approval h2 { + max-width: 20ch; + margin: 8px 0 16px; + color: var(--text); + font-size: clamp(2rem, 3vw, 2.58rem); + line-height: 1.03; + letter-spacing: -0.04em; + text-wrap: balance; +} + +.registry-publish-entry-lead, +.registry-session-lead { + max-width: 62ch; + margin: 0; + color: var(--text-soft); + font-size: clamp(1rem, 1.25vw, 1.08rem); + line-height: 1.74; + text-wrap: pretty; +} + +.registry-publish-command { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 20px; + align-items: center; + max-width: 820px; + margin-top: 25px; + padding: 17px 18px 17px 20px; + border: 1px solid var(--line-soft); + border-left: 3px solid var(--accent); + border-radius: var(--radius-overlay); + background: color-mix(in oklch, var(--panel-strong) 82%, var(--accent-surface)); + box-shadow: none; +} + +.registry-publish-command > span { + display: grid; + gap: 5px; + min-width: 0; +} + +.registry-publish-command small { + color: var(--text-soft); + font-size: 0.78rem; + line-height: 1.35; +} + +.registry-publish-command code { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + gap: 9px; + align-items: baseline; + color: var(--substrate-accent); + font-family: var(--font-mono); + font-size: clamp(0.9rem, 1.35vw, 1.02rem); + font-weight: 600; + line-height: 1.45; + overflow-wrap: anywhere; +} + +.registry-publish-command code i { + color: var(--accent); + font-style: normal; + font-weight: 500; +} + +.registry-publish-command code span { + min-width: 0; + overflow-wrap: anywhere; +} + +.registry-publish-command .registry-button { + min-height: 48px; + padding-inline: 22px; +} + +.registry-publish-command .registry-button.primary, +.registry-session-buttons .registry-button.primary { + border-color: var(--color-transparent); + background: var(--control-primary); + color: var(--control-primary-text); +} + +.registry-publish-command .registry-button.primary:hover, +.registry-session-buttons .registry-button.primary:hover { + border-color: var(--color-transparent); + background: var(--control-primary-hover); + color: var(--control-primary-text); +} + +:root[data-theme="light"] .registry-publish-command { + border-color: color-mix(in oklch, var(--line) 82%, var(--accent-line)); + background: color-mix(in oklch, var(--panel-strong) 88%, var(--accent-surface)); + box-shadow: none; +} + +.registry-publish-entry-note { + display: flex; + gap: 9px; + align-items: flex-start; + max-width: 66ch; + margin: 16px 0 0; + color: var(--muted); + font-size: 0.86rem; + line-height: 1.55; +} + +.registry-assurance-mark { + display: grid; + place-items: center; + width: 18px; + height: 18px; + flex: 0 0 auto; + margin-top: 1px; + border: 1px solid var(--accent-line); + border-radius: var(--radius-data); + background: var(--accent-surface); + color: var(--accent); +} + +.registry-assurance-mark svg { + width: 12px; + height: 12px; + stroke: currentColor; + stroke-width: 1.8; + stroke-linecap: round; + stroke-linejoin: round; +} + +.registry-publish-advanced { + margin-top: 20px; + padding: 0; + font-size: 0.88rem; + text-decoration: underline; + text-decoration-color: var(--line); + text-underline-offset: 4px; +} + +.registry-publish-journey { + display: grid; + grid-template-columns: minmax(150px, 0.22fr) minmax(0, 1fr); + gap: clamp(24px, 4vw, 48px); + align-items: start; + min-width: 0; + padding: 22px 0 0; + border-top: 1px solid var(--line-soft); +} + +.registry-session-journey { + min-width: 0; + padding: 24px 0 24px clamp(28px, 3.5vw, 44px); + border-left: 1px solid var(--line-soft); + background: linear-gradient(90deg, color-mix(in oklch, var(--panel-soft) 72%, transparent), transparent 86%); +} + +.registry-publish-journey ol { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0; + margin: 0; + padding: 0; + list-style: none; +} + +.registry-session-journey ol { + display: grid; + gap: 0; + margin: 20px 0 0; + padding: 0; + list-style: none; +} + +.registry-publish-journey li { + position: relative; + display: grid; + grid-template-columns: 38px minmax(0, 1fr); + gap: 13px; + min-width: 0; + min-height: 0; + padding: 0 clamp(18px, 2.7vw, 32px); + color: var(--dim); +} + +.registry-publish-journey li:first-child { + padding-left: 0; +} + +.registry-publish-journey li:last-child { + padding-right: 0; +} + +.registry-publish-journey li + li { + border-left: 1px solid var(--line-soft); +} + +.registry-session-journey li { + position: relative; + display: grid; + grid-template-columns: 38px minmax(0, 1fr); + gap: 14px; + min-height: 88px; + padding-bottom: 24px; + color: var(--dim); +} + +.registry-session-journey li:not(:last-child)::after { + content: ""; + position: absolute; + top: 38px; + bottom: 0; + left: 16px; + width: 1px; + background: var(--line-soft); +} + +.registry-session-journey li:last-child { + min-height: 0; + padding-bottom: 0; +} + +.registry-publish-journey li > span, +.registry-session-journey li > span { + display: grid; + place-items: center; + align-self: start; + width: 34px; + height: 34px; + border: 1px solid var(--line); + border-radius: var(--radius-control); + background: var(--panel-strong); + box-shadow: inset 0 1px 0 var(--line-inner-soft); + font-family: var(--font-mono); + font-size: var(--text-xs); + font-weight: 600; +} + +.registry-publish-journey li > span { + position: relative; + width: 20px; + height: 20px; + margin: 5px 7px 0; + border-color: var(--line-soft); + border-radius: var(--radius-data); + background: var(--panel-soft); + box-shadow: none; + font-size: 0; +} + +.registry-publish-journey li > span::after { + content: ""; + width: 10px; + height: 2px; + border-radius: 0; + background: var(--accent); +} + +.registry-publish-journey li > div, +.registry-session-journey li > div { + display: grid; + gap: 6px; + padding-top: 4px; +} + +.registry-publish-journey strong, +.registry-session-journey strong { + color: var(--text); + font-size: 0.94rem; + font-weight: 650; + line-height: 1.35; +} + +.registry-publish-journey small, +.registry-session-journey small { + color: var(--text-soft); + font-size: 0.84rem; + line-height: 1.55; +} + +.registry-session-approval { + align-items: start; + min-height: 0; + padding: clamp(42px, 4vw, 58px) 0 68px; +} + +.registry-session-approval h2 { + max-width: 21ch; +} + +.registry-session-approval h2 > span, +.registry-session-approval h2 > strong { + display: block; +} + +.registry-session-approval h2 > strong { + margin-top: 7px; + color: var(--accent); + font-family: var(--font-mono); + font-size: 0.48em; + font-weight: 620; + line-height: 1.25; + letter-spacing: -0.025em; + overflow-wrap: anywhere; +} + +.registry-session-facts { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + column-gap: 24px; + max-width: 760px; + margin: 22px 0 0; +} + +.registry-session-facts > div { + display: grid; + gap: 4px; + padding: 10px 0; + border-top: 1px solid var(--line-soft); +} + +.registry-session-details dl > div { + display: grid; + grid-template-columns: minmax(120px, 0.3fr) minmax(0, 1fr); + gap: 18px; + padding: 12px 0; + border-top: 1px solid var(--line-soft); +} + +.registry-session-facts dt, +.registry-session-facts dd, +.registry-session-details dt, +.registry-session-details dd { + margin: 0; + font-size: 0.86rem; + line-height: 1.5; +} + +.registry-session-facts dt, +.registry-session-details dt { + color: var(--muted); +} + +.registry-session-facts dd, +.registry-session-details dd { + color: var(--text-soft); + overflow-wrap: anywhere; +} + +.registry-session-action { + display: grid; + gap: 17px; + max-width: 760px; + min-height: 0; + margin-top: 22px; + padding: 18px; + border: 1px solid var(--line-soft); + border-radius: 12px; + background: var(--panel-soft); + box-shadow: inset 0 1px 0 var(--line-inner-soft); +} + +.registry-session-action .registry-signer-status { + max-width: 64ch; + min-height: 1.5em; + color: var(--text-soft); + font-size: 0.9rem; + line-height: 1.5; +} + +.registry-session-buttons { + display: flex; + flex-wrap: wrap; + gap: 10px; + align-items: center; +} + +.registry-session-buttons .registry-button.primary { + min-width: 210px; + min-height: 48px; +} + +.registry-session-recovery { + display: none; + max-width: 760px; + margin-top: 18px; + padding: 16px 0; + border-top: 1px solid var(--line-soft); +} + +.registry-session-recovery p { + max-width: 64ch; + margin: 0; + color: var(--muted); + font-size: 0.88rem; + line-height: 1.55; +} + +.registry-session-recovery > div { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 14px; +} + +.registry-session-approval[data-state="error"] .registry-session-recovery { + display: block; +} + +.registry-session-approval[data-state="error"] .registry-session-action, +.registry-session-approval[data-state="error"] .registry-session-facts, +.registry-session-approval[data-state="error"] .registry-session-details, +.registry-session-approval[data-state="error"] .registry-session-journey, +.registry-session-approval[data-state="error"] [data-session-coordinate] { + display: none; +} + +.registry-session-approval[data-state="error"] { + grid-template-columns: minmax(0, 760px); +} + +.registry-session-details { + max-width: 760px; + margin-top: 20px; + color: var(--muted); + font-size: 0.82rem; +} + +.registry-session-details summary { + width: fit-content; + cursor: pointer; + font-weight: 620; +} + +.registry-session-details dl { + margin: 12px 0 0; +} + +.registry-session-journey { + margin-top: 12px; +} + +.registry-session-journey .registry-network-badge { + margin-bottom: 10px; +} + +.registry-session-journey li[data-state="complete"] > span { + border-color: color-mix(in srgb, var(--status-active) 42%, transparent); + background: color-mix(in srgb, var(--status-active) 8%, transparent); + color: var(--status-active); +} + +.registry-session-journey li[data-state="active"] > span { + border-color: var(--accent-line); + background: var(--accent-surface); + color: var(--accent); +} + +.registry-session-journey li[data-state="pending"] { + opacity: 0.66; +} + +.registry-session-approval[data-state="loading"] [data-session-coordinate], +.registry-session-approval[data-state="loading"] .registry-session-facts dd, +.registry-session-approval[data-state="loading"] .registry-session-action output { + color: transparent; + border-radius: 3px; + background: linear-gradient(90deg, var(--panel-soft), var(--surface-hover), var(--panel-soft)); + background-size: 220% 100%; + animation: registry-skeleton 1.4s var(--ease-standard) infinite; +} + +.registry-session-approval[data-state="authorised"] .registry-session-action, +.registry-session-approval[data-state="review_pending"] .registry-session-action { + border-top-color: color-mix(in srgb, var(--status-active) 30%, var(--line-soft)); +} + +.registry-manual-publish { + display: grid; + grid-template-columns: minmax(150px, 0.22fr) minmax(0, 1fr); + column-gap: clamp(32px, 5vw, 64px); + gap: 0; + animation: registry-panel-enter 220ms var(--ease-quick) both; +} + +.registry-manual-publish-head { + grid-column: 1 / -1; + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(270px, 0.48fr); + gap: 18px clamp(36px, 5vw, 64px); + align-items: end; + padding: 24px 0 30px; + border-bottom: 1px solid var(--line-soft); +} + +.registry-manual-publish-head .registry-text-button { + grid-column: 1 / -1; + width: fit-content; + margin: 0 0 2px; + padding: 0; +} + +.registry-manual-publish-intro { + min-width: 0; + max-width: 64ch; +} + +.registry-manual-publish-head h2 { + margin: 0 0 10px; + color: var(--text); + font-size: clamp(1.65rem, 2.4vw, 2.1rem); + line-height: 1.08; + letter-spacing: -0.035em; +} + +.registry-manual-publish-intro > p:last-child { + margin: 0; + color: var(--muted); + font-size: 0.94rem; + line-height: 1.58; +} + +.registry-manual-index { + grid-column: 1; + grid-row: 2 / span 4; + position: sticky; + top: 138px; + align-self: start; + padding-top: 36px; +} + +.registry-manual-index ol { + display: grid; + gap: 0; + margin: 0; + padding: 0; + list-style: none; +} + +.registry-manual-index a { + display: grid; + grid-template-columns: 24px minmax(0, 1fr); + gap: 10px; + align-items: baseline; + min-height: 48px; + padding: 13px 0; + border-top: 1px solid var(--line-soft); + color: var(--muted); + text-decoration: none; + transition: + border-color var(--duration-fast) var(--ease-standard), + color var(--duration-fast) var(--ease-standard), + transform var(--duration-fast) var(--ease-quick); +} + +.registry-manual-index li:first-child a { + border-top-color: var(--accent); + color: var(--text); +} + +.registry-manual-index a:hover, +.registry-manual-index a:focus-visible { + border-top-color: var(--accent); + color: var(--text); +} + +.registry-manual-index a:active { + transform: translateY(1px); +} + +.registry-manual-index span { + color: var(--accent); + font-family: var(--font-mono); + font-size: 0.72rem; + font-weight: 650; +} + +.registry-manual-index strong { + font-size: 0.82rem; + font-weight: 650; + line-height: 1.35; +} + +.registry-submit-intro { order: 0; } +.registry-submit-boundary { order: 1; } +.registry-manual-index { order: 2; } +.registry-authorisation-session { order: 1; } +.registry-builder { order: 3; } +.registry-submit-preflight { order: 4; } +.registry-authorisation-card { order: 5; } +.registry-publish-step { order: 6; } +.registry-wallet-dialog { order: 7; } + +.registry-builder[hidden], +.registry-submit-preflight[hidden], +.registry-submit-boundary[hidden], +.registry-authorisation-session[hidden], +.registry-authorisation-paths[hidden], +.registry-manual-auth[hidden] { + display: none; +} + +.registry-submit-intro { + position: relative; + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(220px, 0.32fr); + gap: clamp(28px, 5vw, 64px); + align-items: center; + padding: 18px 0 24px; + overflow: hidden; + border: 0; + border-bottom: 1px solid var(--line-soft); + border-radius: 0; + background: transparent; + animation: registry-reveal 280ms var(--ease-quick) both; +} + +.registry-submit-intro[hidden] { + display: none; +} + +.registry-submit-intro.is-leaving { + animation: registry-intro-leave 180ms var(--ease-standard) both; +} + +.registry-submit-intro h2 { + margin: 0; + color: var(--text); + font-size: clamp(1.35rem, 1.9vw, 1.68rem); + line-height: 1.08; + letter-spacing: -0.025em; + text-wrap: balance; +} + +.registry-submit-intro-copy > p:last-child { + max-width: 58ch; + margin: 7px 0 0; + color: var(--muted); + font-size: 0.9rem; + line-height: 1.55; + text-wrap: pretty; +} + +.registry-intro-dismiss { + justify-self: end; + min-height: 36px; + padding: 0 12px; + border: 1px solid var(--control-border); + border-radius: var(--control-radius); + background: var(--control-surface); + color: var(--control-text); + cursor: pointer; + font: inherit; + font-size: 0.8rem; + font-weight: 600; + transition: + border-color var(--duration-fast) var(--ease-standard), + color var(--duration-fast) var(--ease-standard), + transform var(--duration-fast) var(--ease-quick); +} + +.registry-intro-dismiss:hover { + border-color: var(--control-border-hover); + background: var(--control-surface-hover); + color: var(--control-text-hover); +} + +.registry-intro-dismiss:active { + transform: translateY(1px); +} + +.registry-submit-boundary { + display: grid; + gap: 7px; + align-self: stretch; + padding: 3px 0 3px 20px; + border-left: 2px solid var(--accent-line); + color: var(--muted); + font-size: 0.82rem; + line-height: 1.52; +} + +.registry-submit-boundary strong { + color: var(--text); + font-weight: 700; +} + +.registry-authorisation-session { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(290px, 0.55fr); + gap: clamp(28px, 5vw, 64px); + align-items: end; + padding: 28px 0 30px; + border-bottom: 1px solid var(--line-soft); +} + +.registry-authorisation-session h2 { + max-width: 24ch; + margin: 3px 0 8px; + color: var(--text); + font-size: clamp(1.35rem, 2vw, 1.7rem); + letter-spacing: -0.025em; + line-height: 1.1; +} + +.registry-authorisation-session p:last-child { + max-width: 64ch; + margin: 0; + color: var(--muted); + font-size: 0.88rem; + line-height: 1.55; +} + +.registry-authorisation-session dl { + display: grid; + gap: 9px; + margin: 0; +} + +.registry-authorisation-session dl div { + display: grid; + grid-template-columns: 92px minmax(0, 1fr); + gap: 12px; + padding-bottom: 8px; + border-bottom: 1px solid var(--line-soft); +} + +.registry-authorisation-session dt, +.registry-authorisation-session dd { + margin: 0; + font-size: 0.76rem; + line-height: 1.4; +} + +.registry-authorisation-session dt { + color: var(--muted); +} + +.registry-authorisation-session dd { + color: var(--text); + font-family: var(--font-mono); + overflow-wrap: anywhere; +} + +.registry-authorisation-session[data-state="loading"] dd { + min-height: 1em; + border-radius: 3px; + background: var(--line-soft); +} + +.registry-authorisation-session[data-state="error"] { + border-bottom-color: var(--accent-line); +} + +.registry-builder { + grid-column: 2; + padding: 36px 0; + border: 0; + border-bottom: 1px solid var(--line-soft); + border-radius: 0; + background: transparent; + animation: registry-panel-enter 260ms var(--ease-quick) both; +} + +.registry-builder-heading { + max-width: 62ch; + margin-bottom: 26px; +} + +.registry-builder-heading h2 { + margin: 0; + color: var(--text); + font-size: 1.3rem; + letter-spacing: -0.015em; +} + +.registry-builder-heading > p:last-child { + margin: 7px 0 0; + color: var(--muted); + font-size: 0.94rem; + line-height: 1.58; +} + +.registry-version-rule { + display: flex; + gap: 6px 12px; + align-items: baseline; + margin-top: 18px; + padding-left: 12px; + border-left: 2px solid var(--accent-line); + color: var(--muted); + font-size: 0.85rem; + line-height: 1.55; +} + +.registry-version-rule strong { + flex: 0 0 auto; + color: var(--text); +} + +.registry-artifact-kind-field { + max-width: 560px; + margin-bottom: 18px; +} + +.registry-profile-builder { + margin-top: 1rem; +} + +.registry-profile-builder > .registry-field-hint { + margin: 0.65rem 0 1rem; +} + +.registry-profile-builder [data-profile-group] { + margin-top: 0.75rem; +} + +.registry-profile-builder [data-profile-group][hidden] { + display: none; +} + +.registry-profile-preview { + margin-top: 1rem; + max-height: 22rem; + overflow: auto; +} + +.registry-profile-preview code { + white-space: pre; +} + +.registry-builder-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 20px; +} + +.registry-builder-grid-compact { + grid-template-columns: minmax(180px, 0.72fr) minmax(260px, 1.28fr); +} + +.registry-builder-field { + display: grid; + gap: 6px; +} + +.registry-builder-field span { + color: var(--text-soft); + font-family: var(--font-sans); + font-size: 0.85rem; + font-weight: 650; + letter-spacing: 0; +} + +.registry-manual-publish .registry-input, +.registry-manual-publish .registry-select { + min-height: 50px; +} + +:root[data-theme="light"] .registry-manual-publish .registry-input, +:root[data-theme="light"] .registry-manual-publish .registry-select, +:root[data-theme="light"] .registry-manual-publish .registry-textarea { + border-color: var(--line); + background: var(--panel-strong); +} + +#registry-submit-form, +#registry-preflight-stage, +#registry-authorisation-stage { + scroll-margin-top: 146px; +} + +.registry-manual-publish .registry-field-hint, +.registry-manual-publish .registry-field-error { + font-size: 0.82rem; + line-height: 1.5; +} + +.registry-submit-preflight, +.registry-publish-step { + grid-column: 2; + display: grid; + grid-template-columns: minmax(220px, 0.72fr) minmax(360px, 1.28fr); + gap: 24px; + align-items: center; + padding: 30px 0; + border: 0; + border-bottom: 1px solid var(--line-soft); + border-radius: 0; + background: transparent; +} + +.registry-submit-preflight h2 { + margin: 0; + color: var(--text); + font-size: 1.2rem; + font-weight: 650; + letter-spacing: -0.012em; +} + +.registry-submit-preflight p, +.registry-publish-step .registry-step-copy > p:not(.registry-publish-lock) { + margin: 6px 0 0; + color: var(--muted); + font-size: 0.91rem; + line-height: 1.56; +} + +.registry-step-num { + display: inline-block; + margin-bottom: 6px; + font-family: var(--font-mono); + font-size: 0.76rem; + font-weight: 700; + color: var(--accent); + letter-spacing: 0.06em; +} + +.registry-step-copy h2 { + margin: 0; + color: var(--text); + font-size: 1.1rem; + font-weight: 600; +} + +.registry-step-code { + display: grid; + gap: 10px; +} + +.registry-step-code code { + min-width: 0; + padding: 12px 14px; + border: 0; + border-left: 2px solid var(--accent-quiet); border-radius: 2px; + background: var(--surface-code-start); + color: var(--substrate-accent); + font-family: var(--font-mono); + font-size: 0.86rem; + line-height: 1.55; + white-space: pre-wrap; + overflow-wrap: anywhere; +} + +.registry-step-code .registry-copy { + justify-self: start; +} + +.registry-command-options { + padding-top: 2px; +} + +.registry-command-options summary { + width: fit-content; + color: var(--muted); + cursor: pointer; + font-size: 0.78rem; + font-weight: 600; +} + +.registry-command-options[open] summary { + margin-bottom: 14px; + color: var(--text); +} + +.registry-command-options .registry-builder-field { + max-width: 520px; +} + +.registry-authorisation-card { + grid-column: 2; + position: relative; + margin: 30px 0 38px; + padding: clamp(24px, 3.2vw, 34px); + overflow: hidden; + border: 1px solid var(--line-soft); + border-radius: var(--radius-overlay); + background: color-mix(in oklch, var(--panel-soft) 92%, var(--bg)); + box-shadow: none; +} + +.registry-authorisation-card::before { + content: none; +} + +.registry-authorisation-head { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(250px, 0.34fr); + align-items: flex-start; + gap: clamp(28px, 5vw, 64px); +} + +.registry-authorisation-head > div:first-child { + min-width: 0; +} + +.registry-authorisation-controls { + width: 100%; + min-width: 0; + display: grid; + gap: 9px; + justify-items: start; +} + +.registry-network-badge { + display: inline-flex; + align-items: center; + gap: 9px; + color: var(--text-soft); + font-family: var(--font-mono); + font-size: var(--text-xs); + letter-spacing: 0.035em; + text-transform: uppercase; +} + +.registry-authorisation-head h2 { + margin: 0; + color: var(--text); + max-width: 22ch; + font-size: clamp(1.45rem, 2.2vw, 1.9rem); + line-height: 1.05; + letter-spacing: -0.035em; + text-wrap: balance; +} + +.registry-authorisation-head p { + max-width: 62ch; + margin: 7px 0 0; + color: var(--muted); + font-size: 0.88rem; + line-height: 1.55; + text-wrap: pretty; +} + +.registry-signer { + display: grid; + gap: 15px; + padding-top: 16px; +} + +.registry-signer[hidden] { + display: none; +} + +.registry-authorisation-paths { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; + margin-top: 26px; +} + +.registry-authorisation-paths button { + display: grid; + gap: 5px; + min-width: 0; + min-height: 86px; + padding: 15px 16px; + border: 1px solid var(--line-soft); + border-radius: var(--radius-panel); + background: var(--panel-strong); + color: var(--text); + text-align: left; + cursor: pointer; + transition: + border-color var(--duration-fast) var(--ease-standard), + background-color var(--duration-fast) var(--ease-standard), + opacity var(--duration-fast) var(--ease-standard); +} + +.registry-authorisation-paths button:hover:not(:disabled) { + border-color: var(--accent-line); +} + +.registry-authorisation-paths button[aria-pressed="true"] { + border-color: var(--accent); + background: var(--accent-surface); + box-shadow: inset 3px 0 0 var(--accent); +} + +.registry-authorisation-paths button:disabled { + cursor: not-allowed; + opacity: 0.46; +} + +.registry-authorisation-paths span { + font-size: 0.88rem; + font-weight: 600; +} + +.registry-authorisation-paths small { + color: var(--muted); + font-size: 0.75rem; + line-height: 1.45; +} + +.registry-existing-capability-panel { + display: grid; + gap: 18px; + margin-top: 20px; + padding-top: 20px; + border-top: 1px solid var(--line-soft); + animation: registry-panel-enter 220ms var(--ease-quick) both; +} + +.registry-existing-capability-panel[hidden] { + display: none; +} + +.registry-existing-capability-panel h3 { + margin: 0; + color: var(--text); + font-size: 1rem; + letter-spacing: -0.01em; +} + +.registry-existing-capability-panel > div:first-child p { + max-width: 68ch; + margin: 6px 0 0; + color: var(--muted); + font-size: 0.82rem; + line-height: 1.5; +} + +.registry-existing-capability-control { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 12px; + align-items: end; +} + +.registry-existing-capability-control .registry-button { + min-height: 42px; +} + +.registry-capability-check-summary { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 1px; + margin: 0; + overflow: hidden; + border: 1px solid var(--line-soft); + border-radius: 8px; + background: var(--line-soft); +} + +.registry-capability-check-summary[hidden] { + display: none; +} + +.registry-capability-check-summary > div { + min-width: 0; + padding: 12px 14px; + background: var(--panel-strong); +} + +.registry-capability-check-summary dt { + color: var(--dim); + font-size: var(--text-xs); + font-weight: 650; + text-transform: none; + letter-spacing: 0; +} + +.registry-capability-check-summary dd { + margin: 5px 0 0; + overflow-wrap: anywhere; + color: var(--text-soft); + font-family: var(--font-mono); + font-size: 0.72rem; + line-height: 1.45; +} + +.registry-signer-principal { + max-width: 860px; +} + +.registry-inline-control { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 10px; + align-items: center; +} + +.registry-authorisation-progress { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0; + margin: 0; + padding: 24px 0 2px; + list-style: none; +} + +.registry-authorisation-progress li { + position: relative; + display: grid; + grid-template-columns: auto minmax(0, 1fr); + gap: 9px; + align-items: center; + min-width: 0; + min-height: 42px; + padding: 11px 14px 0 0; + border: 0; + border-top: 1px solid var(--line-soft); + border-radius: 0; + background: transparent; + color: var(--dim); + transition: + border-color var(--duration-med) var(--ease-standard), + color var(--duration-med) var(--ease-standard), + opacity var(--duration-med) var(--ease-standard); +} + +.registry-authorisation-progress li + li { + padding-left: 14px; +} + +.registry-authorisation-progress li span { + font-family: var(--font-mono); + font-size: var(--text-xs); + font-variant-numeric: tabular-nums; +} + +.registry-authorisation-progress li strong { + overflow: hidden; + font-size: 0.72rem; + font-weight: 650; + text-overflow: ellipsis; + white-space: nowrap; +} + +.registry-authorisation-progress li[data-state="active"] { + border-top-color: var(--accent); + color: var(--text); + background: transparent; + box-shadow: inset 0 1px 0 var(--accent); +} + +.registry-authorisation-progress li[data-state="active"] span { + color: var(--accent); +} + +.registry-authorisation-progress li[data-state="complete"] { + border-top-color: var(--status-active); + color: var(--status-active); + background: transparent; +} + +.registry-authorisation-progress li[data-state="complete"] span { + color: var(--status-active); +} + +.registry-workflow-guide { + display: grid; + grid-template-columns: 36px minmax(0, 1fr); + gap: 12px; + align-items: start; + margin: 12px 0 6px; + padding: 14px 0 16px; + border-bottom: 1px solid var(--line-soft); + transition: + border-color var(--duration-med) var(--ease-standard), + color var(--duration-med) var(--ease-standard), + opacity var(--duration-med) var(--ease-standard); +} + +.registry-workflow-guide > span { + display: grid; + place-items: center; + width: 32px; + height: 32px; + border: 1px solid var(--accent-line); + border-radius: 50%; + background: var(--accent-surface); + color: var(--accent); + font-family: var(--font-mono); + font-size: var(--text-xs); + font-variant-numeric: tabular-nums; +} + +.registry-workflow-guide > div { + display: grid; + gap: 3px; + min-width: 0; +} + +.registry-workflow-guide strong { + color: var(--text); + font-size: 0.82rem; + font-weight: 680; + line-height: 1.35; +} + +.registry-workflow-guide p { + max-width: 68ch; + margin: 0; + color: var(--soft); + font-size: 0.76rem; + line-height: 1.55; +} + +.registry-workflow-guide[data-state="complete"] { + border-bottom-color: color-mix(in srgb, var(--status-active) 28%, var(--line-soft)); +} + +.registry-workflow-guide[data-state="complete"] > span { + border-color: color-mix(in srgb, var(--status-active) 44%, transparent); + background: color-mix(in srgb, var(--status-active) 9%, transparent); + color: var(--status-active); +} + +.registry-progressive-panel { + display: grid; + gap: 10px; + padding: 16px; + border: 1px solid var(--line-soft); + border-radius: var(--radius); + background: var(--panel-strong); + animation: registry-panel-enter 260ms var(--ease-quick) both; +} + +.registry-progressive-panel[hidden], +.registry-signer-principal[hidden], +.registry-technical-details[hidden] { + display: none; +} + +.registry-textarea { + width: 100%; + min-width: 0; + min-height: 180px; + resize: vertical; + padding: 12px 14px; + border: 1px solid var(--line-soft); + border-radius: 7px; + background: var(--surface-code-start); + color: var(--text); + font-family: var(--font-mono); + font-size: 0.78rem; + line-height: 1.5; + box-sizing: border-box; +} + +.registry-textarea:focus { + outline: 0; + border-color: var(--accent-line); + box-shadow: 0 0 0 3px var(--accent-surface); +} + +.registry-signer-actions { + display: flex; + flex-wrap: wrap; + width: 100%; + gap: 10px; + align-items: center; + justify-content: flex-start; +} + +.registry-signer-actions [hidden] { + display: none; +} + +.registry-workflow-actions { + display: flex; + justify-content: flex-end; + padding-top: 18px; + border-top: 1px solid var(--line-soft); +} + +.registry-workflow-actions:has(> [hidden]:only-child), +.registry-workflow-actions:not(:has(> :not([hidden]))) { + display: none; +} + +.registry-workflow-actions .registry-workflow-primary { + width: auto; + min-width: min(100%, 220px); +} + +.registry-workflow-primary { + width: 100%; + min-width: 0; + min-height: var(--control-height-workflow); + padding-inline: 18px; + border-color: var(--color-transparent); + background: var(--control-primary); + color: var(--control-primary-text); + box-shadow: inset 0 1px 0 color-mix(in oklch, white 24%, transparent); +} + +.registry-workflow-primary:hover { + border-color: var(--color-transparent); + background: var(--control-primary-hover); + color: var(--control-primary-text); + transform: translateY(-1px); +} + +.registry-capability-command { + max-width: 920px; +} + +.registry-capability-create-intro { + display: grid; + gap: 4px; +} + +.registry-capability-create-intro strong { + color: var(--text); + font-size: 0.82rem; +} + +.registry-capability-create-intro span { + max-width: 72ch; + color: var(--muted); + font-size: 0.76rem; + line-height: 1.5; +} + +.registry-signer-status { + min-height: 24px; + padding: 0; + border: 0; + border-radius: 0; + background: transparent; + color: var(--muted); + font-family: var(--font-sans); + font-size: 0.8rem; + font-weight: 600; + overflow-wrap: anywhere; +} + +.registry-signer-status::before { + content: none; +} + +.registry-signer-status[data-tone="ok"] { + color: var(--accent); +} + +.registry-signer-status[data-tone="error"] { + color: var(--status-danger-text); +} + +.registry-signer-response { + margin: 0; + max-height: 280px; + overflow: auto; + padding: 12px 14px; + border: 1px solid var(--line-soft); + border-radius: 7px; + background: var(--surface-code-start); + color: var(--text-soft); + font-family: var(--font-mono); + font-size: 0.78rem; + line-height: 1.5; + white-space: pre-wrap; +} + +.registry-signer-response[hidden] { + display: none; +} + +.registry-technical-details { + padding-top: 12px; + border-top: 1px solid var(--line-soft); +} + +.registry-technical-details summary, +.registry-manual-auth summary { + width: fit-content; + color: var(--muted); + cursor: pointer; + font-family: var(--font-mono); + font-size: 0.74rem; +} + +.registry-technical-details[open] summary { + margin-bottom: 10px; + color: var(--accent); +} + +.registry-manual-auth { + padding-top: 12px; + border-top: 1px solid var(--line-soft); +} + +.registry-manual-auth[open] summary { + margin-bottom: 12px; + color: var(--accent); +} + +.registry-capability-command { + max-width: none; + animation: registry-panel-enter 220ms var(--ease-quick) both; +} + +.registry-publish-step { + transition: + border-color var(--duration-med) var(--ease-standard), + background-color var(--duration-med) var(--ease-standard), + opacity var(--duration-med) var(--ease-standard); +} + +.registry-publish-step[hidden] { + display: none; +} + +.registry-publish-step[data-state="locked"] { + display: none; +} + +.registry-publish-step[data-state="ready"] { + border-left: 3px solid var(--status-active); + padding-left: 24px; + background: transparent; +} + +.registry-publish-lock { + color: var(--status-warning-text) !important; + font-weight: 600; +} + +.registry-text-button { + min-height: 40px; + margin-top: 6px; + padding: 0 4px; + border: 0; + background: transparent; + color: var(--control-text); + font: inherit; + font-size: 0.84rem; + font-weight: 600; + text-align: left; + text-decoration: none; + cursor: pointer; + touch-action: manipulation; + transition: + color var(--duration-fast) var(--ease-standard), + opacity var(--duration-fast) var(--ease-standard); +} + +.registry-existing-capability { + width: fit-content; + margin-top: 0; + color: var(--muted); + font-size: 0.78rem; +} + +.registry-text-button:hover { + color: var(--control-text-hover); + text-decoration: underline; + text-decoration-color: var(--accent-line); + text-decoration-thickness: 1px; + text-underline-offset: 3px; +} + +.registry-change-wallet { + margin-top: 0; + font-size: 0.78rem; +} + +.registry-wallet-dialog { + width: min(660px, calc(100vw - 32px)); + height: min(780px, calc(100dvh - 32px)); + max-height: min(780px, calc(100dvh - 32px)); + padding: 0; + overflow: hidden; + border: 1px solid var(--line); + border-radius: var(--radius-dialog); + background: var(--panel-popover); + color: var(--text); + box-shadow: var(--elev-3); + animation: registry-dialog-enter 240ms var(--ease-quick) both; +} + +.registry-wallet-dialog::backdrop { + background: color-mix(in oklch, var(--bg) 74%, transparent); + backdrop-filter: blur(12px) saturate(0.9); + animation: registry-backdrop-enter 200ms var(--ease-standard) both; +} + +.registry-wallet-dialog-shell { + display: grid; + grid-template-rows: auto auto auto minmax(0, 1fr) auto; + gap: 16px; + height: 100%; + padding: 26px; + box-sizing: border-box; +} + +.registry-wallet-dialog-head { + display: grid; + grid-template-columns: 44px minmax(0, 1fr) 44px; + gap: 10px; + align-items: start; +} + +.registry-wallet-dialog-head:has(.registry-wallet-back[hidden]) { + grid-template-columns: minmax(0, 1fr) 44px; +} + +.registry-wallet-dialog-head h2 { + margin: 0; + font-size: clamp(1.45rem, 3vw, 2rem); + line-height: 1; + letter-spacing: -0.035em; +} + +.registry-wallet-dialog-head .registry-label { + margin-bottom: 4px; +} + +.registry-wallet-back, +.registry-wallet-close { + width: 44px; + height: 44px; + display: grid; + place-items: center; + padding: 0; + border: 1px solid transparent; + border-radius: var(--control-radius-large); + background: transparent; + color: var(--muted); + cursor: pointer; + font: inherit; + font-size: 1.15rem; + translate: 0 0; + transition: + border-color var(--duration-fast) var(--ease-standard), + background-color var(--duration-fast) var(--ease-standard), + color var(--duration-fast) var(--ease-standard), + transform var(--duration-fast) var(--ease-quick); +} + +.registry-wallet-back[hidden] { + display: none; +} + +.registry-wallet-back span, +.registry-wallet-close span, +.registry-wallet-back svg, +.registry-wallet-close svg { + display: block; + width: 18px; + height: 18px; +} + +.registry-wallet-back:hover, +.registry-wallet-close:hover { + border-color: var(--control-border); + background: var(--control-surface-hover); + color: var(--control-text-hover); +} + +.registry-wallet-back:active, +.registry-wallet-close:active { + transform: scale(0.96); +} + +.registry-wallet-dialog-body { + margin: 0; + color: var(--muted); + max-width: 56ch; + font-size: 0.88rem; + line-height: 1.55; +} + +.registry-wallet-toolbar { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 12px; + align-items: center; +} + +.registry-wallet-toolbar[hidden] { + display: none; +} + +.registry-wallet-toolbar label { + min-width: 0; +} + +.registry-wallet-toolbar input { + width: 100%; + min-height: 44px; + padding: 0 14px; + border: 1px solid var(--line-soft); + border-radius: var(--radius-panel); + outline: 0; + background: var(--surface-select); + color: var(--text); + font: inherit; + font-size: 0.84rem; + transition: + border-color var(--duration-fast) var(--ease-standard), + box-shadow var(--duration-fast) var(--ease-standard); +} + +.registry-wallet-toolbar input:focus { + border-color: var(--accent-line); + box-shadow: 0 0 0 3px var(--accent-surface); +} + +.registry-wallet-toolbar > span { + min-width: 74px; + color: var(--dim); + font-family: var(--font-mono); + font-size: 0.68rem; + text-align: right; +} + +.registry-wallet-options { + display: grid; + gap: 18px; + min-height: 0; + overflow-y: auto; + padding-right: 7px; + overscroll-behavior: contain; + scrollbar-color: var(--accent-line) transparent; + scrollbar-width: thin; + mask-image: linear-gradient(to bottom, transparent 0, black 10px, black calc(100% - 14px), transparent 100%); + -webkit-mask-image: linear-gradient(to bottom, transparent 0, black 10px, black calc(100% - 14px), transparent 100%); +} + +.registry-wallet-group { + display: grid; + gap: 8px; +} + +.registry-wallet-group[hidden] { + display: none; +} + +.registry-wallet-group > div:first-child { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + min-height: 24px; + color: var(--dim); +} + +.registry-wallet-group > div:first-child strong { + color: var(--text-soft); + font-size: 0.76rem; + font-weight: 650; +} + +.registry-wallet-group > div:first-child span { + font-family: var(--font-mono); + font-size: var(--text-xs); +} + +.registry-wallet-group > div:last-child { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; +} + +.registry-wallet-group[data-group-mode="direct"] > div:last-child { + grid-template-columns: minmax(0, 1fr); +} + +.registry-wallet-options.is-entering > * { + animation: registry-option-enter 220ms var(--ease-quick) both; +} + +.registry-wallet-option { + width: 100%; + min-height: 64px; + display: grid; + grid-template-columns: 40px minmax(0, 1fr) auto; + gap: 12px; + align-items: center; + padding: 10px 12px; + border: 1px solid var(--line-soft); + border-radius: 12px; + background: var(--panel-soft); + color: var(--text); + cursor: pointer; + font: inherit; + text-align: left; + transition: + border-color var(--duration-fast) var(--ease-standard), + background-color var(--duration-fast) var(--ease-standard), + transform var(--duration-fast) var(--ease-quick); +} + +.registry-wallet-option[hidden] { + display: none; +} + +.registry-wallet-option[data-connect-mode="direct"] { + border-color: var(--accent-line); + background: var(--accent-surface); +} + +.registry-wallet-option:hover, +.registry-wallet-option:focus-visible { + border-color: var(--accent-line); + background: var(--accent-surface); + transform: translateY(-1px); } -.registry-home-columns h2 { - margin: 0; - color: var(--text); - font-size: 1.06rem; - font-weight: 600; - line-height: 1.25; - letter-spacing: -0.005em; - text-wrap: balance; +.registry-wallet-option:active { + transform: translateY(0) scale(0.99); } -.registry-home-columns p { - margin: 10px 0 14px; - color: var(--muted); - font-size: 0.92rem; - line-height: 1.6; +.registry-wallet-option img, +.registry-wallet-option-mark { + width: 42px; + height: 42px; + display: grid; + place-items: center; + border-radius: var(--radius-panel); + background: var(--panel-chip); + object-fit: cover; + color: var(--accent); + font-family: var(--font-mono); + font-weight: 700; } -.registry-home-featured { - position: relative; +.registry-wallet-option > span:nth-child(2) { + min-width: 0; display: grid; - grid-template-columns: minmax(0, 1fr) minmax(280px, 0.7fr) auto; - gap: 22px; - align-items: center; - margin-top: 16px; - padding: 24px 24px 24px 28px; + gap: 2px; +} + +.registry-wallet-option strong, +.registry-wallet-option small, +.registry-wallet-connecting strong, +.registry-wallet-connecting small { + display: block; +} + +.registry-wallet-option strong { overflow: hidden; + font-size: 0.9rem; + text-overflow: ellipsis; + white-space: nowrap; } -.registry-home-featured::before { - content: ""; - position: absolute; - inset: 0 auto 0 0; - width: 3px; - background: linear-gradient(180deg, var(--accent), var(--accent-warm)); +.registry-wallet-option small, +.registry-wallet-connecting small { + color: var(--dim); + font-size: 0.7rem; + line-height: 1.35; } -.registry-home-featured h2, -.registry-home-featured p { - margin: 0; +.registry-wallet-option-arrow { + color: var(--dim); + transition: transform var(--duration-fast) var(--ease-quick); } -.registry-home-featured h2 { - color: var(--text); - font-size: 1.25rem; - overflow-wrap: anywhere; +.registry-wallet-option:hover .registry-wallet-option-arrow { + color: var(--accent); + transform: translateX(2px); } -.registry-home-featured p { - margin-top: 7px; +.registry-wallet-empty, +.registry-wallet-connecting { + min-height: 76px; + display: flex; + align-items: center; + justify-content: center; + gap: 12px; + margin: 0; + padding: 16px; + border: 1px dashed var(--line-soft); + border-radius: var(--radius); color: var(--muted); - line-height: 1.5; + font-size: 0.84rem; + text-align: center; } -.registry-home-featured dl { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 14px; +.registry-wallet-connecting { + justify-content: flex-start; + text-align: left; +} + +.registry-wallet-connecting > span { + width: 10px; + height: 10px; + flex: 0 0 auto; + border-radius: 999px; + background: var(--accent); + box-shadow: 0 0 0 0 var(--accent-line); + animation: registry-wallet-pulse 1.25s var(--ease-standard) infinite; +} + +.registry-wallet-dialog-note { margin: 0; + padding-top: 12px; + border-top: 1px solid var(--line-soft); + color: var(--dim); + font-size: 0.74rem; + line-height: 1.5; } -.registry-home-featured dl div { - min-width: 0; - padding: 12px 14px; - border: 1px solid var(--line-soft); - border-radius: 7px; - background: var(--surface-card-strong); +@keyframes registry-reveal { + from { opacity: 0; transform: translateY(8px); } + to { opacity: 1; transform: translateY(0); } } -.registry-home-featured dd { - font-family: var(--font-mono); - font-size: 0.82rem; - font-weight: 500; - overflow-wrap: anywhere; +@keyframes registry-intro-leave { + to { opacity: 0; transform: translateY(-5px); } } -.registry-home-featured code { - overflow-wrap: anywhere; +@keyframes registry-panel-enter { + from { opacity: 0; transform: translateY(6px); } + to { opacity: 1; transform: translateY(0); } } -.registry-app { - width: min(var(--max), calc(100% - var(--site-gutter))); - margin: 0 auto; - padding: 34px 0 88px; +@keyframes registry-dialog-enter { + from { opacity: 0; transform: translateY(10px) scale(0.985); } + to { opacity: 1; transform: translateY(0) scale(1); } } -.registry-topbar { - display: flex; - flex-wrap: wrap; - align-items: flex-end; - justify-content: space-between; - gap: 18px; - padding: 18px 0 20px; - border-bottom: 1px solid var(--line-soft); +@keyframes registry-backdrop-enter { + from { opacity: 0; } + to { opacity: 1; } } -.registry-topbar > div { - min-width: 0; - flex: 1 1 320px; +@keyframes registry-option-enter { + from { opacity: 0; transform: translateY(5px); } + to { opacity: 1; transform: translateY(0); } } -.registry-topbar-cta { - flex: 0 0 auto; - align-self: flex-end; +@keyframes registry-wallet-pulse { + 70% { box-shadow: 0 0 0 8px transparent; } + 100% { box-shadow: 0 0 0 0 transparent; } } -.registry-eyebrow, -.registry-label { - margin: 0 0 8px; +@media (max-width: 900px) { + .registry-api-group, + .registry-api-example { + grid-template-columns: 1fr; + gap: 16px; + } + + .registry-api-endpoint { + grid-template-columns: 54px minmax(0, 1fr) 20px; + } + + .registry-api-endpoint p { + grid-column: 2 / -1; + } + + .registry-builder-grid, + .registry-submit-preflight, + .registry-publish-step { + grid-template-columns: 1fr; + } + + .registry-browse { + grid-template-columns: 1fr; + gap: 14px; + padding-top: 22px; + } + + .registry-browse-filter { + flex: 1 1 auto; + min-width: 0; + max-width: none; + } + + .registry-browse-search, + .registry-service-state { + grid-column: 1; + } + + .registry-service-state { + min-height: 34px; + } + + .registry-submit-intro { + grid-template-columns: 1fr auto; + } + + .registry-manual-publish { + grid-template-columns: minmax(0, 1fr); + } + + .registry-manual-publish-head { + grid-column: 1; + grid-template-columns: minmax(0, 1fr); + gap: 18px; + } + + .registry-manual-index { + grid-column: 1; + grid-row: auto; + position: static; + padding-top: 24px; + } + + .registry-manual-index ol { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .registry-manual-index li + li a { + padding-left: 14px; + } + + .registry-builder, + .registry-submit-preflight, + .registry-authorisation-card, + .registry-publish-step { + grid-column: 1; + } + + .registry-authorisation-session { + grid-template-columns: 1fr; + align-items: start; + } + +} + +@media (max-width: 620px) { + .registry-browse-search { + padding: 6px; + border-radius: var(--radius-panel); + } + + .registry-browse-input input { + font-size: 0.92rem; + text-overflow: ellipsis; + } + + .registry-browse-filter-row { + gap: 8px; + flex-wrap: wrap; + } + + .registry-browse-filter, + .registry-browse-filter.registry-browse-intent { + flex: 1 1 100%; + min-width: 0; + max-width: none; + } + + .registry-filter-menu { + position: static; + width: 100%; + max-height: min(330px, 46dvh); + margin-top: 7px; + } + + .registry-browse-count { + width: 100%; + min-width: 0; + font-size: 0.7rem; + text-align: right; + } + + .registry-empty { + min-height: 0; + padding: 24px 20px; + flex-direction: column; + align-items: flex-start; + gap: 14px; + } + + .registry-empty-icon + .registry-empty-copy { + padding-left: 0; + border-left: 0; + } + + .registry-empty h2 { + font-size: 1.02rem; + } + + .registry-facets-label { + width: 100%; + } + + .registry-api-group-heading { + align-items: flex-start; + flex-direction: column; + gap: 8px; + } + + .registry-api-health { + justify-self: start; + } + + .registry-api-endpoint { + grid-template-columns: 50px minmax(0, 1fr); + } + + .registry-api-endpoint p { + grid-column: 1 / -1; + } + + .registry-api-endpoint > a { + display: none; + } + + .registry-submit-intro { + grid-template-columns: 1fr; + } + + .registry-intro-dismiss { + justify-self: start; + } + + .registry-authorisation-head { + display: grid; + gap: 14px; + } + + .registry-authorisation-controls { + width: 100%; + min-width: 0; + justify-items: stretch; + } + + .registry-authorisation-controls .registry-signer-status { + width: fit-content; + } + + .registry-authorisation-controls .registry-signer-actions { + display: grid; + justify-content: stretch; + } + + .registry-signer-status { + width: fit-content; + } + + .registry-signer-actions .registry-workflow-primary { + width: 100%; + justify-content: center; + } + + .registry-workflow-actions { + justify-content: stretch; + } + + .registry-workflow-actions .registry-workflow-primary { + width: 100%; + } + + .registry-inline-control { + grid-template-columns: 1fr; + } + + .registry-authorisation-progress { + gap: 10px; + } + + .registry-authorisation-progress li { + grid-template-columns: 1fr; + gap: 4px; + } + +} + +/* ===== Contextual package maintenance workspace ===== */ +.registry-maintainer { + width: min(100%, 1120px); + display: grid; + gap: 0; + margin: 0 auto; + padding-top: 12px; +} + +.registry-maintainer-back { + width: fit-content; + display: inline-flex; + align-items: center; + gap: 8px; + min-height: 42px; + color: var(--dim); + font-size: 0.8rem; + font-weight: 600; + text-decoration: none; +} + +.registry-maintainer-back:hover, +.registry-maintainer-back:focus-visible { color: var(--accent); - font-family: var(--font-mono); - font-size: 0.72rem; - font-weight: 700; - letter-spacing: 0; - text-transform: uppercase; } -.registry-topbar h1, -.registry-panel h2, -.registry-panel h3, -.registry-detail-title h2 { +.registry-maintainer-context { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(260px, 0.42fr); + align-items: end; + gap: clamp(28px, 6vw, 80px); + padding: 18px 0 28px; + border-bottom: 1px solid var(--line-soft); +} + +.registry-maintainer-package { + min-width: 0; +} + +.registry-maintainer-package > h1 { + display: block; + max-width: 24ch; margin: 0; color: var(--text); - letter-spacing: 0; + font-size: clamp(2rem, 3.4vw, 3.2rem); + font-weight: 720; + line-height: 1.02; + letter-spacing: -0.045em; + overflow-wrap: anywhere; + text-wrap: balance; } -.registry-topbar h1 { - font-size: clamp(2.1rem, 4vw, 3.4rem); - line-height: 1.02; - letter-spacing: -0.018em; +.registry-maintainer-package > p { + margin: 7px 0 0; + color: var(--dim); + font-family: var(--font-mono); + font-size: 0.76rem; + line-height: 1.5; } -.registry-topbar p:not(.registry-eyebrow), -.registry-panel > p, -.registry-empty p, -.registry-body-copy { - max-width: 760px; - margin: 12px 0 0; +.registry-maintainer-description { + max-width: 40ch; + margin: 0 0 3px; color: var(--muted); - line-height: 1.58; + font-size: 0.88rem; + line-height: 1.6; +} + +.registry-maintainer-workbench { + display: grid; + grid-template-columns: minmax(0, 1.12fr) minmax(350px, 0.72fr); + gap: clamp(36px, 6vw, 76px); + align-items: start; + padding-top: 34px; } -.registry-panel, -.registry-package-row, -.registry-side-card, -.registry-action-card, -.registry-table-wrap, -.registry-form-panel, -.registry-empty { +.registry-maintainer-tasks { + display: grid; + gap: 28px; min-width: 0; - border: 1px solid var(--line-soft); - border-radius: var(--radius); - background: var(--panel-soft); } -.registry-tabs { - position: sticky; - top: 0; - z-index: var(--z-sticky); - display: flex; - gap: 6px; - padding: 12px 0; - background: var(--bg); +.registry-maintainer-options { border-bottom: 1px solid var(--line-soft); } -.registry-tabs a { - display: inline-flex; +.registry-maintainer-options summary { + min-height: 42px; + display: flex; align-items: center; - min-height: 40px; - padding: 0 13px; - border: 1px solid transparent; - border-radius: 7px; + justify-content: space-between; + gap: 16px; color: var(--muted); - font-weight: 700; - text-decoration: none; + cursor: pointer; + font-size: 0.78rem; + font-weight: 650; + list-style: none; } -.registry-tabs a:hover, -.registry-tabs a.active { - border-color: var(--accent-line); - background: var(--accent-surface); +.registry-maintainer-options summary::-webkit-details-marker { + display: none; +} + +.registry-maintainer-options summary::after { + content: "+"; color: var(--accent); + font-family: var(--font-mono); + font-size: 1rem; + font-weight: 500; + transition: transform var(--duration-fast) var(--ease-quick); } -/* Offline registry tab treatment inside the registry subnav. */ -.registry-tabs a.registry-tabs-soon { - color: var(--dim); - opacity: 0.78; - cursor: not-allowed; +.registry-maintainer-options[open] summary::after { + transform: rotate(45deg); } -.registry-tabs a.registry-tabs-soon:hover, -.registry-tabs a.registry-tabs-soon.active { - color: var(--dim); - background: transparent; - border-color: var(--line-soft); - opacity: 0.78; + +.registry-manage-context-fields { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 14px; + padding: 6px 0 22px; } -.registry-tabs-soon-tag { - margin-left: 8px; - padding: 2px 7px; +.registry-manage-remote-status { + display: block; + margin: -12px 0 0; + padding: 10px 12px; border: 1px solid var(--line-soft); - border-radius: 999px; - background: var(--panel-soft); + border-radius: 8px; color: var(--dim); - font-family: var(--font-mono); - font-size: 0.62rem; - font-weight: 700; - letter-spacing: 0.06em; - text-transform: uppercase; + font-size: 0.78rem; + line-height: 1.45; } -.registry-grid { - display: grid; - grid-template-columns: minmax(280px, 0.42fr) minmax(0, 1fr); - gap: 18px; - padding-top: 22px; +.registry-manage-remote-status[data-tone="success"] { + border-color: var(--accent-line); + color: var(--accent); } -/* Search-first browse layout: a single full-width search bar above the - package list. Replaces the old split filter-panel + list grid. */ -.registry-browse { - padding-top: 24px; - display: grid; - gap: 18px; +.registry-manage-remote-status[data-tone="error"] { + border-color: var(--status-danger-line); + color: var(--status-danger-text); } -.registry-browse-search { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 14px; +.registry-manage-remote-status[hidden] { + display: none; } -.registry-browse-input { - position: relative; - flex: 1 1 420px; +.registry-current-task { display: grid; - grid-template-columns: 22px minmax(0, 1fr); - align-items: center; - gap: 12px; - min-height: 52px; - padding: 0 18px; - border: 1px solid var(--line-soft); - border-radius: var(--radius); - background: var(--panel-strong); - color: var(--dim); - transition: border-color var(--duration-fast) var(--ease-standard); + gap: 6px; + padding: 0 0 20px 16px; + border-bottom: 1px solid var(--line-soft); + border-left: 3px solid var(--accent); } -.registry-browse-input svg { - width: 20px; - height: 20px; +.registry-current-task .registry-label { + margin: 0; } -.registry-browse-input input { - width: 100%; - min-width: 0; - border: 0; - outline: 0; - background: transparent; +.registry-current-task strong { color: var(--text); - font: inherit; - font-size: 1rem; + font-size: 1.04rem; } -.registry-browse-input input::placeholder { - color: var(--dim); +.registry-current-task p { + max-width: 64ch; + margin: 0; + color: var(--muted); + font-size: 0.82rem; + line-height: 1.5; } -.registry-browse-input:focus-within { - border-color: var(--accent-line); - color: var(--accent); - box-shadow: inset 0 0 0 1px var(--accent-line); +.registry-maintainer-task-menu { + border-bottom: 1px solid var(--line-soft); } -.registry-browse-count { - font-family: var(--font-mono); - font-size: 0.78rem; - color: var(--dim); +.registry-maintainer-task-menu > summary { + min-height: 44px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + color: var(--muted); + cursor: pointer; + font-size: 0.8rem; + font-weight: 650; + list-style: none; } -.registry-panel { - padding: 18px; +.registry-maintainer-task-menu > summary::-webkit-details-marker { + display: none; } -.registry-section-block { - margin-top: 18px; +.registry-maintainer-task-menu > summary::after { + content: "+"; + color: var(--accent); + font-family: var(--font-mono); + font-size: 1rem; + transition: transform var(--duration-fast) var(--ease-quick); } -.registry-table-block { - margin-top: 16px; +.registry-maintainer-task-menu[open] > summary::after { + transform: rotate(45deg); } -.registry-panel h2 { - font-size: 1.18rem; +.registry-maintainer-task-menu .registry-task-picker { + padding: 12px 0 24px; } -.registry-search-box { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - gap: 10px; - margin-top: 14px; +.registry-task-picker { + min-width: 0; + margin: 0; + padding: 0; + border: 0; } -.registry-input, -.registry-select { +.registry-task-picker legend { width: 100%; - min-height: 42px; - padding: 0 12px; - border: 1px solid var(--line-soft); - border-radius: 7px; - background: var(--surface-select); + margin: 0 0 18px; + padding: 0; color: var(--text); - font: inherit; + font-size: 1.15rem; + font-weight: 700; } -.registry-input:focus, -.registry-select:focus { - outline: 2px solid var(--accent-line); - outline-offset: 1px; +.registry-task-options { + display: grid; + gap: 24px; } -.registry-button, -.registry-copy { - min-height: 40px; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 8px; - padding: 0 12px; - border: 1px solid var(--line-soft); - border-radius: 7px; - background: var(--surface-button); - color: var(--text); - font: inherit; - font-size: 0.86rem; - font-weight: 600; - text-decoration: none; - cursor: pointer; - touch-action: manipulation; +.registry-task-cluster { + display: grid; + gap: 9px; } -.registry-button.primary { - border-color: var(--accent-line); - background: var(--accent-surface); - color: var(--accent); +.registry-task-cluster + .registry-task-cluster { + padding-top: 22px; + border-top: 1px solid var(--line-soft); } -.registry-button:hover, -.registry-copy:hover, -.registry-copy.is-copied { - border-color: var(--accent-line); - color: var(--accent); +.registry-task-cluster-label { + color: var(--dim); + font-family: var(--font-sans); + font-size: var(--text-xs); + font-weight: 650; + letter-spacing: 0; + text-transform: none; } -.registry-copy.is-copy-failed { - border-color: var(--substrate-accent); - color: var(--substrate-accent); +.registry-task-cluster-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; } -.registry-package-list { - display: grid; - gap: 10px; +.registry-task-cluster[data-task-tone="chain"] .registry-task-option:last-child:nth-child(odd) { + grid-column: 1 / -1; } -.registry-package-row { +.registry-task-option { position: relative; + min-width: 0; +} + +.registry-task-option input { + position: absolute; + width: 1px; + height: 1px; + opacity: 0; + pointer-events: none; +} + +.registry-task-option label { + min-height: 82px; display: grid; grid-template-columns: minmax(0, 1fr) auto; - gap: 14px; + align-content: center; align-items: start; - padding: 16px 16px 16px 18px; - color: inherit; - text-decoration: none; - overflow: hidden; + gap: 5px; + padding: 15px 16px; + border: 1px solid var(--line-soft); + border-radius: var(--radius-panel); + background: transparent; + cursor: pointer; transition: border-color var(--duration-fast) var(--ease-standard), background-color var(--duration-fast) var(--ease-standard), transform var(--duration-fast) var(--ease-quick); } -.registry-package-row::before { - content: ""; - position: absolute; - inset: 0 auto 0 0; - width: 3px; - background: var(--accent); - transform: scaleY(0); - transform-origin: center; - transition: transform var(--duration-med) var(--ease-standard); +.registry-task-cluster[data-task-tone="primary"] .registry-task-option label { + min-height: 106px; + padding: 20px; + border-color: var(--accent-line); } -.registry-package-row:hover { +.registry-task-cluster[data-task-tone="danger"] .registry-task-option label { + min-height: 72px; + border-style: dashed; +} + +.registry-task-option label:hover { + border-color: var(--accent-line); + background: var(--surface-hover); +} + +.registry-task-option input:focus-visible + label { + outline: 2px solid var(--accent-line); + outline-offset: 2px; +} + +.registry-task-option input:checked + label { border-color: var(--accent-line); background: var(--accent-surface); - transform: translateY(-1px); } -.registry-package-row:hover::before { - transform: scaleY(1); +.registry-task-option input:active + label { + transform: scale(0.99); +} + +.registry-task-option strong, +.registry-task-option span, +.registry-task-option i { + display: block; +} + +.registry-task-option strong { + grid-column: 1; + color: var(--text); + font-size: 0.92rem; + line-height: 1.3; +} + +.registry-task-option span { + grid-column: 1; + color: var(--muted); + font-size: 0.76rem; + line-height: 1.45; +} + +.registry-task-option i { + grid-column: 2; + grid-row: 1 / span 2; + color: var(--dim); + font-family: var(--font-mono); + font-size: var(--text-xs); + font-style: normal; +} + +.registry-task-option input:checked + label i { + color: var(--accent); +} + +.registry-task-cluster[data-task-tone="danger"] .registry-task-option input:checked + label { + border-color: var(--status-danger-line); + background: var(--status-danger-surface); +} + +.registry-task-cluster[data-task-tone="danger"] .registry-task-option input:checked + label i { + color: var(--status-danger-text); +} + +.registry-yank-fields { + display: grid; + gap: 14px; + margin-top: 20px; + padding-top: 20px; + border-top: 1px solid var(--line-soft); } -.registry-package-row[hidden] { +.registry-yank-fields[hidden], +[data-manage-reproduce-fields][hidden], +[data-manage-deployment-fields][hidden] { display: none; } -.registry-package-row strong, -.registry-detail-title strong { - color: var(--text); - overflow-wrap: anywhere; +.registry-yank-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; } -.registry-package-row p { - margin: 6px 0 0; +.registry-report-paths { + min-height: 112px; + resize: vertical; + font-family: var(--font-mono); + line-height: 1.55; +} + +.registry-yank-warning { + display: grid; + gap: 4px; + padding: 12px 14px; + border-left: 3px solid var(--status-danger-line); + background: var(--status-danger-surface); color: var(--muted); + font-size: 0.8rem; line-height: 1.5; } -.registry-badges { - display: flex; - flex-wrap: wrap; - gap: 7px; - margin-top: 10px; +.registry-yank-warning strong { + color: var(--text); } -.registry-badge, -.registry-status { - display: inline-flex; +.registry-maintainer-command { + position: sticky; + top: 94px; + min-width: 0; + display: grid; + gap: 16px; + padding: 24px; + border: 1px solid var(--line-soft); + border-radius: var(--radius-dialog); + background: var(--panel-soft); + box-shadow: var(--elev-1); +} + +.registry-maintainer-command[data-tone="danger"] { + border-color: var(--status-danger-line); +} + +.registry-maintainer-command-head { + display: flex; align-items: center; - min-height: 24px; - padding: 0 8px; + justify-content: space-between; + gap: 12px; +} + +.registry-maintainer-command-head .registry-label { + margin: 0; +} + +.registry-command-kind { + padding: 5px 8px; border: 1px solid var(--line-soft); - border-radius: 999px; - color: var(--text-soft); + border-radius: var(--radius-data); + color: var(--dim); font-family: var(--font-mono); - font-size: 0.66rem; - font-weight: 600; + font-size: var(--text-xs); + line-height: 1; } -.registry-status { +.registry-maintainer-command[data-tone="write"] .registry-command-kind { border-color: var(--accent-line); color: var(--accent); - text-transform: uppercase; } -/* Semantic status colors: encode registry state by colour so a single - package's identity is readable at a glance. */ -.registry-badge[data-kind="production"] { - border-color: var(--status-active); - color: var(--status-active); - background: var(--status-active-surface); +.registry-maintainer-command[data-tone="danger"] .registry-command-kind { + border-color: var(--status-danger-line); + color: var(--status-danger-text); } -.registry-badge[data-kind="network"] { - border-color: var(--status-info); - color: var(--status-info); - background: var(--status-info-surface); +.registry-maintainer-command[data-valid="false"] .registry-command-line code { + color: var(--muted); } -.registry-badge[data-kind="license"] { - border-color: var(--line-soft); - color: var(--status-stable); +.registry-manage-help { + margin: 0; + color: var(--muted); + font-size: 0.9rem; + line-height: 1.55; } -.registry-badge[data-kind="version"] { - border-color: var(--line-soft); - color: var(--text-soft); +.registry-manage-validation { + display: grid; + gap: 5px; + padding: 11px 12px; + border: 1px solid var(--status-danger-line); + border-radius: 8px; + color: var(--status-danger-text); + font-size: 0.78rem; + line-height: 1.45; } -.registry-status[data-state="active"] { - border-color: var(--status-active); - color: var(--status-active); - background: var(--status-active-surface); +.registry-manage-validation p { + margin: 0; } -.registry-status[data-state="source"] { - border-color: var(--line-soft); - color: var(--status-source); - background: var(--status-source-surface); +.registry-manage-validation[hidden] { + display: none; } -.registry-meta { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 8px; - margin: 16px 0 0; +.registry-manage-note { + margin: 0; + padding-top: 14px; + border-top: 1px solid var(--line-soft); + color: var(--dim); + font-size: 0.84rem; + line-height: 1.55; } -.registry-meta div { - min-width: 0; - padding: 12px; - border: 1px solid var(--line-soft); - border-radius: 7px; - background: var(--panel-strong); +/* ===== MVR-style package detail (SinglePackageHeader + Sidebar) ===== */ +.registry-pkg { + padding-top: 0; } -.registry-detail { +.registry-pkg-header { display: grid; - grid-template-columns: minmax(0, 1fr) minmax(290px, 0.38fr); + grid-template-columns: auto minmax(0, 1fr) auto; gap: 18px; - padding-top: 22px; + align-items: center; + padding: 8px 0 22px; + border-bottom: 1px solid var(--line-soft); } -.registry-detail-title { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 16px; - margin-bottom: 18px; +.registry-pkg-mark { + width: 56px; + height: 56px; + border: 1px solid var(--line-soft); + border-radius: var(--radius); + background: var(--panel-soft); + position: relative; + flex-shrink: 0; } -.registry-command-line { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - gap: 10px; - align-items: center; - margin-top: 16px; +.registry-pkg-mark::before, +.registry-pkg-mark::after { + content: ""; + position: absolute; + inset: 12px; + border: 1px solid var(--accent-line); + border-radius: var(--radius-data); +} + +.registry-pkg-mark::after { + inset: 20px 25px; + border: 0; + border-radius: 0; + background: var(--accent); } -.registry-code, -.registry-command-line code, -.registry-action-card code { +.registry-pkg-titles { min-width: 0; - padding: 10px 12px; - border: 1px solid var(--line-soft); - border-radius: 7px; - background: var(--panel-strong); - color: var(--substrate-accent); +} + +.registry-pkg-titles h1 { + margin: 0; + color: var(--text); + font-size: clamp(1.6rem, 3vw, 2.2rem); + letter-spacing: -0.018em; + overflow-wrap: anywhere; +} + +.registry-pkg-meta { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; + margin: 8px 0 0; + color: var(--dim); font-family: var(--font-mono); - font-size: 0.78rem; + font-size: 0.82rem; +} + +.registry-pkg-meta code { + color: var(--text-soft); overflow-wrap: anywhere; } -.registry-table-wrap { - overflow: auto; +.registry-pkg-hash-copy { + width: var(--control-height-dense); + height: var(--control-height-dense); + min-height: 0; + padding: 0; + border-radius: 5px; } -.registry-table { - width: 100%; - border-collapse: collapse; - font-size: 0.9rem; +.registry-pkg-desc { + margin: 12px 0 0; + max-width: 680px; + color: var(--muted); + line-height: 1.6; } -.registry-table th, -.registry-table td { - padding: 12px; +.registry-use-summary { + display: grid; + grid-template-columns: minmax(120px, 0.22fr) minmax(0, 1fr); + gap: clamp(18px, 4vw, 52px); + margin-top: 24px; + padding: 18px 0 20px 18px; + border-top: 1px solid var(--line-soft); border-bottom: 1px solid var(--line-soft); - text-align: left; - vertical-align: top; + border-left: 3px solid var(--accent); } -.registry-table th { - color: var(--dim); - font-family: var(--font-mono); - font-size: 0.66rem; - text-transform: uppercase; +.registry-use-summary[data-usage-state="needs_evidence"], +.registry-use-summary[data-usage-state="source_only"] { + border-left-color: var(--status-warning-line); } -.registry-table td { - color: var(--text-soft); +.registry-use-summary[data-usage-state="unavailable"] { + border-left-color: var(--status-danger-line); } -.registry-side-card, -.registry-action-card, -.registry-form-panel, -.registry-empty { - padding: 18px; +.registry-use-summary[data-usage-state="deprecated"], +.registry-use-summary[data-usage-state="hash_bound"] { + border-left-color: var(--status-info); } -.registry-side-card + .registry-side-card { - margin-top: 12px; +.registry-use-summary .registry-label { + margin: 3px 0 0; } -.registry-side-card h3, -.registry-action-card h3, -.registry-form-panel h2 { +.registry-use-summary h2 { margin: 0; color: var(--text); - font-size: 1rem; + font-size: clamp(1.15rem, 2vw, 1.45rem); + line-height: 1.2; + letter-spacing: -0.022em; } -.registry-side-card p, -.registry-action-card p, -.registry-form-panel p { - margin: 10px 0 0; +.registry-use-summary div > p { + max-width: 72ch; + margin: 7px 0 0; color: var(--muted); - line-height: 1.5; + font-size: 0.88rem; + line-height: 1.58; } -.registry-actions-grid { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 18px; - padding-top: 22px; +.registry-state-cluster { + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + gap: 7px; + align-self: flex-start; } -.registry-action-card { +.registry-pkg-body { display: grid; - gap: 14px; - grid-template-rows: auto 1fr auto auto; + grid-template-columns: minmax(0, 1fr) minmax(290px, 0.38fr); + gap: 18px; + padding-top: 22px; + align-items: start; } -.registry-form-grid { +.registry-pkg-main { + min-width: 0; display: grid; - grid-template-columns: minmax(0, 0.55fr) minmax(0, 0.45fr); gap: 18px; - padding-top: 22px; } -.registry-field-grid { +.registry-pkg-sidebar, +.registry-pkg-aside { display: grid; gap: 12px; - margin-top: 16px; + position: static; +} + +.registry-next-action { + border-color: var(--accent-line); +} + +.registry-next-action h2 { + margin: 7px 0 0; + color: var(--text); + font-size: 1.12rem; + line-height: 1.18; + letter-spacing: -0.018em; + text-wrap: balance; +} + +.registry-next-action > p:not(.registry-label) { + margin: 9px 0 16px; + color: var(--muted); + font-size: 0.82rem; + line-height: 1.5; +} + +.registry-next-action .registry-button { + width: 100%; + min-height: 40px; +} + +.registry-next-action output { + display: block; + min-height: 1.25em; + margin-top: 7px; + color: var(--muted); + font-size: 0.72rem; } -.registry-field-grid label { +.registry-maintainer-next { display: grid; gap: 6px; - color: var(--text-soft); - font-size: 0.88rem; - font-weight: 700; -} - -/* ===== MVR-style submit flow ===== */ -.registry-flow { - padding-top: 24px; - display: grid; - gap: 24px; + padding: 16px 2px 2px; + border-top: 1px solid var(--line-soft); } -.registry-flow-head { - max-width: 760px; +.registry-maintainer-next[hidden] { + display: none; } -.registry-flow-head h2 { - margin: 0; +.registry-maintainer-next strong { color: var(--text); - font-size: 1.6rem; - letter-spacing: -0.012em; + font-size: 0.9rem; } -.registry-flow-head p { - margin: 12px 0 0; +.registry-maintainer-next > span { color: var(--muted); - line-height: 1.6; + font-size: 0.78rem; + line-height: 1.5; } -.registry-builder { - padding: 22px; - border: 1px solid var(--line-soft); - border-radius: var(--radius); - background: var(--panel-soft); +.registry-maintainer-next a { + width: fit-content; + margin-top: 4px; + color: var(--accent); + font-size: 0.8rem; + font-weight: 650; + text-underline-offset: 3px; } -.registry-builder-grid { +.registry-facts { display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 16px; + gap: 0; + margin: 12px 0 0; } -.registry-builder-field { +.registry-facts > div { display: grid; - gap: 6px; + grid-template-columns: minmax(0, 1fr) auto; + gap: 16px; + padding: 10px 0; + border-top: 1px solid var(--line-soft); } -.registry-builder-field span { +.registry-facts dt { + color: var(--dim); + font-size: 0.78rem; +} + +.registry-facts dd { + margin: 0; color: var(--text-soft); font-family: var(--font-mono); - font-size: 0.72rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.04em; + font-size: 0.76rem; + text-align: right; } -.registry-steps { - list-style: none; - margin: 0; - padding: 0; +.registry-link-list { display: grid; - gap: 14px; + gap: 10px; + margin-top: 12px; +} + +.registry-link-list a { + color: var(--accent); + font-size: 0.84rem; + text-underline-offset: 3px; } -.registry-step-card { +.registry-pkg-sidebar .registry-meta { + grid-template-columns: 1fr; +} + +.registry-source-links { display: grid; - grid-template-columns: minmax(0, 1fr) minmax(320px, 0.85fr); - gap: 22px; - align-items: center; - padding: 22px; - border: 1px solid var(--line-soft); - border-radius: var(--radius); - background: var(--panel-soft); + gap: 10px; +} + +.registry-source-links a { + width: fit-content; + color: var(--accent); + font-weight: 600; + text-decoration-thickness: 1px; + text-underline-offset: 3px; +} + +.registry-pkg-install { position: relative; overflow: hidden; } -.registry-step-card::before { +.registry-pkg-install::before { content: ""; position: absolute; inset: 0 auto 0 0; width: 3px; background: linear-gradient(180deg, var(--accent), var(--accent-warm)); - opacity: 0.7; } -.registry-step-copy { - min-width: 0; -} +@media (max-width: 900px) { + .registry-pkg-header { + grid-template-columns: auto minmax(0, 1fr); + align-items: start; + } + + .registry-pkg-mark { + margin-top: 4px; + } + + .registry-state-cluster { + grid-column: 1 / -1; + justify-self: start; + justify-content: flex-start; + } + + .registry-pkg-body { + grid-template-columns: 1fr; + } -.registry-step-num { - display: inline-block; - margin-bottom: 8px; - font-family: var(--font-mono); - font-size: 0.72rem; - font-weight: 700; - color: var(--accent); - letter-spacing: 0.06em; } -.registry-step-copy h3 { - margin: 0; - color: var(--text); - font-size: 1.08rem; - font-weight: 600; +@media (max-width: 900px) { + .registry-maintainer-workbench { + grid-template-columns: 1fr; + } + + .registry-maintainer-command { + position: static; + } } -.registry-step-copy p { - margin: 8px 0 0; - color: var(--muted); - font-size: 0.92rem; - line-height: 1.6; +.registry-empty { + min-height: 148px; + padding: 30px 32px; + display: flex; + align-items: center; + border: 1px solid color-mix(in oklch, var(--line-soft) 78%, transparent); + border-radius: var(--radius-panel); + background: color-mix(in oklch, var(--panel-soft) 55%, transparent); + text-align: left; + box-shadow: none; } -.registry-step-code { - display: grid; - gap: 10px; +.registry-empty::after { + content: none; } -.registry-step-code code { +.registry-empty-copy { min-width: 0; - padding: 12px 14px; - border: 1px solid var(--line-soft); - border-radius: 7px; - background: var(--surface-code-start); - color: var(--substrate-accent); - font-family: var(--font-mono); - font-size: 0.8rem; - line-height: 1.5; - white-space: pre-wrap; - overflow-wrap: anywhere; } -.registry-step-code .registry-copy { - justify-self: start; +.registry-empty h2 { + margin: 0; + font-size: 1.08rem; + line-height: 1.25; + letter-spacing: -0.015em; } -.registry-signer { - display: grid; - gap: 18px; - padding: 22px; - border: 1px solid var(--line-soft); - border-radius: var(--radius); - background: var(--panel-soft); +.registry-empty p { + max-width: 58ch; + margin-top: 8px; + font-size: 0.9rem; } -.registry-signer-grid { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 16px; +.registry-empty .registry-button.secondary { + min-height: var(--control-height); + padding: 0 12px; + border-color: var(--control-border); + background: var(--control-surface); + color: var(--control-text); } -.registry-signer-api { - grid-column: 1 / -1; +.registry-empty .registry-button.secondary:hover { + border-color: var(--control-border-hover); + background: var(--control-surface-hover); + color: var(--control-text-hover); + transform: translateY(-1px); } -.registry-textarea { - width: 100%; - min-width: 0; - min-height: 220px; - resize: vertical; - padding: 12px 14px; - border: 1px solid var(--line-soft); - border-radius: 7px; - background: var(--surface-code-start); - color: var(--text); - font-family: var(--font-mono); - font-size: 0.78rem; - line-height: 1.5; - box-sizing: border-box; +.registry-empty-icon, +.registry-empty-icon svg { + width: 38px; + height: 38px; + flex: 0 0 auto; + color: var(--dim); + opacity: 0.7; } -.registry-textarea:focus { - outline: 0; - border-color: var(--accent-line); - box-shadow: 0 0 0 3px var(--accent-surface); +.registry-empty-icon + .registry-empty-copy { + padding-left: 18px; + border-left: 1px solid var(--line-soft); } -.registry-signer-actions { - display: flex; - flex-wrap: wrap; - gap: 10px; +.registry-empty-publish { + display: inline-flex; + align-items: center; + gap: 7px; + color: var(--accent); + font-size: 0.88rem; + font-weight: 650; + text-decoration: none; } -.registry-signer-status { - min-height: 22px; - color: var(--muted); - font-family: var(--font-mono); - font-size: 0.78rem; - overflow-wrap: anywhere; +.registry-empty-publish:hover { + color: var(--text); } -.registry-signer-status[data-tone="ok"] { - color: var(--accent); +@media (max-width: 900px) { + .registry-app { + padding-top: 10px; + } + + .registry-topbar { + grid-template-columns: 1fr; + } + + .registry-meta { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .registry-command-line, + .registry-command-line { + grid-template-columns: 1fr; + } + + .registry-package-row { + grid-template-columns: 40px minmax(0, 1fr) auto; + } + + .registry-tabs { + position: relative; + top: auto; + overflow-x: auto; + } + + .registry-tabs a, + .registry-button, + .registry-copy { + min-height: 44px; + } } -.registry-signer-status[data-tone="error"] { - color: var(--accent-warm); +@media (max-width: 520px) { + .registry-maintainer-context { + align-items: stretch; + flex-direction: column; + } + + .registry-manage-context-fields, + .registry-task-options, + .registry-yank-grid { + grid-template-columns: 1fr; + } + + .registry-task-option label { + min-height: 76px; + } + + .registry-maintainer-command { + padding: 16px; + } + + .registry-maintainer-command-head { + align-items: flex-start; + flex-direction: column; + } + + .registry-meta { + grid-template-columns: 1fr; + } } -.registry-signer-response { - margin: 0; - max-height: 280px; - overflow: auto; - padding: 12px 14px; - border: 1px solid var(--line-soft); - border-radius: 7px; - background: var(--surface-code-start); - color: var(--text-soft); - font-family: var(--font-mono); +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.registry-service-state { + display: flex; + align-items: center; + gap: 12px; + min-height: 44px; + padding: 10px 14px; + border-left: 2px solid var(--status-warning-line); + border-radius: var(--radius-data); + background: color-mix(in oklch, var(--status-warning-surface) 52%, transparent); + color: var(--muted); font-size: 0.78rem; - line-height: 1.5; - white-space: pre-wrap; + line-height: 1.45; } -.registry-signer-response[hidden] { - display: none; +.registry-service-retry { + min-height: 36px; + margin-left: auto; + padding: 0; + border: 0; + background: transparent; + color: var(--control-text); + font: inherit; + font-weight: 600; + cursor: pointer; + text-decoration: underline; + text-underline-offset: 3px; } -@media (max-width: 900px) { - .registry-builder-grid, - .registry-step-card, - .registry-signer-grid { - grid-template-columns: 1fr; - } +.registry-service-retry:hover { + color: var(--control-text-hover); + text-decoration-color: var(--accent-line); } -/* ===== MVR-style manage workspace (apps page pattern) ===== */ -.registry-manage { +.registry-browse-skeleton { display: grid; - grid-template-columns: minmax(260px, 0.32fr) minmax(0, 1fr); - gap: 18px; - padding-top: 24px; - align-items: start; + gap: 0; + border-top: 1px solid var(--line-soft); } -.registry-manage-list { - padding: 16px; - border: 1px solid var(--line-soft); - border-radius: var(--radius); - background: var(--panel-soft); - position: sticky; - top: 16px; +.registry-skeleton-row { + display: grid; + grid-template-columns: 40px minmax(0, 1fr); + gap: 16px; + align-items: start; + min-height: 104px; + padding: 16px 14px 16px 16px; + border-bottom: 1px solid var(--line-soft); } -.registry-manage-list-label { +.registry-skeleton-mark, +.registry-skeleton-copy i { display: block; - margin-bottom: 12px; - font-family: var(--font-mono); - font-size: 0.66rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.06em; - color: var(--dim); + background: linear-gradient(90deg, var(--panel-soft), var(--surface-hover), var(--panel-soft)); + background-size: 220% 100%; + animation: registry-skeleton 1.4s var(--ease-standard) infinite; } -.registry-manage-list ul { - list-style: none; - margin: 0; - padding: 0; - display: grid; - gap: 4px; +.registry-skeleton-mark { + width: 40px; + height: 40px; + border: 1px solid var(--line-soft); + border-radius: var(--radius-control); } -.registry-manage-item { - width: 100%; +.registry-skeleton-copy { display: grid; - grid-template-columns: minmax(0, 1fr) auto; - gap: 10px; - align-items: center; - padding: 11px 13px; - border: 0; - border-radius: calc(var(--radius) - 1px); - background: transparent; - color: var(--muted); - text-align: left; - cursor: pointer; - transition: - background-color var(--duration-fast) var(--ease-standard), - color var(--duration-fast) var(--ease-standard); + gap: 9px; + padding-top: 3px; } -.registry-manage-item:hover { - background: var(--surface-hover); - color: var(--text); +.registry-skeleton-copy i { + height: 9px; + border-radius: 3px; } -.registry-manage-item.active { - background: var(--accent-surface); - color: var(--accent); +.registry-skeleton-copy i:first-child { + width: min(240px, 42%); + height: 13px; } -.registry-manage-item-name { - font-family: var(--font-sans); - font-size: 0.9rem; - font-weight: 600; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; +.registry-skeleton-copy i:nth-child(2) { + width: min(520px, 76%); } -.registry-manage-item-meta { - font-family: var(--font-mono); - font-size: 0.7rem; - color: var(--dim); +.registry-skeleton-copy i:last-child { + width: min(300px, 52%); } -.registry-manage-item.active .registry-manage-item-meta { - color: var(--accent); +.registry-browse-skeleton[hidden], +.registry-package-list[hidden], +.registry-empty[hidden], +.registry-load-more[hidden], +.registry-button[hidden], +.registry-service-state[hidden], +.registry-service-retry[hidden] { + display: none; } -.registry-manage-detail { - min-width: 0; +.registry-load-more { + width: fit-content; + justify-self: center; + margin-top: 4px; } -.registry-manage-head { - padding-bottom: 18px; - border-bottom: 1px solid var(--line-soft); +.registry-row-arrow { + width: 18px; + height: 18px; + align-self: center; + color: var(--dim); + transition: color var(--duration-fast) var(--ease-standard), transform var(--duration-fast) var(--ease-quick); } -.registry-manage-head h2 { - margin: 0; - color: var(--text); - font-size: 1.5rem; - letter-spacing: -0.012em; - overflow-wrap: anywhere; +.registry-search-icon, +.registry-search-icon svg { + width: 18px; + height: 18px; } -.registry-manage-sub { - margin: 8px 0 0; - color: var(--dim); +.registry-package-row:hover .registry-row-arrow { + color: var(--accent); + transform: translateX(2px); +} + +.registry-package-detail { + padding-top: 24px; +} + +.registry-package-loading { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + align-items: center; + gap: 12px; + min-height: 180px; + color: var(--muted); font-family: var(--font-mono); font-size: 0.78rem; } -.registry-manage-body { - display: grid; - grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); - gap: 20px; - margin-top: 20px; +.registry-package-loading[hidden] { + display: none; } -.registry-manage-form { +.registry-package-skeleton { + grid-column: 1 / -1; display: grid; - gap: 14px; - align-content: start; + gap: 10px; } -.registry-manage-output { - padding: 20px; +.registry-package-skeleton span { + display: block; + height: 72px; border: 1px solid var(--line-soft); border-radius: var(--radius); - background: var(--surface-card-strong); - display: grid; - gap: 14px; - align-content: start; + background: linear-gradient(90deg, var(--panel-soft), var(--surface-hover), var(--panel-soft)); + background-size: 220% 100%; + animation: registry-skeleton 1.4s var(--ease-standard) infinite; } -.registry-manage-help { - margin: 0; - color: var(--muted); - font-size: 0.9rem; - line-height: 1.55; +@keyframes registry-skeleton { + to { background-position: -120% 0; } } -.registry-manage-note { - margin: 0; - padding-top: 14px; - border-top: 1px solid var(--line-soft); - color: var(--dim); - font-size: 0.84rem; - line-height: 1.55; +.registry-package-error { + max-width: 720px; } -.registry-manage-empty { - margin-top: 28px; +.registry-empty-actions { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 18px; } -/* ===== MVR-style package detail (SinglePackageHeader + Sidebar) ===== */ -.registry-pkg { - padding-top: 24px; +.registry-empty .registry-empty-actions .registry-button { + margin-top: 0; } -.registry-pkg-header { +.registry-evidence-list { display: grid; - grid-template-columns: auto minmax(0, 1fr) auto; - gap: 18px; - align-items: center; - padding: 8px 0 22px; - border-bottom: 1px solid var(--line-soft); + gap: 10px; + margin-top: 16px; } -.registry-pkg-mark { - width: 56px; - height: 56px; - border: 1px solid var(--accent-line); - border-radius: var(--radius); - background: - linear-gradient(135deg, var(--accent-surface), transparent 54%), - var(--panel-strong); - position: relative; - flex-shrink: 0; +.registry-evidence-row { + display: grid; + grid-template-columns: minmax(0, 1.15fr) minmax(210px, 0.62fr) auto; + gap: 20px; + align-items: start; + padding: 18px 0; + border-top: 1px solid var(--line-soft); + color: var(--muted); + font-size: 0.8rem; } -.registry-pkg-mark::before, -.registry-pkg-mark::after { - content: ""; - position: absolute; - inset: 12px; - border: 1px solid var(--accent); - border-radius: 5px; +.registry-evidence-copy strong { + color: var(--text); + font-size: 0.88rem; } -.registry-pkg-mark::after { - inset: 22px; - background: var(--accent); +.registry-evidence-copy p { + max-width: 62ch; + margin: 5px 0 0; + color: var(--muted); + line-height: 1.5; } -.registry-pkg-titles { - min-width: 0; +.registry-evidence-meta { + display: grid; + gap: 7px; + margin: 0; } -.registry-pkg-titles h1 { - margin: 0; - color: var(--text); - font-size: clamp(1.6rem, 3vw, 2.2rem); - letter-spacing: -0.018em; - overflow-wrap: anywhere; +.registry-evidence-meta > div { + display: grid; + grid-template-columns: minmax(72px, auto) minmax(0, 1fr); + gap: 10px; } -.registry-pkg-meta { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 8px; - margin: 8px 0 0; +.registry-evidence-meta dt { color: var(--dim); - font-family: var(--font-mono); - font-size: 0.82rem; + font-size: 0.7rem; } -.registry-pkg-meta code { +.registry-evidence-meta dd { + min-width: 0; + margin: 0; color: var(--text-soft); - overflow-wrap: anywhere; + text-align: right; } -.registry-pkg-hash-copy { - width: 24px; - height: 24px; - min-height: 0; - padding: 0; - border-radius: 5px; +.registry-evidence-meta code { + color: var(--text-soft); + overflow-wrap: anywhere; } -.registry-pkg-desc { - margin: 12px 0 0; - max-width: 680px; - color: var(--muted); - line-height: 1.6; +.registry-evidence-actions { + display: grid; + justify-items: end; + gap: 8px; } -.registry-pkg-header .registry-status { - align-self: flex-start; +.registry-evidence-actions .registry-copy { + min-height: 36px; + white-space: nowrap; } -.registry-pkg-body { - display: grid; - grid-template-columns: minmax(0, 1fr) minmax(290px, 0.38fr); - gap: 18px; - padding-top: 22px; - align-items: start; +.registry-evidence-actions a { + color: var(--accent); + font-size: 0.74rem; + text-underline-offset: 3px; } -.registry-pkg-main { - min-width: 0; +.registry-evidence-card { display: grid; - gap: 18px; + grid-template-columns: minmax(0, 1fr) auto auto auto; + align-items: center; + gap: 14px; + padding: 12px 14px; + border: 1px solid var(--line-soft); + border-radius: 7px; + background: var(--panel-strong); + color: var(--muted); + font-size: 0.8rem; } -.registry-pkg-sidebar { - display: grid; - gap: 12px; - position: sticky; - top: 16px; +.registry-evidence-card strong { + color: var(--text); } -.registry-pkg-install { - position: relative; - overflow: hidden; +.registry-evidence-card code { + overflow-wrap: anywhere; } -.registry-pkg-install::before { - content: ""; - position: absolute; - inset: 0 auto 0 0; - width: 3px; - background: linear-gradient(180deg, var(--accent), var(--accent-warm)); +@media (max-width: 720px) { + .registry-use-summary { + grid-template-columns: 1fr; + gap: 8px; + padding-left: 14px; + } + + .registry-responsive-table { + min-width: 0; + table-layout: fixed; + } + + .registry-responsive-table, + .registry-responsive-table tbody, + .registry-responsive-table tr, + .registry-responsive-table td { + display: block; + width: 100%; + max-width: 100%; + box-sizing: border-box; + } + + .registry-responsive-table thead { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + } + + .registry-responsive-table tbody { + display: grid; + gap: 10px; + padding: 10px; + } + + .registry-responsive-table tr { + padding: 8px 12px; + border: 1px solid var(--line-soft); + border-radius: 7px; + background: var(--panel-strong); + } + + .registry-responsive-table td { + display: grid; + grid-template-columns: minmax(92px, 0.36fr) minmax(0, 1fr); + align-items: start; + gap: 12px; + padding: 8px 0; + border-bottom: 1px solid var(--line-soft); + overflow-wrap: anywhere; + } + + .registry-responsive-table td:last-child { + border-bottom: 0; + } + + .registry-responsive-table td::before { + content: attr(data-label); + color: var(--dim); + font-family: var(--font-mono); + font-size: 0.68rem; + font-weight: 600; + text-transform: uppercase; + } + + .registry-value-copy { + width: fit-content; + margin: 7px 0 0; + grid-column: 2; + } + + .registry-evidence-card { + grid-template-columns: 1fr; + } + + .registry-evidence-row { + grid-template-columns: 1fr; + gap: 12px; + } + + .registry-evidence-meta dd { + text-align: left; + } + + .registry-evidence-actions { + grid-template-columns: auto 1fr; + align-items: center; + justify-items: start; + } } @media (max-width: 900px) { - .registry-pkg-header { - grid-template-columns: auto minmax(0, 1fr); + .registry-session-approval { + grid-template-columns: 1fr; + gap: 36px; + min-height: 0; + padding: 44px 0 56px; } - .registry-pkg-header .registry-status { - grid-column: 1 / -1; - justify-self: start; + .registry-publish-entry { + gap: 30px; + padding: 34px 0 52px; } - .registry-pkg-body { - grid-template-columns: 1fr; + .registry-session-journey { + padding: 28px 0 0; + border-top: 1px solid var(--line-soft); + border-left: 0; + background: transparent; } - .registry-pkg-sidebar { - position: static; + .registry-session-journey ol { + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 24px; } -} -@media (max-width: 900px) { - .registry-manage, - .registry-manage-body { + .registry-session-journey li { + min-height: 0; + padding-bottom: 0; + } + + .registry-session-journey li:not(:last-child)::after { + display: none; + } + + .registry-maintainer-context { grid-template-columns: 1fr; + align-items: start; + gap: 16px; } - .registry-manage-list { - position: static; + .registry-maintainer-description { + max-width: 58ch; } -} -.registry-empty { - margin-top: 18px; - text-align: left; + .registry-maintainer-workbench { + gap: 34px; + } } -@media (max-width: 900px) { - .registry-home { - width: min(100% - 28px, 920px); - padding-top: 32px; +@media (max-width: 620px) { + .registry-app { + --registry-route-start: 20px; } - .registry-home-search { - min-height: auto; - padding-top: 28px; + .registry-flow { + padding-top: 0; } - .registry-home-result, - .registry-home-band, - .registry-home-featured { - grid-template-columns: 1fr; + .registry-publish-entry, + .registry-session-approval { + gap: 30px; + padding: var(--registry-route-start) 0 44px; } - .registry-home-result-meta { - justify-content: flex-start; + .registry-session-approval h2 { + max-width: none; + font-size: clamp(1.9rem, 10vw, 2.35rem); + } + + .registry-session-approval h2 { + max-width: 14ch; + font-size: clamp(2rem, 9.4vw, 2.35rem); + line-height: 1.03; } - .registry-home-stats, - .registry-home-columns, - .registry-home-featured dl { + .registry-publish-command { grid-template-columns: 1fr; + gap: 12px; + margin-top: 24px; + padding: 16px; } - .registry-home-featured .registry-button { + .registry-publish-command .registry-button, + .registry-session-buttons .registry-button.primary { width: 100%; } - .registry-app { - padding-top: 28px; + .registry-publish-journey ol, + .registry-session-journey ol { + grid-template-columns: 1fr; + gap: 0; + } + + .registry-publish-journey li, + .registry-session-journey li { + min-height: 82px; + padding-bottom: 20px; + } + + .registry-publish-journey li:not(:last-child)::after, + .registry-session-journey li:not(:last-child)::after { + display: block; } - .registry-topbar, - .registry-grid, - .registry-detail, - .registry-actions-grid, - .registry-form-grid { + .registry-publish-journey li:not(:last-child)::after { + content: ""; + position: absolute; + top: 38px; + bottom: 0; + left: 16px; + width: 1px; + background: var(--line-soft); + } + + .registry-publish-journey { grid-template-columns: 1fr; + gap: 18px; + padding-top: 22px; } - .registry-meta { - grid-template-columns: repeat(2, minmax(0, 1fr)); + .registry-publish-journey li { + padding-right: 0; + padding-left: 0; + border-left: 0; } - .registry-search-box, - .registry-command-line, - .registry-package-row { + .registry-publish-journey li + li { + border-left: 0; + } + + .registry-session-facts > div, + .registry-session-details dl > div { grid-template-columns: 1fr; + gap: 4px; } - .registry-tabs { - overflow-x: auto; + .registry-session-facts { + grid-template-columns: 1fr; } - .registry-tabs a, - .registry-button, - .registry-copy { - min-height: 44px; + .registry-session-action { + padding: 16px; } -} -@media (max-width: 520px) { - .registry-home-search h1 { - font-size: clamp(2.7rem, 15vw, 4.2rem); + .registry-session-approval[data-state="pending"] .registry-session-action { + position: sticky; + bottom: 10px; + background: color-mix(in srgb, var(--bg) 94%, transparent); + backdrop-filter: blur(12px); + box-shadow: var(--elev-2); + } + + .registry-session-buttons { + display: grid; + grid-template-columns: 1fr; + } + + .registry-session-buttons .registry-text-button { + justify-self: start; + } + + .registry-manual-publish-head { + padding-top: 12px; + } + + .registry-manual-publish-head .registry-text-button { + margin-bottom: 0; + } + + .registry-submit-boundary { + padding-left: 16px; + } + + .registry-manual-index { + padding-top: 18px; } - .registry-home-input-wrap { + .registry-manual-index a { + grid-template-columns: 18px minmax(0, 1fr); + gap: 7px; min-height: 58px; - padding: 0 14px; + padding-block: 12px; } - .registry-home-actions .registry-button { - width: 100%; + .registry-manual-index li + li a { + padding-left: 10px; } - .registry-meta { - grid-template-columns: 1fr; + .registry-manual-index strong { + font-size: 0.75rem; } -} -/* ===== Registry Coming Soon ===== */ -/* - * Standalone placeholder for the public browse route while the registry - * index is offline. Submit / Manage / per-package routes remain available - * through direct URLs, but this route deliberately avoids the registry app - * shell so the Coming Soon message is not nested under tabs. - */ -.registry-coming-soon-page { - width: min(760px, calc(100% - var(--site-gutter))); - min-height: calc(100dvh - 68px); - margin: 0 auto; - padding: clamp(72px, 12svh, 128px) 0 clamp(64px, 10svh, 112px); - display: grid; - align-content: center; -} + .registry-submit-intro { + gap: 16px; + padding-bottom: 22px; + } -.registry-coming-soon { - display: grid; - justify-items: center; - text-align: center; -} + .registry-authorisation-card { + margin: 22px 0 30px; + padding: 22px; + border-radius: var(--radius-overlay); + } -.registry-coming-soon-card { - position: relative; - width: min(660px, 100%); - padding: clamp(34px, 5vw, 54px); - border: 1px solid var(--line-soft); - border-radius: var(--radius); - background: transparent; - display: grid; - justify-items: center; - gap: 18px; - box-shadow: 0 26px 90px var(--shadow-soft); -} + .registry-authorisation-head { + grid-template-columns: 1fr; + } -.registry-coming-soon-card::before { - content: ""; - position: absolute; - inset: 0 auto auto 50%; - width: min(148px, 42%); - height: 2px; - border-radius: 999px; - background: linear-gradient(90deg, var(--accent), var(--accent-warm)); - opacity: 0.82; - transform: translateX(-50%); -} + .registry-authorisation-paths, + .registry-capability-check-summary { + grid-template-columns: 1fr; + } -.registry-coming-soon-card::after { - content: none; -} + .registry-existing-capability-control { + grid-template-columns: 1fr; + } -.registry-coming-soon-card .registry-eyebrow { - margin: 0; -} + .registry-existing-capability-control .registry-button { + width: 100%; + } -.registry-coming-soon-title { - margin: 0; - max-width: 560px; - color: var(--text); - font-family: var(--font-sans); - font-weight: 700; - font-size: clamp(2.35rem, 5vw, 4.35rem); - line-height: 1.08; - letter-spacing: 0; - text-wrap: balance; -} + .registry-authorisation-progress { + grid-template-columns: 1fr; + } -.registry-coming-soon-lead { - margin: 0; - max-width: 540px; - color: var(--muted); - font-size: clamp(1.02rem, 1.4vw, 1.18rem); - line-height: 1.6; - text-wrap: pretty; -} + .registry-authorisation-progress li { + grid-template-columns: 30px minmax(0, 1fr); + min-height: 44px; + padding-right: 0; + } -.registry-coming-soon-actions { - display: flex; - flex-wrap: wrap; - align-items: center; - justify-content: center; - gap: 10px; - margin-top: 4px; -} + .registry-authorisation-progress li + li { + padding-left: 0; + } -@media (max-width: 600px) { - .registry-coming-soon-page { - width: min(100% - 40px, 760px); - align-content: start; - padding-top: 94px; + .registry-wallet-dialog { + width: calc(100vw - 16px); + height: calc(100dvh - 16px); + max-height: calc(100dvh - 16px); + border-radius: 16px; } -} -/* ---- Registry phase track ---- */ -.registry-phase-track { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 16px; - margin: 28px 0; - padding: 20px; - border: 1px solid var(--line-soft); - border-radius: var(--radius); - background: var(--panel); -} + .registry-wallet-dialog-shell { + gap: 14px; + padding: 18px; + } -.registry-phase { - display: grid; - gap: 4px; -} + .registry-wallet-dialog-head, + .registry-wallet-dialog-head:has(.registry-wallet-back[hidden]) { + grid-template-columns: minmax(0, 1fr) 40px; + } -.registry-phase-dot { - display: inline-flex; - align-items: center; - justify-content: center; - width: 28px; - height: 28px; - border-radius: 50%; - border: 1px solid var(--line-soft); - background: var(--panel-soft); - margin-bottom: 4px; -} + .registry-wallet-dialog-head:has(.registry-wallet-back:not([hidden])) { + grid-template-columns: 40px minmax(0, 1fr) 40px; + } -.registry-phase-dot svg { - width: 14px; - height: 14px; -} + .registry-wallet-back, + .registry-wallet-close { + width: 40px; + height: 40px; + } -.registry-phase-done .registry-phase-dot { - border-color: var(--accent-line); - background: var(--accent-surface); - color: var(--accent); -} + .registry-wallet-dialog-head h2 { + font-size: 1.55rem; + } -.registry-phase-active .registry-phase-dot { - border-color: var(--accent-warm-line); - background: var(--accent-warm-surface); - color: var(--accent-warm); - animation: phase-pulse 2.4s var(--ease-standard) infinite alternate; -} + .registry-wallet-dialog-body { + font-size: 0.82rem; + } -.registry-phase-pending .registry-phase-dot { - opacity: 0.4; -} + .registry-wallet-toolbar { + gap: 8px; + } -@keyframes phase-pulse { - from { box-shadow: 0 0 0 0 var(--accent-warm-line); } - to { box-shadow: 0 0 0 6px transparent; } -} + .registry-wallet-toolbar > span { + min-width: 62px; + font-size: var(--text-micro); + } -.registry-phase-label { - font-family: var(--font-mono); - font-size: 0.62rem; - font-weight: 700; - letter-spacing: 0.08em; - text-transform: uppercase; - color: var(--dim); -} + .registry-wallet-options { + gap: 16px; + } -.registry-phase-active .registry-phase-label { - color: var(--accent-warm); -} + .registry-wallet-group > div:last-child { + grid-template-columns: 1fr; + } -.registry-phase-title { - font-size: 0.82rem; - line-height: 1.2; - color: var(--text); -} + .registry-wallet-option { + min-height: 66px; + } -.registry-phase-pending .registry-phase-title { - color: var(--muted); -} + .registry-wallet-dialog-note { + font-size: 0.68rem; + } -.registry-phase-caption { - margin: 0; - font-size: 0.74rem; - line-height: 1.4; - color: var(--muted); -} + .registry-empty { + min-height: 0; + padding: 24px 20px; + } -/* ---- Registry notify card ---- */ -.registry-notify-card { - display: flex; - align-items: flex-start; - gap: 14px; - margin-top: 20px; - padding: 20px 24px; - border: 1px solid var(--line-soft); - border-radius: var(--radius); - background: var(--panel); -} + .registry-empty h2 { + font-size: 1.02rem; + } -.registry-notify-icon { - display: inline-flex; - flex-shrink: 0; - align-items: center; - justify-content: center; - width: 36px; - height: 36px; - border-radius: 50%; - border: 1px solid var(--accent-line); - background: var(--accent-surface); - color: var(--accent); -} + .registry-maintainer { + padding-top: 4px; + } -.registry-notify-icon svg { - width: 16px; - height: 16px; -} + .registry-maintainer-context { + padding: 12px 0 24px; + } -.registry-notify-body { - flex: 1; - min-width: 0; -} + .registry-maintainer-package > h1 { + max-width: 15ch; + font-size: clamp(2rem, 10vw, 2.8rem); + } -.registry-notify-label { - display: block; - margin-bottom: 8px; - font-size: 0.86rem; - font-weight: 600; - color: var(--text-soft); -} + .registry-maintainer-workbench { + gap: 30px; + padding-top: 26px; + } -.registry-notify-form { - display: flex; - gap: 8px; -} + .registry-manage-context-fields, + .registry-task-cluster-grid, + .registry-yank-grid { + grid-template-columns: 1fr; + } -.registry-notify-input { - flex: 1; - min-width: 0; - padding: 8px 12px; - border: 1px solid var(--line-soft); - border-radius: var(--radius-sm); - background: var(--panel-soft); - color: var(--text); - font-family: var(--font-sans); - font-size: 0.84rem; -} + .registry-task-options { + gap: 20px; + } -.registry-notify-input:focus { - outline: none; - border-color: var(--accent-line); -} + .registry-task-cluster + .registry-task-cluster { + padding-top: 18px; + } -.registry-notify-input::placeholder { - color: var(--dim); -} + .registry-task-cluster[data-task-tone="primary"] .registry-task-option label { + min-height: 94px; + } -.registry-notify-done { - margin: 8px 0 0; - color: var(--accent); - font-size: 0.84rem; -} + .registry-maintainer-command { + padding: 18px; + border-radius: var(--radius-overlay); + } -@media (max-width: 768px) { - .registry-phase-track { + .registry-maintainer-command .registry-command-line { grid-template-columns: 1fr; - gap: 12px; } - .registry-notify-form { - flex-direction: column; + .registry-maintainer-command .registry-copy { + width: 100%; + } +} + +@media (prefers-reduced-motion: reduce) { + ::view-transition-old(registry-header), + ::view-transition-new(registry-header), + ::view-transition-old(registry-route), + ::view-transition-new(registry-route), + .registry-package-skeleton span, + .registry-browse-skeleton span, + .registry-session-approval[data-state="loading"] [data-session-coordinate], + .registry-session-approval[data-state="loading"] .registry-session-facts dd, + .registry-session-approval[data-state="loading"] .registry-session-action output, + .registry-publish-entry, + .registry-session-approval, + .registry-manual-publish, + .registry-tabs a::after, + .registry-tabs::after, + .registry-submit-intro, + .registry-progressive-panel, + .registry-capability-command, + .registry-wallet-dialog, + .registry-wallet-dialog::backdrop, + .registry-wallet-options.is-entering > *, + .registry-wallet-connecting > span, + .registry-filter-menu, + .registry-empty { + animation: none; } } diff --git a/tsconfig.json b/tsconfig.json index 8bf91d3..6ad2881 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { "extends": "astro/tsconfigs/strict", "include": [".astro/types.d.ts", "**/*"], - "exclude": ["dist"] + "exclude": ["dist", "dist-testnet", "public/wasm"] }