From 3e45229e6510cb487500a990a557df2e9ae15943 Mon Sep 17 00:00:00 2001
From: moul <94029+moul@users.noreply.github.com>
Date: Fri, 3 Apr 2026 08:52:18 +0000
Subject: [PATCH 01/40] feat(gnoweb): add integrated playground, eval, and fork
views
---
gno.land/pkg/gnoweb/app.go | 4 +
gno.land/pkg/gnoweb/client.go | 10 +
gno.land/pkg/gnoweb/client_mock.go | 5 +
.../pkg/gnoweb/components/layout_header.go | 28 +-
.../pkg/gnoweb/components/layout_index.go | 2 +-
gno.land/pkg/gnoweb/components/layout_test.go | 11 +-
gno.land/pkg/gnoweb/components/view_eval.go | 89 ++++++
.../pkg/gnoweb/components/view_playground.go | 34 +++
.../pkg/gnoweb/components/views/eval.html | 81 ++++++
.../gnoweb/components/views/playground.html | 78 +++++
.../pkg/gnoweb/frontend/css/06-blocks.css | 271 ++++++++++++++++++
.../pkg/gnoweb/frontend/js/controller-eval.ts | 157 ++++++++++
.../frontend/js/controller-playground.ts | 201 +++++++++++++
gno.land/pkg/gnoweb/handler_http.go | 125 ++++++++
gno.land/pkg/gnoweb/handler_http_test.go | 4 +
gno.land/pkg/gnoweb/handler_playground.go | 168 +++++++++++
.../pkg/gnoweb/public/js/controller-eval.js | 1 +
.../gnoweb/public/js/controller-playground.js | 25 ++
gno.land/pkg/gnoweb/public/main.css | 4 +-
19 files changed, 1287 insertions(+), 11 deletions(-)
create mode 100644 gno.land/pkg/gnoweb/components/view_eval.go
create mode 100644 gno.land/pkg/gnoweb/components/view_playground.go
create mode 100644 gno.land/pkg/gnoweb/components/views/eval.html
create mode 100644 gno.land/pkg/gnoweb/components/views/playground.html
create mode 100644 gno.land/pkg/gnoweb/frontend/js/controller-eval.ts
create mode 100644 gno.land/pkg/gnoweb/frontend/js/controller-playground.ts
create mode 100644 gno.land/pkg/gnoweb/handler_playground.go
create mode 100644 gno.land/pkg/gnoweb/public/js/controller-eval.js
create mode 100644 gno.land/pkg/gnoweb/public/js/controller-playground.js
diff --git a/gno.land/pkg/gnoweb/app.go b/gno.land/pkg/gnoweb/app.go
index 174af39138f..8dae3ef87df 100644
--- a/gno.land/pkg/gnoweb/app.go
+++ b/gno.land/pkg/gnoweb/app.go
@@ -177,6 +177,10 @@ func NewRouter(logger *slog.Logger, cfg *AppConfig) (http.Handler, error) {
assetsHandler := cacheAssetHandler(AssetHandler())
mux.Handle(assetsBase, http.StripPrefix(assetsBase, assetsHandler))
+ // Handle playground API endpoints
+ mux.Handle("/_/api/eval", handlerPlaygroundEval(logger, adpcli, cfg.Domain, cfg.NodeRemote))
+ mux.Handle("/_/api/funcs", handlerPlaygroundFuncs(logger, adpcli))
+
// Handle status page
mux.Handle("/status.json", handlerStatusJSON(logger, rpcclient))
diff --git a/gno.land/pkg/gnoweb/client.go b/gno.land/pkg/gnoweb/client.go
index 21dbf79b344..6fab887ad77 100644
--- a/gno.land/pkg/gnoweb/client.go
+++ b/gno.land/pkg/gnoweb/client.go
@@ -48,6 +48,10 @@ type ClientAdapter interface {
// Doc retrieves the JSON doc suitable for printing from a
// specified package path.
Doc(ctx context.Context, path string) (*doc.JSONDocumentation, error)
+
+ // Eval evaluates a Gno expression via vm/qeval query.
+ // The data string should be in the format "gno.land/r/pkg.Expression(args)".
+ Eval(ctx context.Context, data string) ([]byte, error)
}
type rpcClient struct {
@@ -171,6 +175,12 @@ func (c *rpcClient) Doc(ctx context.Context, pkgPath string) (*doc.JSONDocumenta
return jdoc, nil
}
+// Eval evaluates a Gno expression via the vm/qeval ABCI query.
+func (c *rpcClient) Eval(ctx context.Context, data string) ([]byte, error) {
+ const qpath = "vm/qeval"
+ return c.query(ctx, qpath, []byte(data))
+}
+
// query sends a query to the RPC client and returns the response
// data.
func (c *rpcClient) query(ctx context.Context, qpath string, data []byte) ([]byte, error) {
diff --git a/gno.land/pkg/gnoweb/client_mock.go b/gno.land/pkg/gnoweb/client_mock.go
index 9d0d20f2402..2849282bfbf 100644
--- a/gno.land/pkg/gnoweb/client_mock.go
+++ b/gno.land/pkg/gnoweb/client_mock.go
@@ -138,6 +138,11 @@ func (m *MockClient) Doc(ctx context.Context, path string) (*doc.JSONDocumentati
return &doc.JSONDocumentation{Funcs: pkg.Functions}, nil
}
+// Eval evaluates a Gno expression (mock implementation).
+func (m *MockClient) Eval(_ context.Context, data string) ([]byte, error) {
+ return []byte("(mock eval: " + data + ")"), nil
+}
+
// Helper: check if package has a Render(string) string function.
func pkgHasRender(pkg *MockPackage) bool {
if len(pkg.Functions) == 0 {
diff --git a/gno.land/pkg/gnoweb/components/layout_header.go b/gno.land/pkg/gnoweb/components/layout_header.go
index 10916ad7c56..617bf6199b2 100644
--- a/gno.land/pkg/gnoweb/components/layout_header.go
+++ b/gno.land/pkg/gnoweb/components/layout_header.go
@@ -38,10 +38,12 @@ func StaticHeaderGeneralLinks() []HeaderLink {
}
func StaticHeaderDevLinks(u weburl.GnoURL, mode ViewMode, static bool) []HeaderLink {
- contentURL, sourceURL, helpURL := u, u, u
+ contentURL, sourceURL, helpURL, evalURL, forkURL := u, u, u, u, u
contentURL.WebQuery = url.Values{}
sourceURL.WebQuery = url.Values{"source": {""}}
helpURL.WebQuery = url.Values{"help": {""}}
+ evalURL.WebQuery = url.Values{"eval": {""}}
+ forkURL.WebQuery = url.Values{"fork": {""}}
contentLink := HeaderLink{
Label: "Content",
@@ -64,6 +66,20 @@ func StaticHeaderDevLinks(u weburl.GnoURL, mode ViewMode, static bool) []HeaderL
IsActive: isActive(u.WebQuery, "Actions"),
}
+ evalLink := HeaderLink{
+ Label: "Eval",
+ URL: evalURL.EncodeWebURL(),
+ Icon: "ico-tx-link",
+ IsActive: isActive(u.WebQuery, "Eval"),
+ }
+
+ forkLink := HeaderLink{
+ Label: "Fork",
+ URL: forkURL.EncodeWebURL(),
+ Icon: "ico-link",
+ IsActive: isActive(u.WebQuery, "Fork"),
+ }
+
switch {
case static:
return []HeaderLink{contentLink}
@@ -72,9 +88,9 @@ func StaticHeaderDevLinks(u weburl.GnoURL, mode ViewMode, static bool) []HeaderL
case mode == ViewModeUser:
return []HeaderLink{contentLink}
case mode == ViewModePackage:
- return []HeaderLink{contentLink, sourceLink}
+ return []HeaderLink{contentLink, sourceLink, forkLink}
default:
- return []HeaderLink{contentLink, sourceLink, actionsLink}
+ return []HeaderLink{contentLink, sourceLink, actionsLink, evalLink, forkLink}
}
}
@@ -93,11 +109,15 @@ func EnrichHeaderData(data HeaderData, mode ViewMode) HeaderData {
func isActive(webQuery url.Values, label string) bool {
switch label {
case "Content":
- return !webQuery.Has("source") && !webQuery.Has("help")
+ return !webQuery.Has("source") && !webQuery.Has("help") && !webQuery.Has("eval") && !webQuery.Has("fork")
case "Source":
return webQuery.Has("source")
case "Actions":
return webQuery.Has("help")
+ case "Eval":
+ return webQuery.Has("eval")
+ case "Fork":
+ return webQuery.Has("fork")
default:
return false
}
diff --git a/gno.land/pkg/gnoweb/components/layout_index.go b/gno.land/pkg/gnoweb/components/layout_index.go
index 245143c52b4..f1de4f98a6b 100644
--- a/gno.land/pkg/gnoweb/components/layout_index.go
+++ b/gno.land/pkg/gnoweb/components/layout_index.go
@@ -89,7 +89,7 @@ func IndexLayout(data IndexData) Component {
// Set dev mode based on view type and mode
switch data.BodyView.Type {
- case HelpViewType, SourceViewType, DirectoryViewType, StatusViewType:
+ case HelpViewType, SourceViewType, DirectoryViewType, StatusViewType, EvalViewType, PlaygroundViewType:
dataLayout.IsDevmodView = true
}
diff --git a/gno.land/pkg/gnoweb/components/layout_test.go b/gno.land/pkg/gnoweb/components/layout_test.go
index b387a44cd6e..84f9a33777f 100644
--- a/gno.land/pkg/gnoweb/components/layout_test.go
+++ b/gno.land/pkg/gnoweb/components/layout_test.go
@@ -112,7 +112,7 @@ func TestEnrichHeaderData(t *testing.T) {
enrichedData := EnrichHeaderData(data, ViewModeHome)
assert.NotEmpty(t, enrichedData.Links.General, "expected general links to be populated")
- assert.Len(t, enrichedData.Links.Dev, 3, "expected dev links with Actions for home mode")
+ assert.Len(t, enrichedData.Links.Dev, 5, "expected dev links with Actions, Eval, Fork for home mode")
}
func TestIsActive(t *testing.T) {
@@ -186,10 +186,12 @@ func TestStaticHeaderDevLinks_WithRealmMode(t *testing.T) {
// Test realm mode (default case)
links := StaticHeaderDevLinks(u, ViewModeRealm, false)
- assert.Len(t, links, 3, "expected Content, Source, and Actions links")
+ assert.Len(t, links, 5, "expected Content, Source, Actions, Eval, Fork links")
assert.Equal(t, "Content", links[0].Label)
assert.Equal(t, "Source", links[1].Label)
assert.Equal(t, "Actions", links[2].Label)
+ assert.Equal(t, "Eval", links[3].Label)
+ assert.Equal(t, "Fork", links[4].Label)
}
func TestStaticHeaderDevLinks_WithPackageMode(t *testing.T) {
@@ -201,9 +203,10 @@ func TestStaticHeaderDevLinks_WithPackageMode(t *testing.T) {
// Test package mode
links := StaticHeaderDevLinks(u, ViewModePackage, false)
- assert.Len(t, links, 2, "expected Content and Source links only")
+ assert.Len(t, links, 3, "expected Content, Source, Fork links")
assert.Equal(t, "Content", links[0].Label)
assert.Equal(t, "Source", links[1].Label)
+ assert.Equal(t, "Fork", links[2].Label)
}
func TestStaticHeaderDevLinks_StaticContent(t *testing.T) {
@@ -243,7 +246,7 @@ func TestEnrichHeaderData_WithRealmMode(t *testing.T) {
enriched := EnrichHeaderData(data, ViewModeRealm)
assert.Equal(t, "/r/test/pkg", enriched.RealmPath)
assert.Empty(t, enriched.Links.General)
- assert.Len(t, enriched.Links.Dev, 3, "expected Content, Source, and Actions links")
+ assert.Len(t, enriched.Links.Dev, 5, "expected Content, Source, Actions, Eval, Fork links")
}
func TestEnrichHeaderData_WithExplorerMode(t *testing.T) {
diff --git a/gno.land/pkg/gnoweb/components/view_eval.go b/gno.land/pkg/gnoweb/components/view_eval.go
new file mode 100644
index 00000000000..34acf93c1a2
--- /dev/null
+++ b/gno.land/pkg/gnoweb/components/view_eval.go
@@ -0,0 +1,89 @@
+package components
+
+import (
+ "go/token"
+
+ "github.com/gnolang/gno/gnovm/pkg/doc"
+)
+
+const EvalViewType ViewType = "eval-view"
+
+type EvalFuncInfo struct {
+ Name string
+ Signature string
+ Doc string
+ Params []*doc.JSONField
+}
+
+type EvalData struct {
+ Remote string
+ PkgPath string
+ Domain string
+ Functions []EvalFuncInfo
+}
+
+type EvalTocData struct {
+ Icon string
+ Items []HelpTocItem
+}
+
+type evalViewParams struct {
+ EvalData
+ Article ArticleData
+ ComponentTOC Component
+}
+
+func EvalView(data EvalData) *View {
+ tocData := EvalTocData{
+ Icon: "code",
+ Items: make([]HelpTocItem, len(data.Functions)),
+ }
+
+ for i, fn := range data.Functions {
+ tocData.Items[i] = HelpTocItem{
+ Link: "#func-" + fn.Name,
+ Text: fn.Name,
+ }
+ }
+
+ toc := NewTemplateComponent("ui/toc_generic", tocData)
+ content := NewTemplateComponent("renderEvalContent", data)
+
+ viewData := evalViewParams{
+ EvalData: data,
+ Article: ArticleData{
+ ComponentContent: content,
+ Classes: "c-eval-view",
+ },
+ ComponentTOC: toc,
+ }
+
+ return NewTemplateView(EvalViewType, "renderEval", viewData)
+}
+
+// BuildEvalFuncs extracts public non-method functions suitable for eval.
+func BuildEvalFuncs(jdoc *doc.JSONDocumentation) []EvalFuncInfo {
+ var funcs []EvalFuncInfo
+ for _, fn := range jdoc.Funcs {
+ if fn.Type != "" || !token.IsExported(fn.Name) {
+ continue
+ }
+ // Skip crossing functions (they mutate state)
+ if fn.Crossing {
+ continue
+ }
+
+ params := fn.Params
+ if len(params) >= 1 && params[0].Type == "realm" {
+ params = params[1:]
+ }
+
+ funcs = append(funcs, EvalFuncInfo{
+ Name: fn.Name,
+ Signature: fn.Signature,
+ Doc: fn.Doc,
+ Params: params,
+ })
+ }
+ return funcs
+}
diff --git a/gno.land/pkg/gnoweb/components/view_playground.go b/gno.land/pkg/gnoweb/components/view_playground.go
new file mode 100644
index 00000000000..74ae1adf1e9
--- /dev/null
+++ b/gno.land/pkg/gnoweb/components/view_playground.go
@@ -0,0 +1,34 @@
+package components
+
+const PlaygroundViewType ViewType = "playground-view"
+
+type PlaygroundData struct {
+ // InitialCode is pre-filled code (e.g. from fork)
+ InitialCode string
+ // ForkFrom is the package path this was forked from
+ ForkFrom string
+ // Remote is the RPC endpoint
+ Remote string
+ // ChainId is the current chain ID
+ ChainId string
+ // Domain is the node domain
+ Domain string
+}
+
+type playgroundViewParams struct {
+ PlaygroundData
+ Article ArticleData
+}
+
+func PlaygroundView(data PlaygroundData) *View {
+ content := NewTemplateComponent("ui/playground_editor", data)
+ viewData := playgroundViewParams{
+ PlaygroundData: data,
+ Article: ArticleData{
+ ComponentContent: content,
+ Classes: "c-playground-view",
+ },
+ }
+
+ return NewTemplateView(PlaygroundViewType, "renderPlayground", viewData)
+}
diff --git a/gno.land/pkg/gnoweb/components/views/eval.html b/gno.land/pkg/gnoweb/components/views/eval.html
new file mode 100644
index 00000000000..85b2a8f20d4
--- /dev/null
+++ b/gno.land/pkg/gnoweb/components/views/eval.html
@@ -0,0 +1,81 @@
+{{/* ===================================================================================
+LAYOUT - Eval view wrapper
+=================================================================================== */}}
+{{ define "renderEval" }}
+
+{{ with render .ComponentTOC }}
+{{ template "layout/aside" . }}
+{{ end }}
+
+
+{{ template "layout/article" .Article }}
+{{ end }}
+
+
+{{/* ===================================================================================
+UI - Inline Eval content
+=================================================================================== */}}
+{{ define "renderEvalContent" }}
+{{ $data := . }}
+
+
+
+
+
+
+
+
+
+
+
// Enter an expression above
+
+
+
+ {{ if .Functions }}
+
+
Quick Call
+
+ {{ range .Functions }}
+
+
+ {{ if .Doc }}{{ .Doc }}{{ end }}
+
+ {{ end }}
+
+
+ {{ end }}
+
+
+
+
+{{ end }}
diff --git a/gno.land/pkg/gnoweb/components/views/playground.html b/gno.land/pkg/gnoweb/components/views/playground.html
new file mode 100644
index 00000000000..4aa419df07d
--- /dev/null
+++ b/gno.land/pkg/gnoweb/components/views/playground.html
@@ -0,0 +1,78 @@
+{{/* ===================================================================================
+LAYOUT - Playground view
+=================================================================================== */}}
+{{ define "renderPlayground" }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
// Run code to see output here
+
+
+{{ end }}
+
+
+{{/* ===================================================================================
+UI - Playground editor component (inner content)
+=================================================================================== */}}
+{{ define "ui/playground_editor" }}
+
+
+
+{{ end }}
diff --git a/gno.land/pkg/gnoweb/frontend/css/06-blocks.css b/gno.land/pkg/gnoweb/frontend/css/06-blocks.css
index b18936cba52..0e2a3c1183d 100644
--- a/gno.land/pkg/gnoweb/frontend/css/06-blocks.css
+++ b/gno.land/pkg/gnoweb/frontend/css/06-blocks.css
@@ -2518,3 +2518,274 @@ main.dev-mode .b-toc a {
}
}
}
+
+/* ==========================================================================
+ PLAYGROUND - Scratch pad editor
+ ========================================================================== */
+.b-playground {
+ display: flex;
+ flex-direction: column;
+ gap: var(--g-space-2);
+ min-height: 70vh;
+}
+
+.b-playground-toolbar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: var(--g-space-2);
+ flex-wrap: wrap;
+}
+
+.b-playground-toolbar-left {
+ display: flex;
+ align-items: center;
+ gap: var(--g-space-2);
+}
+
+.b-playground-toolbar-right {
+ display: flex;
+ align-items: center;
+ gap: var(--g-space-1);
+}
+
+.b-playground-title {
+ font-size: var(--g-font-size-200);
+ font-weight: var(--g-font-semibold);
+}
+
+.b-playground-editor-area {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ border: 1px solid var(--s-color-border-default);
+ border-radius: var(--g-radius-2);
+ overflow: hidden;
+ min-height: 40vh;
+}
+
+.b-playground-tabs {
+ display: flex;
+ align-items: center;
+ gap: 0;
+ background-color: var(--s-color-bg-muted);
+ border-bottom: 1px solid var(--s-color-border-default);
+ overflow-x: auto;
+}
+
+.b-playground-tab {
+ padding: var(--g-space-1) var(--g-space-2);
+ border: none;
+ background: none;
+ cursor: pointer;
+ font-size: var(--g-font-size-50);
+ font-family: var(--g-font-mono);
+ color: var(--s-color-text-muted);
+ border-bottom: 2px solid transparent;
+ white-space: nowrap;
+
+ &:hover {
+ color: var(--s-color-text-base);
+ background-color: var(--s-color-bg-default);
+ }
+
+ &--active {
+ color: var(--s-color-text-base);
+ border-bottom-color: var(--s-color-border-brand);
+ font-weight: var(--g-font-semibold);
+ }
+}
+
+.b-playground-tab-add {
+ padding: var(--g-space-1) var(--g-space-2);
+ border: none;
+ background: none;
+ cursor: pointer;
+ font-size: var(--g-font-size-100);
+ color: var(--s-color-text-muted);
+
+ &:hover {
+ color: var(--s-color-text-base);
+ }
+}
+
+.b-playground-editor {
+ flex: 1;
+ display: flex;
+}
+
+.b-playground-code {
+ flex: 1;
+ width: 100%;
+ min-height: 300px;
+ padding: var(--g-space-2);
+ border: none;
+ background-color: var(--s-color-bg-default);
+ color: var(--s-color-text-base);
+ font-family: var(--g-font-mono);
+ font-size: var(--g-font-size-75);
+ line-height: 1.6;
+ resize: none;
+ tab-size: 4;
+ outline: none;
+}
+
+.b-playground-output {
+ border: 1px solid var(--s-color-border-default);
+ border-radius: var(--g-radius-2);
+ overflow: hidden;
+}
+
+.b-playground-output-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: var(--g-space-1) var(--g-space-2);
+ background-color: var(--s-color-bg-muted);
+ border-bottom: 1px solid var(--s-color-border-default);
+}
+
+.b-playground-output-title {
+ font-size: var(--g-font-size-50);
+ font-weight: var(--g-font-semibold);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+.b-playground-output-clear {
+ border: none;
+ background: none;
+ cursor: pointer;
+ font-size: var(--g-font-size-50);
+ color: var(--s-color-text-muted);
+
+ &:hover {
+ color: var(--s-color-text-base);
+ }
+}
+
+.b-playground-output-content {
+ padding: var(--g-space-2);
+ margin: 0;
+ font-family: var(--g-font-mono);
+ font-size: var(--g-font-size-75);
+ line-height: 1.6;
+ white-space: pre-wrap;
+ word-break: break-word;
+ min-height: 80px;
+ max-height: 300px;
+ overflow-y: auto;
+}
+
+/* ==========================================================================
+ EVAL - Expression evaluator
+ ========================================================================== */
+.b-eval {
+ display: flex;
+ flex-direction: column;
+ gap: var(--g-space-3);
+}
+
+.b-eval-header {
+ margin-bottom: var(--g-space-1);
+}
+
+.b-eval-form {
+ display: flex;
+ gap: var(--g-space-2);
+ align-items: flex-end;
+}
+
+.b-eval-form .b-input--full {
+ flex: 1;
+}
+
+.b-eval-result {
+ border: 1px solid var(--s-color-border-default);
+ border-radius: var(--g-radius-2);
+ overflow: hidden;
+}
+
+.b-eval-result-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: var(--g-space-1) var(--g-space-2);
+ background-color: var(--s-color-bg-muted);
+ border-bottom: 1px solid var(--s-color-border-default);
+ font-size: var(--g-font-size-50);
+ font-weight: var(--g-font-semibold);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+.b-eval-result-clear {
+ border: none;
+ background: none;
+ cursor: pointer;
+ font-size: var(--g-font-size-50);
+ color: var(--s-color-text-muted);
+
+ &:hover {
+ color: var(--s-color-text-base);
+ }
+}
+
+.b-eval-result-content {
+ padding: var(--g-space-2);
+ margin: 0;
+ font-family: var(--g-font-mono);
+ font-size: var(--g-font-size-75);
+ line-height: 1.6;
+ white-space: pre-wrap;
+ word-break: break-word;
+ min-height: 60px;
+ max-height: 400px;
+ overflow-y: auto;
+}
+
+.b-eval-func {
+ display: flex;
+ align-items: center;
+ gap: var(--g-space-2);
+}
+
+.b-eval-history-entry {
+ padding: var(--g-space-1-5);
+ border: 1px solid var(--s-color-border-default);
+ border-radius: var(--g-radius-1);
+
+ & + & {
+ margin-top: var(--g-space-1);
+ }
+}
+
+.b-eval-history-expr {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: var(--g-space-1);
+}
+
+.b-eval-history-rerun {
+ border: none;
+ background: none;
+ cursor: pointer;
+ font-size: var(--g-font-size-100);
+ color: var(--s-color-text-muted);
+
+ &:hover {
+ color: var(--s-color-text-base);
+ }
+}
+
+.b-eval-history-result {
+ margin: 0;
+ padding: var(--g-space-1);
+ font-family: var(--g-font-mono);
+ font-size: var(--g-font-size-50);
+ background-color: var(--s-color-bg-muted);
+ border-radius: var(--g-radius-1);
+ white-space: pre-wrap;
+ word-break: break-word;
+}
diff --git a/gno.land/pkg/gnoweb/frontend/js/controller-eval.ts b/gno.land/pkg/gnoweb/frontend/js/controller-eval.ts
new file mode 100644
index 00000000000..b9d9d86e806
--- /dev/null
+++ b/gno.land/pkg/gnoweb/frontend/js/controller-eval.ts
@@ -0,0 +1,157 @@
+import { BaseController } from "./controller.js";
+
+interface HistoryEntry {
+ expression: string;
+ result: string;
+ isError: boolean;
+}
+
+export class EvalController extends BaseController {
+ private _inputEl!: HTMLInputElement;
+ private _resultEl!: HTMLElement;
+ private _historyListEl!: HTMLElement;
+ private _history: HistoryEntry[] = [];
+
+ protected connect(): void {
+ this._inputEl = this.getTarget("input") as HTMLInputElement;
+ this._resultEl = this.getTarget("result") as HTMLElement;
+ this._historyListEl = this.getTarget("history-list") as HTMLElement;
+
+ this._inputEl?.focus();
+
+ this._inputEl?.addEventListener("keydown", (e: KeyboardEvent) => {
+ if (e.key === "ArrowUp" && this._history.length > 0) {
+ e.preventDefault();
+ this._inputEl.value =
+ this._history[this._history.length - 1].expression;
+ }
+ });
+ }
+
+ public evalExpression(event: Event): void {
+ event.preventDefault();
+ const expr = this._inputEl.value.trim();
+ if (!expr) return;
+ this._doEval(expr);
+ }
+
+ public quickCall(event: Event & { params?: Record }): void {
+ const funcName = event.params?.funcName as string;
+ const funcSig = event.params?.funcSig as string;
+
+ if (funcName) {
+ const paramMatch = funcSig?.match(/\(([^)]*)\)/);
+ const params = paramMatch ? paramMatch[1] : "";
+
+ if (params) {
+ this._inputEl.value = `${funcName}(${params
+ .split(",")
+ .map((p: string) => {
+ const parts = p.trim().split(/\s+/);
+ const type = parts[parts.length - 1];
+ return type === "string" ? '""' : "0";
+ })
+ .join(", ")})`;
+ this._inputEl.focus();
+ this._inputEl.setSelectionRange(
+ funcName.length + 1,
+ this._inputEl.value.length - 1,
+ );
+ } else {
+ this._inputEl.value = `${funcName}()`;
+ this._doEval(`${funcName}()`);
+ }
+ }
+ }
+
+ public clearResult(): void {
+ this._resultEl.textContent = "// Enter an expression above";
+ this._resultEl.classList.remove("u-color-danger");
+ }
+
+ private async _doEval(expression: string): Promise {
+ const remote = this.getValue("remote");
+ const pkgPath = this.getValue("pkg-path");
+
+ this._resultEl.textContent = "Evaluating...";
+ this._resultEl.classList.remove("u-color-danger");
+
+ try {
+ const data = `${pkgPath}.${expression}`;
+ const url = `${remote}/abci_query?path=vm%2fqeval&data=${btoa(data)}`;
+ const response = await fetch(url);
+
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}`);
+ }
+
+ const json = await response.json();
+ const responseBase = json.result.response.ResponseBase;
+
+ let result: string;
+ let isError: boolean;
+
+ if (responseBase.Data) {
+ result = atob(responseBase.Data);
+ isError = false;
+ } else if (responseBase.Error) {
+ const errorValue = responseBase.Error.value || responseBase.Error;
+ result = `Error: ${typeof errorValue === "string" ? errorValue : JSON.stringify(errorValue)}`;
+ isError = true;
+ } else {
+ result = "(empty result)";
+ isError = false;
+ }
+
+ this._resultEl.textContent = result;
+ this._resultEl.classList.toggle("u-color-danger", isError);
+
+ this._addToHistory(expression, result, isError);
+ } catch (err) {
+ const errMsg = `Error: ${err instanceof Error ? err.message : String(err)}`;
+ this._resultEl.textContent = errMsg;
+ this._resultEl.classList.add("u-color-danger");
+ this._addToHistory(expression, errMsg, true);
+ }
+ }
+
+ private _addToHistory(
+ expression: string,
+ result: string,
+ isError: boolean,
+ ): void {
+ this._history.push({ expression, result, isError });
+
+ if (this._historyListEl) {
+ const entry = document.createElement("div");
+ entry.className = "b-eval-history-entry";
+
+ const exprDiv = document.createElement("div");
+ exprDiv.className = "b-eval-history-expr";
+
+ const codeEl = document.createElement("code");
+ codeEl.className = "u-font-mono";
+ codeEl.textContent = expression;
+ exprDiv.appendChild(codeEl);
+
+ const rerunBtn = document.createElement("button");
+ rerunBtn.className = "b-eval-history-rerun";
+ rerunBtn.textContent = "\u21A9"; // ↩
+ rerunBtn.title = "Re-run";
+ rerunBtn.addEventListener("click", () => {
+ this._inputEl.value = expression;
+ this._doEval(expression);
+ });
+ exprDiv.appendChild(rerunBtn);
+
+ const resultPre = document.createElement("pre");
+ resultPre.className = `b-eval-history-result${isError ? " u-color-danger" : ""}`;
+ resultPre.textContent =
+ result.length > 200 ? result.substring(0, 200) + "..." : result;
+
+ entry.appendChild(exprDiv);
+ entry.appendChild(resultPre);
+ this._historyListEl.prepend(entry);
+ }
+ }
+}
diff --git a/gno.land/pkg/gnoweb/frontend/js/controller-playground.ts b/gno.land/pkg/gnoweb/frontend/js/controller-playground.ts
new file mode 100644
index 00000000000..b186620ba14
--- /dev/null
+++ b/gno.land/pkg/gnoweb/frontend/js/controller-playground.ts
@@ -0,0 +1,201 @@
+import { BaseController } from "./controller.js";
+
+interface PlaygroundFile {
+ name: string;
+ content: string;
+}
+
+export class PlaygroundController extends BaseController {
+ private files: PlaygroundFile[] = [];
+ private activeFile = 0;
+ private _codeEl!: HTMLTextAreaElement;
+ private _outputEl!: HTMLElement;
+ private _tabsEl!: HTMLElement;
+
+ protected connect(): void {
+ this._codeEl = this.getTarget("code") as HTMLTextAreaElement;
+ this._outputEl = this.getTarget("output") as HTMLElement;
+ this._tabsEl = this.getTarget("tabs") as HTMLElement;
+
+ // Initialize files from the editor content
+ const initialCode = this._codeEl.value;
+ if (initialCode.includes("// --- ") && initialCode.includes(" ---")) {
+ this._parseForkedFiles(initialCode);
+ } else {
+ this.files = [{ name: "main.gno", content: initialCode }];
+ }
+
+ this._renderTabs();
+ this._setupKeyboard();
+ }
+
+ private _parseForkedFiles(code: string): void {
+ const parts = code.split(/^\/\/ --- (.+?) ---$/m);
+ this.files = [];
+ for (let i = 1; i < parts.length; i += 2) {
+ const name = parts[i].trim();
+ const content = (parts[i + 1] || "").trim();
+ if (name) {
+ this.files.push({ name, content });
+ }
+ }
+ if (this.files.length === 0) {
+ this.files = [{ name: "main.gno", content: code }];
+ }
+ this._codeEl.value = this.files[0].content;
+ }
+
+ private _setupKeyboard(): void {
+ this._codeEl.addEventListener("keydown", (e: KeyboardEvent) => {
+ if (e.ctrlKey && e.key === "Enter") {
+ e.preventDefault();
+ this.runCode();
+ return;
+ }
+ if (e.key === "Tab" && !e.shiftKey) {
+ e.preventDefault();
+ const start = this._codeEl.selectionStart;
+ const end = this._codeEl.selectionEnd;
+ this._codeEl.value =
+ this._codeEl.value.substring(0, start) +
+ "\t" +
+ this._codeEl.value.substring(end);
+ this._codeEl.selectionStart = this._codeEl.selectionEnd = start + 1;
+ }
+ });
+ }
+
+ private _renderTabs(): void {
+ // Clear existing tabs using safe DOM methods
+ while (this._tabsEl.firstChild) {
+ this._tabsEl.removeChild(this._tabsEl.firstChild);
+ }
+
+ this.files.forEach((f, i) => {
+ const btn = document.createElement("button");
+ btn.className = `b-playground-tab${i === this.activeFile ? " b-playground-tab--active" : ""}`;
+ btn.textContent = f.name;
+ btn.addEventListener("click", () => this._switchToFile(f.name));
+ this._tabsEl.appendChild(btn);
+ });
+
+ const addBtn = document.createElement("button");
+ addBtn.className = "b-playground-tab-add";
+ addBtn.textContent = "+";
+ addBtn.title = "Add file";
+ addBtn.addEventListener("click", () => this.addFile());
+ this._tabsEl.appendChild(addBtn);
+ }
+
+ private _switchToFile(fileName: string): void {
+ this.files[this.activeFile].content = this._codeEl.value;
+ const idx = this.files.findIndex((f) => f.name === fileName);
+ if (idx >= 0) {
+ this.activeFile = idx;
+ this._codeEl.value = this.files[idx].content;
+ this._renderTabs();
+ }
+ }
+
+ public switchTab(event: Event): void {
+ const target = event.currentTarget as HTMLElement;
+ const fileName = target.dataset.playgroundFileParam || "";
+ this._switchToFile(fileName);
+ }
+
+ public addFile(): void {
+ const name = prompt("File name (e.g. helper.gno):");
+ if (!name) return;
+ if (!name.endsWith(".gno")) {
+ alert("File name must end with .gno");
+ return;
+ }
+ if (this.files.some((f) => f.name === name)) {
+ alert("File already exists");
+ return;
+ }
+
+ this.files[this.activeFile].content = this._codeEl.value;
+ this.files.push({ name, content: `package main\n` });
+ this.activeFile = this.files.length - 1;
+ this._codeEl.value = this.files[this.activeFile].content;
+ this._renderTabs();
+ }
+
+ public async runCode(): Promise {
+ this.files[this.activeFile].content = this._codeEl.value;
+ this._outputEl.textContent = "Running...";
+
+ const remote = this.getValue("remote");
+ const domain = this.getValue("domain");
+ const code = this._codeEl.value;
+ const pkgMatch = code.match(/^package\s+(\w+)/m);
+ const pkgName = pkgMatch ? pkgMatch[1] : "main";
+
+ if (code.includes("func Render(")) {
+ try {
+ const resp = await fetch("/_/api/eval", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ pkg_path: `${domain}/r/playground_preview`,
+ expression: `Render("")`,
+ }),
+ });
+ const result = await resp.json();
+ if (result.error) {
+ this._outputEl.textContent = `Error: ${result.error}`;
+ this._outputEl.classList.add("u-color-danger");
+ } else {
+ this._outputEl.textContent = result.result;
+ this._outputEl.classList.remove("u-color-danger");
+ }
+ } catch {
+ this._outputEl.textContent = `Note: Server-side execution requires a running gno node.\n\nPackage: ${pkgName}\nFiles: ${this.files.map((f) => f.name).join(", ")}\n\nTo deploy and test, use:\n gnokey maketx addpkg -pkgpath "${domain}/r/yourname/pkg" ...`;
+ this._outputEl.classList.remove("u-color-danger");
+ }
+ } else {
+ this._outputEl.textContent = `Package: ${pkgName}\nFiles: ${this.files.map((f) => f.name).join(", ")}\n\nTo run locally:\n gno run ${this.files.map((f) => f.name).join(" ")}\n\nTo test:\n gno test .`;
+ this._outputEl.classList.remove("u-color-danger");
+ }
+ }
+
+ public runTests(): void {
+ this._outputEl.textContent =
+ "Testing requires a running gno node.\n\nTo test locally:\n gno test .";
+ }
+
+ public formatCode(): void {
+ this._outputEl.textContent =
+ "Formatting requires server-side gno fmt (coming soon).\n\nTo format locally:\n gno fmt -w " +
+ this.files[this.activeFile].name;
+ }
+
+ public shareCode(): void {
+ this.files[this.activeFile].content = this._codeEl.value;
+
+ const code =
+ this.files.length === 1
+ ? this.files[0].content
+ : this.files
+ .map((f) => `// --- ${f.name} ---\n${f.content}`)
+ .join("\n\n");
+
+ const encoded = encodeURIComponent(code);
+ const url = `${window.location.origin}/_/play?code=${encoded}`;
+
+ navigator.clipboard
+ .writeText(url)
+ .then(() => {
+ this._outputEl.textContent = "Share URL copied to clipboard!";
+ })
+ .catch(() => {
+ this._outputEl.textContent = `Share URL:\n${url}`;
+ });
+ }
+
+ public clearOutput(): void {
+ this._outputEl.textContent = "// Run code to see output here";
+ this._outputEl.classList.remove("u-color-danger");
+ }
+}
diff --git a/gno.land/pkg/gnoweb/handler_http.go b/gno.land/pkg/gnoweb/handler_http.go
index dc8499c806d..d4f8812cc71 100644
--- a/gno.land/pkg/gnoweb/handler_http.go
+++ b/gno.land/pkg/gnoweb/handler_http.go
@@ -157,6 +157,12 @@ func (h *HTTPHandler) Get(w http.ResponseWriter, r *http.Request) {
return
}
+ // Handle playground page
+ if r.URL.Path == "/_/play" {
+ h.servePlayground(w, r)
+ return
+ }
+
// Handle download request outside of component rendering flow.
if gnourl.WebQuery.Has("download") {
h.ServeSourceDownload(r.Context(), gnourl, w, r)
@@ -303,6 +309,16 @@ func (h *HTTPHandler) GetPackageView(ctx context.Context, gnourl *weburl.GnoURL,
return h.GetHelpView(ctx, gnourl)
}
+ // Handle Eval page (expression evaluator)
+ if gnourl.WebQuery.Has("eval") {
+ return h.GetEvalView(ctx, gnourl)
+ }
+
+ // Handle Fork page (fork source to playground)
+ if gnourl.WebQuery.Has("fork") {
+ return h.GetForkView(ctx, gnourl)
+ }
+
// Handle Source page
if gnourl.WebQuery.Has("source") || gnourl.IsFile() {
return h.GetSourceView(ctx, gnourl)
@@ -719,6 +735,115 @@ func (h *HTTPHandler) ServeSourceDownload(ctx context.Context, gnourl *weburl.Gn
w.Write(source) // write raw file
}
+// servePlayground renders the standalone playground page.
+func (h *HTTPHandler) servePlayground(w http.ResponseWriter, r *http.Request) {
+ var theme string
+ if c, err := r.Cookie("theme"); err == nil {
+ if c.Value == "light" || c.Value == "dark" {
+ theme = c.Value
+ }
+ }
+
+ // Check if we have initial code from query (e.g. shared snippet)
+ initialCode := r.URL.Query().Get("code")
+ forkFrom := r.URL.Query().Get("from")
+ if initialCode == "" {
+ initialCode = `package main
+
+func Render(path string) string {
+ return "Hello, Playground!"
+}
+`
+ }
+
+ indexData := components.IndexData{
+ HeadData: components.HeadData{
+ Title: "Playground — " + h.Static.Domain,
+ AssetsPath: h.Static.AssetsPath,
+ ChromaPath: h.Static.ChromaPath,
+ ChainId: h.Static.ChainId,
+ Remote: h.Static.RemoteHelp,
+ BuildTime: h.Static.BuildTime,
+ },
+ FooterData: components.FooterData{
+ Analytics: h.Static.Analytics,
+ AssetsPath: h.Static.AssetsPath,
+ BuildTime: h.Static.BuildTime,
+ },
+ Theme: theme,
+ Banner: h.Static.Banner,
+ Mode: components.ViewModeRealm,
+ }
+
+ indexData.BodyView = components.PlaygroundView(components.PlaygroundData{
+ InitialCode: initialCode,
+ ForkFrom: forkFrom,
+ Remote: h.Static.RemoteHelp,
+ ChainId: h.Static.ChainId,
+ Domain: h.Static.Domain,
+ })
+
+ w.WriteHeader(http.StatusOK)
+ if err := components.IndexLayout(indexData).Render(w); err != nil {
+ h.Logger.Error("failed to render playground", "error", err)
+ }
+}
+
+// GetEvalView renders the expression evaluator page for a package/realm.
+func (h *HTTPHandler) GetEvalView(ctx context.Context, gnourl *weburl.GnoURL) (int, *components.View) {
+ jdoc, err := h.Client.Doc(ctx, gnourl.Path)
+ if err != nil {
+ h.Logger.Error("unable to fetch qdoc for eval", "error", err)
+ return GetClientErrorStatusPage(gnourl, err)
+ }
+
+ funcs := components.BuildEvalFuncs(jdoc)
+
+ return http.StatusOK, components.EvalView(components.EvalData{
+ Remote: h.Static.RemoteHelp,
+ PkgPath: path.Join(h.Static.Domain, gnourl.Path),
+ Domain: h.Static.Domain,
+ Functions: funcs,
+ })
+}
+
+// GetForkView loads all source files from a package and redirects to playground with the code.
+func (h *HTTPHandler) GetForkView(ctx context.Context, gnourl *weburl.GnoURL) (int, *components.View) {
+ pkgPath := gnourl.Path
+
+ files, err := h.Client.ListFiles(ctx, pkgPath)
+ if err != nil {
+ h.Logger.Warn("unable to list files for fork", "path", pkgPath, "error", err)
+ return GetClientErrorStatusPage(gnourl, err)
+ }
+
+ // Collect all .gno files
+ var allCode strings.Builder
+ for _, fileName := range files {
+ if !strings.HasSuffix(fileName, ".gno") {
+ continue
+ }
+ file, _, err := h.Client.File(ctx, pkgPath, fileName)
+ if err != nil {
+ continue
+ }
+ if allCode.Len() > 0 {
+ allCode.WriteString("\n// --- " + fileName + " ---\n\n")
+ } else {
+ allCode.WriteString("// --- " + fileName + " ---\n\n")
+ }
+ allCode.Write(file)
+ }
+
+ return http.StatusOK, components.PlaygroundView(components.PlaygroundData{
+ InitialCode: allCode.String(),
+ ForkFrom: path.Join(h.Static.Domain, pkgPath),
+ Remote: h.Static.RemoteHelp,
+ ChainId: h.Static.ChainId,
+ Domain: h.Static.Domain,
+ })
+}
+
func GetClientErrorStatusPage(_ *weburl.GnoURL, err error) (int, *components.View) {
if err == nil {
return http.StatusOK, nil
diff --git a/gno.land/pkg/gnoweb/handler_http_test.go b/gno.land/pkg/gnoweb/handler_http_test.go
index d48f0b7b217..9975984e2ca 100644
--- a/gno.land/pkg/gnoweb/handler_http_test.go
+++ b/gno.land/pkg/gnoweb/handler_http_test.go
@@ -76,6 +76,10 @@ func (s *stubClient) ListPaths(ctx context.Context, prefix string, limit int) ([
return nil, errors.New("stubClient: ListPaths not implemented")
}
+func (s *stubClient) Eval(_ context.Context, data string) ([]byte, error) {
+ return []byte("(stub eval: " + data + ")"), nil
+}
+
type rawRenderer struct{}
func (rawRenderer) RenderRealm(w io.Writer, u *weburl.GnoURL, src []byte, ctx gnoweb.RealmRenderContext) (md.Toc, error) {
diff --git a/gno.land/pkg/gnoweb/handler_playground.go b/gno.land/pkg/gnoweb/handler_playground.go
new file mode 100644
index 00000000000..b6c172d18b6
--- /dev/null
+++ b/gno.land/pkg/gnoweb/handler_playground.go
@@ -0,0 +1,168 @@
+package gnoweb
+
+import (
+ "encoding/json"
+ "fmt"
+ "log/slog"
+ "net/http"
+ "strings"
+ "time"
+)
+
+// PlaygroundConfig holds playground-specific configuration.
+type PlaygroundConfig struct {
+ Enabled bool // Whether playground features are enabled
+}
+
+// playgroundAPIHandler handles playground JSON API endpoints.
+type playgroundAPIHandler struct {
+ logger *slog.Logger
+ client ClientAdapter
+ domain string
+ remote string
+}
+
+// evalRequest is the JSON request body for the eval endpoint.
+type evalRequest struct {
+ PkgPath string `json:"pkg_path"`
+ Expression string `json:"expression"`
+}
+
+// evalResponse is the JSON response for the eval endpoint.
+type evalResponse struct {
+ Result string `json:"result,omitempty"`
+ Error string `json:"error,omitempty"`
+}
+
+// funcsResponse represents function info returned by the funcs endpoint.
+type funcsResponse struct {
+ Functions []funcInfo `json:"functions"`
+ PkgDoc string `json:"pkg_doc,omitempty"`
+}
+
+type funcInfo struct {
+ Name string `json:"name"`
+ Doc string `json:"doc,omitempty"`
+ Signature string `json:"signature"`
+ Params []paramInfo `json:"params,omitempty"`
+ Crossing bool `json:"crossing"`
+}
+
+type paramInfo struct {
+ Name string `json:"name"`
+ Type string `json:"type"`
+}
+
+// handlerPlaygroundEval creates an HTTP handler for expression evaluation.
+func handlerPlaygroundEval(logger *slog.Logger, cli ClientAdapter, domain, remote string) http.Handler {
+ h := &playgroundAPIHandler{
+ logger: logger,
+ client: cli,
+ domain: domain,
+ remote: remote,
+ }
+ return http.HandlerFunc(h.serveEval)
+}
+
+// handlerPlaygroundFuncs creates an HTTP handler for listing functions.
+func handlerPlaygroundFuncs(logger *slog.Logger, cli ClientAdapter) http.Handler {
+ h := &playgroundAPIHandler{
+ logger: logger,
+ client: cli,
+ }
+ return http.HandlerFunc(h.serveFuncs)
+}
+
+func (h *playgroundAPIHandler) serveEval(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodPost {
+ http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
+ return
+ }
+
+ var req evalRequest
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ writeJSON(w, http.StatusBadRequest, evalResponse{Error: "invalid request body"})
+ return
+ }
+
+ if req.PkgPath == "" || req.Expression == "" {
+ writeJSON(w, http.StatusBadRequest, evalResponse{Error: "pkg_path and expression are required"})
+ return
+ }
+
+ // Clean the pkg path
+ pkgPath := strings.TrimPrefix(req.PkgPath, h.domain+"/")
+ pkgPath = strings.TrimPrefix(pkgPath, h.domain)
+ pkgPath = strings.TrimPrefix(pkgPath, "/")
+
+ // Build the qeval data string: "gno.land/r/demo/boards.Render("")"
+ data := fmt.Sprintf("%s/%s.%s", h.domain, pkgPath, req.Expression)
+
+ h.logger.Debug("playground eval", "data", data)
+
+ start := time.Now()
+ result, err := h.client.Eval(r.Context(), data)
+ took := time.Since(start)
+
+ h.logger.Debug("playground eval result", "took", took, "error", err)
+
+ if err != nil {
+ writeJSON(w, http.StatusOK, evalResponse{Error: err.Error()})
+ return
+ }
+
+ writeJSON(w, http.StatusOK, evalResponse{Result: string(result)})
+}
+
+func (h *playgroundAPIHandler) serveFuncs(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodGet {
+ http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
+ return
+ }
+
+ pkgPath := r.URL.Query().Get("path")
+ if pkgPath == "" {
+ writeJSON(w, http.StatusBadRequest, map[string]string{"error": "path parameter required"})
+ return
+ }
+
+ jdoc, err := h.client.Doc(r.Context(), pkgPath)
+ if err != nil {
+ writeJSON(w, http.StatusOK, map[string]string{"error": err.Error()})
+ return
+ }
+
+ resp := funcsResponse{
+ PkgDoc: jdoc.PackageDoc,
+ }
+
+ for _, fn := range jdoc.Funcs {
+ if fn.Type != "" { // Skip methods
+ continue
+ }
+
+ fi := funcInfo{
+ Name: fn.Name,
+ Doc: fn.Doc,
+ Signature: fn.Signature,
+ Crossing: fn.Crossing,
+ }
+
+ for _, p := range fn.Params {
+ fi.Params = append(fi.Params, paramInfo{
+ Name: p.Name,
+ Type: p.Type,
+ })
+ }
+
+ resp.Functions = append(resp.Functions, fi)
+ }
+
+ writeJSON(w, http.StatusOK, resp)
+}
+
+func writeJSON(w http.ResponseWriter, status int, v any) {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(status)
+ json.NewEncoder(w).Encode(v) //nolint:errcheck
+}
diff --git a/gno.land/pkg/gnoweb/public/js/controller-eval.js b/gno.land/pkg/gnoweb/public/js/controller-eval.js
new file mode 100644
index 00000000000..a088b64e35d
--- /dev/null
+++ b/gno.land/pkg/gnoweb/public/js/controller-eval.js
@@ -0,0 +1 @@
+import{BaseController as p}from"./controller.js";var h=class extends p{_inputEl;_resultEl;_historyListEl;_history=[];connect(){this._inputEl=this.getTarget("input"),this._resultEl=this.getTarget("result"),this._historyListEl=this.getTarget("history-list"),this._inputEl?.focus(),this._inputEl?.addEventListener("keydown",t=>{t.key==="ArrowUp"&&this._history.length>0&&(t.preventDefault(),this._inputEl.value=this._history[this._history.length-1].expression)})}evalExpression(t){t.preventDefault();let e=this._inputEl.value.trim();e&&this._doEval(e)}quickCall(t){let e=t.params?.funcName,a=t.params?.funcSig;if(e){let s=a?.match(/\(([^)]*)\)/),r=s?s[1]:"";r?(this._inputEl.value=`${e}(${r.split(",").map(i=>{let o=i.trim().split(/\s+/);return o[o.length-1]==="string"?'""':"0"}).join(", ")})`,this._inputEl.focus(),this._inputEl.setSelectionRange(e.length+1,this._inputEl.value.length-1)):(this._inputEl.value=`${e}()`,this._doEval(`${e}()`))}}clearResult(){this._resultEl.textContent="// Enter an expression above",this._resultEl.classList.remove("u-color-danger")}async _doEval(t){let e=this.getValue("remote"),a=this.getValue("pkg-path");this._resultEl.textContent="Evaluating...",this._resultEl.classList.remove("u-color-danger");try{let s=`${a}.${t}`,r=`${e}/abci_query?path=vm%2fqeval&data=${btoa(s)}`,i=await fetch(r);if(!i.ok)throw new Error(`HTTP ${i.status}`);let n=(await i.json()).result.response.ResponseBase,l,u;if(n.Data)l=atob(n.Data),u=!1;else if(n.Error){let c=n.Error.value||n.Error;l=`Error: ${typeof c=="string"?c:JSON.stringify(c)}`,u=!0}else l="(empty result)",u=!1;this._resultEl.textContent=l,this._resultEl.classList.toggle("u-color-danger",u),this._addToHistory(t,l,u)}catch(s){let r=`Error: ${s instanceof Error?s.message:String(s)}`;this._resultEl.textContent=r,this._resultEl.classList.add("u-color-danger"),this._addToHistory(t,r,!0)}}_addToHistory(t,e,a){if(this._history.push({expression:t,result:e,isError:a}),this._historyListEl){let s=document.createElement("div");s.className="b-eval-history-entry";let r=document.createElement("div");r.className="b-eval-history-expr";let i=document.createElement("code");i.className="u-font-mono",i.textContent=t,r.appendChild(i);let o=document.createElement("button");o.className="b-eval-history-rerun",o.textContent="\u21A9",o.title="Re-run",o.addEventListener("click",()=>{this._inputEl.value=t,this._doEval(t)}),r.appendChild(o);let n=document.createElement("pre");n.className=`b-eval-history-result${a?" u-color-danger":""}`,n.textContent=e.length>200?e.substring(0,200)+"...":e,s.appendChild(r),s.appendChild(n),this._historyListEl.prepend(s)}}};export{h as EvalController};
diff --git a/gno.land/pkg/gnoweb/public/js/controller-playground.js b/gno.land/pkg/gnoweb/public/js/controller-playground.js
new file mode 100644
index 00000000000..00a3c2005e0
--- /dev/null
+++ b/gno.land/pkg/gnoweb/public/js/controller-playground.js
@@ -0,0 +1,25 @@
+import{BaseController as r}from"./controller.js";var a=class extends r{files=[];activeFile=0;_codeEl;_outputEl;_tabsEl;connect(){this._codeEl=this.getTarget("code"),this._outputEl=this.getTarget("output"),this._tabsEl=this.getTarget("tabs");let t=this._codeEl.value;t.includes("// --- ")&&t.includes(" ---")?this._parseForkedFiles(t):this.files=[{name:"main.gno",content:t}],this._renderTabs(),this._setupKeyboard()}_parseForkedFiles(t){let e=t.split(/^\/\/ --- (.+?) ---$/m);this.files=[];for(let i=1;i{if(t.ctrlKey&&t.key==="Enter"){t.preventDefault(),this.runCode();return}if(t.key==="Tab"&&!t.shiftKey){t.preventDefault();let e=this._codeEl.selectionStart,i=this._codeEl.selectionEnd;this._codeEl.value=this._codeEl.value.substring(0,e)+" "+this._codeEl.value.substring(i),this._codeEl.selectionStart=this._codeEl.selectionEnd=e+1}})}_renderTabs(){for(;this._tabsEl.firstChild;)this._tabsEl.removeChild(this._tabsEl.firstChild);this.files.forEach((e,i)=>{let n=document.createElement("button");n.className=`b-playground-tab${i===this.activeFile?" b-playground-tab--active":""}`,n.textContent=e.name,n.addEventListener("click",()=>this._switchToFile(e.name)),this._tabsEl.appendChild(n)});let t=document.createElement("button");t.className="b-playground-tab-add",t.textContent="+",t.title="Add file",t.addEventListener("click",()=>this.addFile()),this._tabsEl.appendChild(t)}_switchToFile(t){this.files[this.activeFile].content=this._codeEl.value;let e=this.files.findIndex(i=>i.name===t);e>=0&&(this.activeFile=e,this._codeEl.value=this.files[e].content,this._renderTabs())}switchTab(t){let i=t.currentTarget.dataset.playgroundFileParam||"";this._switchToFile(i)}addFile(){let t=prompt("File name (e.g. helper.gno):");if(t){if(!t.endsWith(".gno")){alert("File name must end with .gno");return}if(this.files.some(e=>e.name===t)){alert("File already exists");return}this.files[this.activeFile].content=this._codeEl.value,this.files.push({name:t,content:`package main
+`}),this.activeFile=this.files.length-1,this._codeEl.value=this.files[this.activeFile].content,this._renderTabs()}}async runCode(){this.files[this.activeFile].content=this._codeEl.value,this._outputEl.textContent="Running...";let t=this.getValue("remote"),e=this.getValue("domain"),i=this._codeEl.value,n=i.match(/^package\s+(\w+)/m),o=n?n[1]:"main";if(i.includes("func Render("))try{let l=await(await fetch("/_/api/eval",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pkg_path:`${e}/r/playground_preview`,expression:'Render("")'})})).json();l.error?(this._outputEl.textContent=`Error: ${l.error}`,this._outputEl.classList.add("u-color-danger")):(this._outputEl.textContent=l.result,this._outputEl.classList.remove("u-color-danger"))}catch{this._outputEl.textContent=`Note: Server-side execution requires a running gno node.
+
+Package: ${o}
+Files: ${this.files.map(s=>s.name).join(", ")}
+
+To deploy and test, use:
+ gnokey maketx addpkg -pkgpath "${e}/r/yourname/pkg" ...`,this._outputEl.classList.remove("u-color-danger")}else this._outputEl.textContent=`Package: ${o}
+Files: ${this.files.map(s=>s.name).join(", ")}
+
+To run locally:
+ gno run ${this.files.map(s=>s.name).join(" ")}
+
+To test:
+ gno test .`,this._outputEl.classList.remove("u-color-danger")}runTests(){this._outputEl.textContent=`Testing requires a running gno node.
+
+To test locally:
+ gno test .`}formatCode(){this._outputEl.textContent=`Formatting requires server-side gno fmt (coming soon).
+
+To format locally:
+ gno fmt -w `+this.files[this.activeFile].name}shareCode(){this.files[this.activeFile].content=this._codeEl.value;let t=this.files.length===1?this.files[0].content:this.files.map(n=>`// --- ${n.name} ---
+${n.content}`).join(`
+
+`),e=encodeURIComponent(t),i=`${window.location.origin}/_/play?code=${e}`;navigator.clipboard.writeText(i).then(()=>{this._outputEl.textContent="Share URL copied to clipboard!"}).catch(()=>{this._outputEl.textContent=`Share URL:
+${i}`})}clearOutput(){this._outputEl.textContent="// Run code to see output here",this._outputEl.classList.remove("u-color-danger")}};export{a as PlaygroundController};
diff --git a/gno.land/pkg/gnoweb/public/main.css b/gno.land/pkg/gnoweb/public/main.css
index ece537a6e51..693ac8d5d18 100644
--- a/gno.land/pkg/gnoweb/public/main.css
+++ b/gno.land/pkg/gnoweb/public/main.css
@@ -1,6 +1,6 @@
-:root{--g-px-base:16;--g-space-mult:4;--g-space-base:calc(1rem/var(--g-space-mult));--g-breakpoint-max:calc(1580/var(--g-px-base)*1rem);--g-z-min:-1;--g-z-1:1;--g-z-max:9999;--g-duration-75:75ms;--g-duration-150:150ms;--g-opacity-50:0.5;--g-grid-1:repeat(1,minmax(0,1fr));--g-grid-10:repeat(10,minmax(0,1fr));--g-space-px:1px;--g-space-0-5:calc(var(--g-space-base)*0.5);--g-space-1:var(--g-space-base);--g-space-1-5:calc(var(--g-space-base)*1.5);--g-space-2:calc(var(--g-space-base)*2);--g-space-2-5:calc(var(--g-space-base)*2.5);--g-space-3:calc(var(--g-space-base)*3);--g-space-4:calc(var(--g-space-base)*4);--g-space-4-5:calc(var(--g-space-base)*4.5);--g-space-5:calc(var(--g-space-base)*5);--g-space-6:calc(var(--g-space-base)*6);--g-space-7:calc(var(--g-space-base)*7);--g-space-8:calc(var(--g-space-base)*8);--g-space-10:calc(var(--g-space-base)*10);--g-space-12:calc(var(--g-space-base)*12);--g-space-14:calc(var(--g-space-base)*14);--g-space-20:calc(var(--g-space-base)*20);--g-space-24:calc(var(--g-space-base)*24);--g-space-28:calc(var(--g-space-base)*28);--g-space-32:calc(var(--g-space-base)*32);--g-space-36:calc(var(--g-space-base)*36);--g-space-44:calc(var(--g-space-base)*44);--g-space-48:calc(var(--g-space-base)*48);--g-space-52:calc(var(--g-space-base)*52);--g-space-72:calc(var(--g-space-base)*72);--g-space-96:calc(var(--g-space-base)*96);--g-font-size-50:calc(12/var(--g-px-base)*1rem);--g-font-size-100:calc(14/var(--g-px-base)*1rem);--g-font-size-200:calc(16/var(--g-px-base)*1rem);--g-font-size-300:calc(18/var(--g-px-base)*1rem);--g-font-size-400:calc(20/var(--g-px-base)*1rem);--g-font-size-500:calc(22/var(--g-px-base)*1rem);--g-font-size-600:calc(24/var(--g-px-base)*1rem);--g-font-size-700:calc(32/var(--g-px-base)*1rem);--g-font-size-800:calc(38/var(--g-px-base)*1rem);--g-font-family-mono:"Roboto",'Menlo, Consolas, "Ubuntu Mono", "Roboto Mono", "DejaVu Sans Mono", monospace';--g-font-family-inter-var:"Inter",'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", sans-serif';--g-font-normal:400;--g-font-medium:500;--g-font-semibold:600;--g-font-bold:700;--g-italic:oblique 14deg;--g-line-height-tight:1.25;--g-line-height-snug:1.375;--g-line-height-normal:1.5;--g-border-radius-sm:calc(4/var(--g-px-base)*1rem);--g-border-radius:calc(6/var(--g-px-base)*1rem);--g-border-radius-full:9999px;--g-color-light:#fff;--g-color-transparent:transparent;--g-color-gray-50:#f0f0f0;--g-color-gray-100:#e2e2e2;--g-color-gray-200:#bdbdbd;--g-color-gray-300:#999;--g-color-gray-400:#7c7c7c;--g-color-gray-500:#696969;--g-color-gray-600:#585858;--g-color-gray-700:#292929;--g-color-gray-750:#1f1f1f;--g-color-gray-800:#141414;--g-color-gray-850:#0e0e0e;--g-color-gray-900:#090909;--g-color-green-50:#e7efed;--g-color-green-400:#60ab96;--g-color-green-500:#277b63;--g-color-green-600:#226c57;--g-color-green-900:#144134;--g-color-green-950:#002c20;--g-color-blue-400:#49afeb;--g-color-blue-600:#3e96c9;--g-color-blue-900:#21506b;--g-color-yellow-50:#fff7eb;--g-color-yellow-400:#facc32;--g-color-yellow-600:#fbbf24;--g-color-yellow-900:#7b4807;--g-color-yellow-950:#362600;--g-color-red-400:#eb6c49;--g-color-red-600:#c95c3e;--g-color-red-900:#6b2521;--g-color-purple-400:#7f49eb;--g-color-purple-600:#6c3ec9;--g-color-purple-900:#39216b}@supports (color:color(display-p3 0 0 0%)){:root{--g-color-green-950:#002c20;--g-color-yellow-50:#fff7eb;--g-color-yellow-950:#362600}@media (color-gamut:p3){:root{--g-color-green-950:color(display-p3 0.04602 0.17026 0.1277);--g-color-yellow-50:color(display-p3 0.99709 0.97106 0.92232);--g-color-yellow-950:color(display-p3 0.2031 0.15112 0.01811)}}}:root{--s-color-bg-base:var(--g-color-light,#fff);--s-color-bg-base-dev:var(--g-color-gray-50,#f0f0f0);--s-color-bg-surface-primary:var(--g-color-gray-50,#f0f0f0);--s-color-bg-surface-primary-hover:var(--g-color-gray-100,#f0f0f0);--s-color-bg-surface-secondary:var(--g-color-gray-100,#e2e2e2);--s-color-bg-surface-quaternary:var(--g-color-gray-400,#7c7c7c);--s-color-bg-brand-default:var(--g-color-green-600,#226c57);--s-color-bg-brand-weak:var(--g-color-green-50,#f0f9ff);--s-color-bg-success-default:var(--g-color-green-600,#144134);--s-color-bg-info-default:var(--g-color-blue-600,#21506b);--s-color-bg-warning-default:var(--g-color-yellow-600,#665100);--s-color-bg-warning-weak:var(--g-color-yellow-50,#f9d985);--s-color-bg-warning-action:var(--g-color-yellow-400,#f9d985);--s-color-bg-caution-default:var(--g-color-red-600,#610);--s-color-bg-tip-default:var(--g-color-purple-600,#49216b);--s-color-bg-note-default:var(--g-color-gray-600,#21506b);--s-color-bg-input:var(--g-color-light,#fff);--s-color-text-base:var(--g-color-light,#fff);--s-color-text-primary:var(--g-color-gray-900,#080809);--s-color-text-secondary:var(--g-color-gray-600,#454a4e);--s-color-text-tertiary:var(--g-color-gray-400,#f0f0f0);--s-color-text-tertiary-hover:var(--g-color-gray-600,#e2e2e2);--s-color-text-quaternary:var(--g-color-gray-100,#f0f0f0);--s-color-text-brand-default:var(--g-color-light,#fff);--s-color-text-link:var(--g-color-green-600,#226c57);--s-color-text-link-hover:var(--g-color-green-600,#226c57);--s-color-text-success:var(--g-color-green-900,#144134);--s-color-text-info:var(--g-color-blue-900,#21506b);--s-color-text-warning:var(--g-color-yellow-900,#665100);--s-color-text-caution:var(--g-color-red-900,#610);--s-color-text-tip:var(--g-color-purple-900,#49216b);--s-color-border-primary:var(--g-color-gray-200,#bdbdbd);--s-color-border-secondary:var(--g-color-gray-100,#e2e2e2);--s-color-border-tertiary:var(--g-color-gray-300,#999);--s-color-border-quaternary:var(--g-color-gray-400,#7c7c7c);--s-color-border-transparent:var(--g-color-transparent,transparent);--s-color-border-input:var(--g-color-gray-300,#999);--s-color-border-brand-default:var(--g-color-green-600,#226c57);--s-color-border-success:var(--g-color-green-600,#144134);--s-color-border-info:var(--g-color-blue-600,#21506b);--s-color-border-warning:var(--g-color-yellow-600,#665100);--s-color-border-error:var(--g-color-red-600,#610);--s-color-border-tip:var(--g-color-purple-600,#49216b);--s-color-border-note:var(--g-color-gray-600,#21506b);--s-rounded-sm:var(--g-border-radius-sm,4px);--s-rounded:var(--g-border-radius,6px);--s-rounded-full:var(--g-border-radius-full,9999px);--s-border:var(--g-space-px,1px) solid var(--s-color-border-primary);--s-border-secondary:var(--g-space-px,1px) solid var(--s-color-border-secondary);--s-logo-hat:var(--g-color-green-600,#226c57);--s-logo-beard:var(--g-color-gray-300,#999)}[data-theme=dark]{--s-color-bg-base:var(--g-color-gray-850);--s-color-bg-base-dev:var(--g-color-gray-800);--s-color-bg-surface-primary:var(--g-color-gray-800);--s-color-bg-surface-primary-hover:var(--g-color-gray-750);--s-color-bg-surface-secondary:var(--g-color-gray-750);--s-color-bg-surface-quaternary:var(--g-color-gray-600);--s-color-bg-brand-weak:var(--g-color-green-950);--s-color-bg-warning-weak:var(--g-color-yellow-950);--s-color-bg-input:var(--g-color-gray-800);--s-color-text-primary:var(--g-color-gray-100);--s-color-text-secondary:var(--g-color-gray-200);--s-color-text-tertiary:var(--g-color-gray-400);--s-color-text-tertiary-hover:var(--g-color-gray-300);--s-color-text-quaternary:var(--g-color-gray-500);--s-color-text-brand-default:var(--g-color-light);--s-color-text-link:var(--g-color-green-500);--s-color-text-link-hover:var(--g-color-green-400);--s-color-text-success:var(--g-color-green-400);--s-color-text-info:var(--g-color-blue-400);--s-color-text-warning:var(--g-color-yellow-400);--s-color-text-caution:var(--g-color-red-400);--s-color-text-tip:var(--g-color-purple-400);--s-color-border-primary:var(--g-color-gray-700);--s-color-border-secondary:var(--g-color-gray-750);--s-color-border-tertiary:var(--g-color-gray-600);--s-color-border-quaternary:var(--g-color-gray-500);--s-color-border-input:var(--g-color-gray-700);--s-color-border-brand-default:var(--g-color-green-600);--s-color-border-success:var(--g-color-green-400);--s-color-border-info:var(--g-color-blue-400);--s-color-border-warning:var(--g-color-yellow-400);--s-color-border-error:var(--g-color-red-400);--s-color-border-tip:var(--g-color-purple-400);--s-color-border-note:var(--g-color-gray-600);--s-logo-hat:#fff;--s-logo-beard:grey}*,::backdrop,::file-selector-button,:after,:before{border:0 solid;box-sizing:border-box;margin:0;padding:0}html{font-family:ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}h1,h2,h3{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub{bottom:-.25em;font-size:75%;line-height:0;position:relative;vertical-align:baseline}table{border-collapse:collapse;border-color:inherit;text-indent:0}summary{display:list-item}menu,ol,ul{list-style:none}embed,img,object,svg{display:block;vertical-align:middle}img{height:auto;max-width:100%}::file-selector-button,button,input,select,textarea{background-color:transparent;border-radius:0;color:inherit;font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;opacity:1}::file-selector-button{margin-right:4px}::-moz-placeholder{opacity:1}::placeholder{opacity:1}@supports (not (-webkit-appearance:-apple-pay-button)) or (contain-intrinsic-size:1px){::-moz-placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-meridiem-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-year-field{padding-bottom:0;padding-top:0}::-webkit-calendar-picker-indicator{line-height:1}::file-selector-button,button,input:where([type=button],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}@font-face{font-display:swap;font-family:Roboto;font-style:normal;font-weight:900;src:url(fonts/roboto/roboto-mono-normal.woff2) format("woff2"),url(fonts/roboto/roboto-mono-normal.woff) format("woff")}@font-face{font-display:block;font-family:Inter;font-style:oblique 0deg 10deg;font-variant:normal;font-weight:100 900;src:url(fonts/intervar/Intervar.woff2) format("woff2")}html{background-color:var(--s-color-bg-base);color:var(--s-color-text-secondary);font-family:var(--g-font-family-inter-var);font-feature-settings:"kern" on,"liga" on,"calt" off,"zero" on,contextual common-ligatures,"kern";-webkit-font-feature-settings:"kern" on,"liga" on,"calt" off,"zero" on;font-size:calc(var(--g-px-base)*1px);line-height:var(--g-line-height-normal);-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-kerning:normal;font-variant-ligatures:contextual common-ligatures;text-rendering:optimizeLegibility}body{display:flex;flex-direction:column;min-height:100vh}main{background-color:var(--s-color-bg-base);flex-grow:2;width:100%}main.dev-mode{background-color:var(--s-color-bg-base-dev)}main>section{display:grid;grid-auto-flow:dense;grid-template-columns:var(--g-grid-1);grid-column-gap:var(--g-space-20);-moz-column-gap:var(--g-space-20);column-gap:var(--g-space-20);min-height:100%;padding-left:var(--g-space-4);padding-right:var(--g-space-4)}@media (min-width:calc(640 / 16 * 1rem)){main>section{padding-left:var(--g-space-10);padding-right:var(--g-space-10)}}@media (min-width:calc(820 / 16 * 1rem)){main>section{grid-template-columns:var(--g-grid-10)}}@media (min-width:calc(1366 / 16 * 1rem)){main>section{-moz-column-gap:var(--g-space-32);column-gap:var(--g-space-32)}}svg{max-height:100%;max-width:100%}form{margin-bottom:0;margin-top:0}code{font-family:var(--g-font-mono)}summary{cursor:pointer}md-renderer{margin-top:var(--g-space-4);padding-bottom:var(--g-space-24)}@media (min-width:calc(820 / 16 * 1rem)){md-renderer{grid-column:span 7;margin-top:0}}::-moz-selection{background-color:var(--s-color-bg-brand-default);color:var(--s-color-text-base)}::selection{background-color:var(--s-color-bg-brand-default);color:var(--s-color-text-base)}summary::-webkit-details-marker{display:none}summary::marker{display:none}.c-stack{display:flex;flex-direction:column;justify-content:flex-start}.c-stack>*+*{margin-top:var(--g-space-4)}.c-inline{align-items:center;display:inline-flex;gap:var(--g-space-3)}.c-between{align-items:center;display:flex;justify-content:space-between}.c-center{box-sizing:border-box;margin-left:auto;margin-right:auto;max-width:var(--g-breakpoint-max);padding-left:var(--g-space-4);padding-right:var(--g-space-4)}@media (min-width:calc(640 / 16 * 1rem)){.c-center{padding-left:var(--g-space-10);padding-right:var(--g-space-10)}}.c-full-screen{align-items:center;display:flex;flex-direction:column;grid-column:1/-1;height:100%;justify-content:center;margin-top:var(--g-space-10);padding-bottom:var(--g-space-24);width:100%}.c-reel{display:flex;overflow:scroll}.c-icon{flex-shrink:0;height:1.15em;width:1.15em}.c-with-icon{align-items:flex-start;display:inline-flex}.c-with-icon .c-icon,.c-with-icon--inline .c-icon{margin-left:.3em;margin-right:.3em;margin-top:.15em}.c-with-icon--inline{display:inline-block}.c-with-icon--inline>*{vertical-align:middle}.c-with-icon--inline .c-icon{margin-top:0}.c-view-grid{display:flex;flex-direction:column}@media (min-width:calc(640 / 16 * 1rem)){.c-view-grid{-moz-column-gap:var(--g-space-8);column-gap:var(--g-space-8);flex-direction:row}}@media (min-width:calc(820 / 16 * 1rem)){.c-view-grid{display:grid;grid-template-columns:var(--g-grid-10);grid-column-gap:var(--g-space-20);-moz-column-gap:var(--g-space-20);column-gap:var(--g-space-20)}}@media (min-width:calc(1366 / 16 * 1rem)){.c-view-grid{-moz-column-gap:var(--g-space-32);column-gap:var(--g-space-32)}}.c-toggle-btn>input{display:none}.c-toggle-btn label{visibility:hidden}.c-toggle-btn input:checked+label{visibility:visible}.c-readme-view,.c-realm-view{--cr-px-base:var(--g-px-base);--cr-space-mult:1;--cr-space-base:calc(1em/var(--g-space-mult)*var(--cr-space-mult));--cr-space-0:0;--cr-space-0-5:calc(var(--cr-space-base)*0.5);--cr-space-1:var(--cr-space-base);--cr-space-2:calc(var(--cr-space-base)*2);--cr-space-3:calc(var(--cr-space-base)*3);--cr-space-4:calc(var(--cr-space-base)*4);--cr-space-5:calc(var(--cr-space-base)*5);--cr-space-7:calc(var(--cr-space-base)*7);--cr-space-8:calc(var(--cr-space-base)*8);--cr-space-24:calc(var(--cr-space-base)*24);--cr-color-brand-default:var(--s-color-text-link);display:block;font-size:calc(var(--cr-px-base)*1px);padding-top:var(--g-space-4);word-break:break-word}.c-readme-view:empty,.c-realm-view:empty{display:none}.c-realm-view:has(.b-btn:only-child){display:none}.c-readme-view:has(.b-btn:only-child){display:none}@media (min-width:calc(820 / 16 * 1rem)){.c-readme-view,.c-realm-view{grid-row-start:1;padding-top:var(--g-space-6)}}.c-readme-view a,.c-realm-view a{color:var(--cr-color-brand-default);display:inline-block;font-weight:inherit;position:relative;text-wrap:balance;vertical-align:top}.c-readme-view a:hover,.c-realm-view a:hover{-webkit-text-decoration:underline;text-decoration:underline}.c-realm-view a:has(>img){vertical-align:middle}.c-readme-view a:has(>img){vertical-align:middle}.c-readme-view a>span,.c-realm-view a>span{margin-bottom:.1em}.c-readme-view a>.tooltip+.tooltip,.c-realm-view a>.tooltip+.tooltip{margin-left:.2em}.c-readme-view a>.tooltip:last-of-type,.c-realm-view a>.tooltip:last-of-type{margin-right:.2em}.c-realm-view a:has(>img:first-child):has(.tooltip:last-child):not(:has(>:nth-child(3)))>.tooltip{background-color:var(--s-color-bg-base);border-radius:var(--g-border-radius-full);bottom:var(--g-space-2);left:var(--g-space-2);margin-left:0;position:absolute}.c-readme-view a:has(>img:first-child):has(.tooltip:last-child):not(:has(>:nth-child(3)))>.tooltip{background-color:var(--s-color-bg-base);border-radius:var(--g-border-radius-full);bottom:var(--g-space-2);left:var(--g-space-2);margin-left:0;position:absolute}.c-realm-view a:has(>img:first-child):has(.tooltip+.tooltip:last-child):not(:has(>:nth-child(4)))>.tooltip{background-color:var(--s-color-bg-base);border-radius:var(--g-border-radius-full);bottom:var(--g-space-2);left:var(--g-space-2);margin-left:0;position:absolute}.c-readme-view a:has(>img:first-child):has(.tooltip+.tooltip:last-child):not(:has(>:nth-child(4)))>.tooltip{background-color:var(--s-color-bg-base);border-radius:var(--g-border-radius-full);bottom:var(--g-space-2);left:var(--g-space-2);margin-left:0;position:absolute}.c-realm-view a:has(>img:first-child):has(.tooltip+.tooltip:last-child):not(:has(>:nth-child(4)))>.tooltip:first-of-type{bottom:var(--g-space-2);left:var(--g-space-7);position:absolute}.c-readme-view a:has(>img:first-child):has(.tooltip+.tooltip:last-child):not(:has(>:nth-child(4)))>.tooltip:first-of-type{bottom:var(--g-space-2);left:var(--g-space-7);position:absolute}.c-readme-view h1+h2,.c-readme-view h2+h3,.c-readme-view h3+h4,.c-realm-view h1+h2,.c-realm-view h2+h3,.c-realm-view h3+h4{margin-top:var(--cr-space-4)}.c-readme-view h1,.c-readme-view h2,.c-readme-view h3,.c-readme-view h4,.c-realm-view h1,.c-realm-view h2,.c-realm-view h3,.c-realm-view h4{color:var(--s-color-text-primary);line-height:var(--g-line-height-tight);margin-top:var(--cr-space-4)}.c-readme-view h1,.c-realm-view h1{font-size:var(--g-font-size-700);font-weight:var(--g-font-bold);margin-bottom:var(--cr-space-2)}@media (min-width:calc(640 / 16 * 1rem)){.c-readme-view h1,.c-realm-view h1{font-size:var(--g-font-size-800)}}.c-readme-view h2,.c-realm-view h2{font-size:var(--g-font-size-500);font-weight:var(--g-font-bold)}@media (min-width:calc(640 / 16 * 1rem)){.c-readme-view h2,.c-realm-view h2{font-size:var(--g-font-size-600)}}.c-readme-view h2 *,.c-realm-view h2 *{font-weight:var(--g-font-bold)}.c-readme-view h3,.c-readme-view h4,.c-realm-view h3,.c-realm-view h4{color:var(--s-color-text-secondary);font-weight:var(--g-font-semibold)}.c-readme-view h3,.c-realm-view h3{font-size:var(--g-font-size-400);margin-top:var(--cr-space-4)}.c-readme-view h4,.c-realm-view h4{font-size:var(--g-font-size-300);margin-top:var(--cr-space-3)}@media (min-width:calc(640 / 16 * 1rem)){.c-readme-view h4,.c-realm-view h4{font-size:var(--g-font-size-300)}}.c-readme-view h3 *,.c-readme-view h4 *,.c-realm-view h3 *,.c-realm-view h4 *{font-weight:var(--g-font-semibold)}.c-readme-view h5,.c-readme-view h6,.c-realm-view h5,.c-realm-view h6{font-size:var(--g-font-size-300);font-weight:var(--g-font-bold);margin-bottom:var(--cr-space-0);margin-top:var(--cr-space-0)}.c-readme-view h5+p,.c-readme-view h6+p,.c-realm-view h5+p,.c-realm-view h6+p{margin-top:var(--cr-space-0)}.c-readme-view img,.c-realm-view img{border:1px solid var(--s-color-bg-surface-primary);border-radius:var(--g-border-radius-sm);margin-bottom:var(--cr-space-2);margin-top:var(--cr-space-2);max-width:100%;-webkit-user-select:none;-moz-user-select:none;user-select:none}.c-readme-view figure,.c-realm-view figure{margin-bottom:var(--cr-space-3);margin-top:var(--cr-space-3);text-align:center}.c-readme-view figcaption,.c-realm-view figcaption{color:var(--s-color-text-secondary);font-size:var(--g-font-size-100)}.c-readme-view video,.c-realm-view video{margin-bottom:var(--g-space-4);margin-top:var(--g-space-4);max-width:100%}.c-readme-view p,.c-realm-view p{margin-bottom:var(--cr-space-3);margin-top:var(--cr-space-3)}.c-realm-view p:has(>a:only-child>img){margin-bottom:var(--cr-space-4);margin-top:var(--cr-space-4)}.c-readme-view p:has(>a:only-child>img){margin-bottom:var(--cr-space-4);margin-top:var(--cr-space-4)}.c-realm-view p:has(>a:only-child>img) img{margin-bottom:0;margin-top:0}.c-readme-view p:has(>a:only-child>img) img{margin-bottom:0;margin-top:0}.c-readme-view strong,.c-readme-view strong *,.c-realm-view strong,.c-realm-view strong *{font-weight:var(--g-font-bold)}.c-readme-view em,.c-realm-view em{font-style:var(--g-italic)}.c-readme-view blockquote,.c-realm-view blockquote{border-left:solid var(--g-space-0-5) var(--s-color-border-tertiary);color:var(--s-color-text-secondary);margin-bottom:var(--cr-space-4);margin-top:var(--cr-space-4);padding-left:var(--g-space-3)}.c-readme-view blockquote>blockquote,.c-realm-view blockquote>blockquote{margin-bottom:var(--cr-space-7);margin-top:var(--cr-space-7)}.c-readme-view caption,.c-realm-view caption{color:var(--s-color-text-secondary);font-size:var(--g-font-size-100);margin-top:var(--cr-space-2);text-align:left}.c-readme-view q,.c-realm-view q{quotes:"“" "”"}.c-readme-view q:before,.c-realm-view q:before{content:open-quote}.c-readme-view q:after,.c-realm-view q:after{content:close-quote}.c-readme-view details,.c-realm-view details{margin-bottom:var(--cr-space-3);margin-top:var(--cr-space-3)}.c-readme-view summary,.c-realm-view summary{cursor:pointer;font-weight:var(--g-font-bold)}.c-readme-view math,.c-realm-view math{font-family:var(--g-font-family-mono)}.c-readme-view small,.c-realm-view small{font-size:var(--g-font-size-100)}.c-readme-view del,.c-realm-view del{-webkit-text-decoration:line-through;text-decoration:line-through}.c-readme-view sub,.c-realm-view sub{font-size:var(--g-font-size-50);vertical-align:sub}.c-readme-view sup,.c-realm-view sup{font-size:var(--g-font-size-50);padding-left:var(--space-px);vertical-align:middle}.c-readme-view sup>a,.c-realm-view sup>a{vertical-align:middle}.c-readme-view ol,.c-readme-view ul,.c-realm-view ol,.c-realm-view ul{margin-bottom:var(--cr-space-4);margin-top:var(--cr-space-4);padding-left:var(--g-space-4)}.c-readme-view ul,.c-realm-view ul{list-style:disc}.c-readme-view ol,.c-realm-view ol{list-style:decimal}.c-readme-view ol ol,.c-readme-view ol ul,.c-readme-view ul ol,.c-readme-view ul ul,.c-realm-view ol ol,.c-realm-view ol ul,.c-realm-view ul ol,.c-realm-view ul ul{margin-bottom:var(--cr-space-2);margin-top:var(--cr-space-2);padding-left:var(--g-space-4)}.c-readme-view li,.c-realm-view li{margin-bottom:var(--cr-space-1);margin-top:var(--cr-space-1)}.c-readme-view code,.c-readme-view pre,.c-realm-view code,.c-realm-view pre{font-family:var(--g-font-family-mono)}.c-readme-view pre,.c-readme-view pre.chroma-chroma,.c-realm-view pre,.c-realm-view pre.chroma-chroma{background-color:var(--s-color-bg-surface-primary);border-radius:var(--g-border-radius-sm);margin-bottom:var(--cr-space-3);margin-top:var(--cr-space-3);overflow-x:auto;padding:var(--cr-space-4)}.c-readme-view :not(pre)>code,.c-realm-view :not(pre)>code{background-color:var(--s-color-bg-surface-secondary);border-radius:var(--g-border-radius-sm);font-size:.96em;padding:var(--cr-space-0-5) var(--cr-space-1)}.c-readme-view a code,.c-realm-view a code{color:inherit}.c-readme-view hr,.c-realm-view hr{border-top:var(--s-border-secondary);margin-bottom:var(--cr-space-8);margin-top:var(--cr-space-8)}.c-readme-view table,.c-realm-view table{border-collapse:collapse;display:block;margin-bottom:var(--cr-space-5);margin-top:var(--cr-space-5);max-width:100%;width:100%}.c-readme-view td,.c-readme-view th,.c-realm-view td,.c-realm-view th{border:var(--s-border);padding:var(--cr-space-2) var(--cr-space-4);white-space:normal;word-break:break-word}.c-readme-view th,.c-realm-view th{background-color:var(--s-color-bg-surface-secondary);font-weight:var(--g-font-bold)}.c-readme-view button,.c-readme-view input,.c-readme-view select,.c-readme-view textarea,.c-realm-view button,.c-realm-view input,.c-realm-view select,.c-realm-view textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--s-color-bg-input);border:var(--s-border);padding:var(--cr-space-2) var(--cr-space-4)}.c-readme-view>.realm-view__btns:first-child+*,.c-readme-view>:first-child:not(.realm-view__btns),.c-realm-view>.realm-view__btns:first-child+*,.c-realm-view>:first-child:not(.realm-view__btns){margin-top:0!important}.c-readme-view .footnote-backref,.c-readme-view h1:not(.does-not-exist),.c-readme-view h2:not(.does-not-exist),.c-readme-view h3:not(.does-not-exist),.c-readme-view h4:not(.does-not-exist),.c-readme-view sup:not(.does-not-exist),.c-realm-view .footnote-backref,.c-realm-view h1:not(.does-not-exist),.c-realm-view h2:not(.does-not-exist),.c-realm-view h3:not(.does-not-exist),.c-realm-view h4:not(.does-not-exist),.c-realm-view sup:not(.does-not-exist){scroll-margin-top:var(--cr-space-24)}.c-readme-view .b-btn,.c-realm-view .b-btn{color:var(--s-color-text-secondary);display:inline-flex}.c-readme-view .b-btn:hover,.c-realm-view .b-btn:hover{-webkit-text-decoration:none;text-decoration:none}.c-readme-view .b-btn:first-child,.c-realm-view .b-btn:first-child{float:right;margin-top:var(--g-space-4)}.c-readme-view>.b-btn:first-child+*,.c-readme-view>:first-child:not(.b-btn),.c-realm-view>.b-btn:first-child+*,.c-realm-view>:first-child:not(.b-btn){margin-top:0}.c-readme-view{background-color:var(--s-color-bg-base);border-radius:var(--g-border-radius);margin-bottom:var(--g-space-6);padding:var(--g-space-6) var(--g-space-4) var(--g-space-4);width:100%}@media (min-width:calc(820 / 16 * 1rem)){.c-readme-view{grid-row-start:auto}}.b-gnome .hat,.b-logo .hat{fill:var(--s-logo-hat)}.b-gnome .beard,.b-logo .beard{fill:var(--s-logo-beard)}.b-banner{align-items:center;background-color:var(--s-color-bg-brand-default);color:var(--s-color-text-base);display:flex;font-size:var(--g-font-size-50);font-weight:var(--g-font-semibold);justify-content:center;padding:var(--g-space-1-5) var(--g-space-4);text-align:center;-webkit-text-decoration:none;text-decoration:none;width:100%}@media (min-width:calc(640 / 16 * 1rem)){.b-banner{font-size:var(--g-font-size-100)}}a.b-banner:hover{opacity:.9;-webkit-text-decoration:underline;text-decoration:underline}.b-header{background-color:var(--s-color-bg-base);border-bottom:var(--s-border);font-size:var(--g-font-size-100);position:sticky;top:0;z-index:var(--g-z-max)}.b-header nav{align-items:stretch;height:auto}.b-header .main-nav{align-items:stretch;display:flex;flex:1 1 auto;gap:var(--g-space-1);height:100%;min-width:0;padding-bottom:var(--g-space-2);padding-top:var(--g-space-2);width:100%}@media (min-width:calc(820 / 16 * 1rem)){.b-header .main-nav{grid-column:span 7}}.b-header .main-nav--explorer{grid-column:span 10}.b-header .user-picture{border:var(--s-border-secondary);border-radius:var(--s-rounded);cursor:pointer;flex-shrink:0;height:var(--g-space-10);width:var(--g-space-10)}.b-header .user-picture>svg{height:100%;width:100%}.b-main-navigation{color:var(--s-color-text-quaternary);height:auto;position:relative;width:100%}.b-main-navigation>.inner{align-items:center;background-color:var(--s-color-bg-surface-secondary);border:var(--s-border-secondary);border-radius:var(--s-rounded);height:100%;padding-left:var(--g-space-1-5);padding-right:var(--g-space-1-5);position:relative}@media (min-width:calc(640 / 16 * 1rem)){.b-main-navigation>.inner{padding-right:var(--g-space-8)}}.b-main-navigation>.inner:has([data-role=header-input-search]:focus-within){border-color:var(--s-color-border-tertiary)}.b-main-navigation .searchbar{bottom:0;color:var(--s-color-text-secondary);font-size:var(--g-font-size-200);font-weight:var(--g-font-medium);left:0;padding:var(--g-space-1-5);padding-right:var(--g-space-8);position:absolute;right:0;top:0}.b-main-navigation .searchbar>input{background-color:transparent;height:100%;outline:none;width:100%}.b-main-navigation .searchbar:focus-within+.b-breadcrumb{display:none}.b-main-navigation .network-toggle{align-items:center;background-color:var(--g-color-transparent);border-radius:var(--g-border-radius);cursor:pointer;display:none;height:calc(100% - 2px);justify-content:center;padding:var(--g-space-1-5);position:absolute;right:1px;top:1px;z-index:var(--g-z-max)}@media (min-width:calc(640 / 16 * 1rem)){.b-main-navigation .network-toggle{display:flex}}.b-main-navigation .network-toggle>svg{color:var(--s-color-text-tertiary);height:var(--g-space-5);width:var(--g-space-5)}.b-main-navigation .network-toggle:hover>svg{color:var(--s-color-text-tertiary-hover)}.b-main-navigation .b-popup-dialog>.inner{color:var(--s-color-text-tertiary);width:var(--g-space-72)}.b-main-navigation .b-popup-dialog header>span{color:var(--s-color-text-secondary);font-size:var(--g-font-size-100);font-weight:var(--g-font-semibold)}.b-main-navigation .b-popup-dialog .item{display:flex;gap:var(--g-space-1)}.b-main-navigation .b-popup-dialog .item>svg{height:var(--g-space-4);width:var(--g-space-4)}.b-main-navigation .b-popup-dialog .item-content{display:flex;flex-direction:column}.b-main-navigation .b-popup-dialog .item-label{font-size:var(--g-font-size-50)}.b-main-navigation .b-popup-dialog .item-value{color:var(--s-color-text-secondary);font-size:var(--g-font-size-100);font-weight:var(--g-font-semibold)}.b-main-menu{display:flex;flex:0 0 auto;grid-column:span 3;height:var(--g-space-12)}@media (min-width:calc(640 / 16 * 1rem)){.b-main-menu{height:auto}}.b-main-menu .menu-toggle{align-items:center;cursor:pointer;display:flex;margin-left:auto;order:3}.b-main-menu .menu-toggle>svg{height:var(--g-space-5);margin-left:var(--g-space-4);width:var(--g-space-5)}@media (min-width:calc(820 / 16 * 1rem)){.b-main-menu .menu-toggle>svg{margin-left:var(--g-space-2)}}.b-main-menu .menu-toggle-input~.menu-dev{display:none}.b-main-menu .menu-toggle-input:checked~.menu-dev{display:flex}.b-main-menu .menu-toggle-input:checked~.menu-general{display:none}.b-main-menu .menu-dev,.b-main-menu .menu-general{display:flex;height:100%;justify-content:flex-end}.b-menu-link:last-child,.b-menu-link:last-child .link{margin-right:0}.b-menu-link .link{align-items:center;color:var(--s-color-text-tertiary);display:flex;font-size:var(--g-font-size-100);font-weight:var(--g-font-semibold);gap:var(--g-space-1);height:100%;margin-right:var(--g-space-3);position:relative}.b-menu-link .link:hover{color:var(--s-color-text-tertiary-hover)}.b-menu-link .link:after{background-color:var(--s-color-bg-brand-default);border-radius:var(--s-rounded) var(--s-rounded) 0 0;bottom:0;content:"";height:var(--g-space-1);left:0;position:absolute;transition:width var(--g-transition-fast);width:0}.b-menu-link .link>svg{flex-shrink:0;height:var(--g-space-5);min-width:var(--g-space-2);width:var(--g-space-5)}@media (min-width:calc(1020 / 16 * 1rem)){.b-menu-link .link>svg{display:none}}@media (min-width:calc(1366 / 16 * 1rem)){.b-menu-link .link>svg{display:inline-block;height:var(--g-space-4-5);width:var(--g-space-4-5)}}@media (min-width:calc(640 / 16 * 1rem)){.b-menu-link .link{font-weight:var(--g-font-bold)}}@media (min-width:calc(1366 / 16 * 1rem)){.b-menu-link .link{margin-right:var(--g-space-6);padding-right:var(--g-space-1)}}@media (min-width:calc(640 / 16 * 1rem)){.b-menu-link .link-label{display:none}}@media (min-width:calc(1020 / 16 * 1rem)){.b-menu-link .link-label{display:inline}}.b-menu-link .link--icon{font-weight:var(--g-font-regular);margin-right:var(--g-space-4)}@media (min-width:calc(480 / 16 * 1rem)){.b-menu-link .link--icon{margin-right:var(--g-space-6)}}.b-menu-link .link--is-active{color:var(--s-color-text-secondary)}.b-menu-link .link--is-active:after{width:100%}.b-menu-link .link--is-active>svg{color:var(--s-color-bg-brand-default)}.menu-general .link{color:var(--s-color-text-secondary)}.menu-general .link:hover{color:var(--s-color-text-link-hover)}.b-breadcrumb{display:flex}.b-breadcrumb,.b-breadcrumb:after{background-color:var(--s-color-bg-surface-secondary)}.b-breadcrumb:after{bottom:0;content:"";display:block;left:0;pointer-events:none;position:absolute;right:0;top:0}.b-breadcrumb>ol{color:var(--s-color-text-primary);display:flex;font-weight:var(--g-font-semibold);line-height:var(--g-line-height-snug)}.b-breadcrumb .argument,.b-breadcrumb .element,.b-breadcrumb .query{align-items:center;display:flex;white-space:nowrap;z-index:var(--g-z-1)}.b-breadcrumb .argument:not(:first-child):before,.b-breadcrumb .element:not(:first-child):before,.b-breadcrumb .query:not(:first-child):before{color:var(--s-color-text-tertiary);content:"/";line-height:var(--g-line-height-normal);padding-left:.18rem;padding-right:.18rem;padding-top:var(--g-space-px)}.b-breadcrumb .argument a,.b-breadcrumb .element a,.b-breadcrumb .query a{background-color:var(--s-color-bg-base);border:1px solid var(--s-color-border-transparent);border-radius:var(--s-rounded-sm);display:inline-block;min-width:var(--g-space-4);padding:var(--g-space-0-5);text-align:center}.b-breadcrumb .argument a:hover,.b-breadcrumb .element a:hover,.b-breadcrumb .query a:hover{background-color:var(--s-color-bg-brand-default);color:var(--s-color-text-base)}.b-breadcrumb .argument:not(:first-child):before{content:":"}.b-breadcrumb .argument a{background-color:var(--s-color-bg-surface-quaternary);color:var(--s-color-text-base)}.b-breadcrumb .query:not(:first-child):before{content:"&"}.b-breadcrumb .query:nth-child(1 of .query):before{content:"?"}.b-breadcrumb .query label{background-color:var(--s-color-bg-surface-primary);border:var(--s-border);border-radius:var(--s-rounded-sm);color:var(--s-color-text-secondary);cursor:text;display:inline-flex;height:100%;min-width:var(--g-space-4);padding:var(--g-space-0-5) var(--g-space-1);position:relative;text-align:center;width:100%}.b-breadcrumb .query label:focus-within{border-color:var(--s-color-border-quaternary)}.b-breadcrumb .query label:hover{border-color:var(--s-color-border-quaternary)}.b-breadcrumb .query input{background-color:var(--s-color-bg-surface-primary);max-width:10ch;order:3;outline:none;field-sizing:content}@supports not (field-sizing:content){.b-breadcrumb .query input{width:5rem!important}}.b-breadcrumb .query input::-moz-placeholder{opacity:0}.b-breadcrumb .query input::placeholder{opacity:0}.b-breadcrumb .query input:-moz-placeholder{width:var(--g-space-px)}.b-breadcrumb .query input:placeholder-shown{width:var(--g-space-px)}.b-breadcrumb .query input:placeholder-shown::-moz-placeholder{color:var(--g-color-transparent)}.b-breadcrumb .query input:-moz-placeholder::placeholder{color:var(--g-color-transparent)}.b-breadcrumb .query input:placeholder-shown::placeholder{color:var(--g-color-transparent)}.b-footer{border-top:var(--s-border);font-size:var(--g-font-size-100);padding-bottom:var(--g-space-4);padding-top:var(--g-space-4);width:100%}.b-footer>nav{flex-direction:column;row-gap:var(--g-space-8)}@media (min-width:calc(640 / 16 * 1rem)){.b-footer>nav{flex-wrap:wrap}}.b-footer .logo{color:var(--s-color-text-primary);grid-column:1/-1;width:var(--g-space-44)}.b-footer .logo:hover{color:var(--s-color-text-primary);-webkit-text-decoration:none;text-decoration:none}@media (min-width:calc(1020 / 16 * 1rem)){.b-footer .logo{align-self:center;grid-column:1/3;grid-row:1/1;width:60%}}.b-footer .nav-primary{display:flex;gap:var(--g-space-10);grid-column:1/-1;grid-row:2/3}@media (min-width:calc(640 / 16 * 1rem)){.b-footer .nav-primary{align-items:center;flex:1 0 0%;flex-direction:row;gap:var(--g-space-6);justify-content:space-between}}@media (min-width:calc(1020 / 16 * 1rem)){.b-footer .nav-primary{grid-column:2/8;grid-row:1/1}}.b-footer .nav-primary>ul{display:flex;flex:1;flex-direction:column;flex-wrap:wrap;gap:var(--g-space-1) var(--g-space-3)}@media (min-width:calc(640 / 16 * 1rem)){.b-footer .nav-primary>ul{flex:initial;flex-direction:row}.b-footer .nav-social{margin-left:auto}}@media (min-width:calc(820 / 16 * 1rem)){.b-footer .nav-social{grid-column:span 3;justify-self:end;margin-left:0}}.b-footer .nav-theme{align-items:center;display:flex;gap:var(--g-space-2)}@media (min-width:calc(640 / 16 * 1rem)){.b-footer .nav-theme{flex-basis:100%}}@media (min-width:calc(820 / 16 * 1rem)){.b-footer .nav-theme{grid-column:span 3}}.b-footer .nav-theme .nav-theme-label{color:var(--s-color-text-secondary)}.b-footer .nav-theme:has([data-theme-target=sun]:not(.u-hidden)) .nav-theme-label:before{content:"Light"}.b-footer .nav-theme:has([data-theme-target=moon]:not(.u-hidden)) .nav-theme-label:before{content:"Dark"}.b-footer .nav-theme:has([data-theme-target=system]:not(.u-hidden))
+:root{--g-px-base:16;--g-space-mult:4;--g-space-base:calc(1rem/var(--g-space-mult));--g-breakpoint-max:calc(1580/var(--g-px-base)*1rem);--g-z-min:-1;--g-z-1:1;--g-z-max:9999;--g-duration-75:75ms;--g-duration-150:150ms;--g-opacity-50:0.5;--g-grid-1:repeat(1,minmax(0,1fr));--g-grid-10:repeat(10,minmax(0,1fr));--g-space-px:1px;--g-space-0-5:calc(var(--g-space-base)*0.5);--g-space-1:var(--g-space-base);--g-space-1-5:calc(var(--g-space-base)*1.5);--g-space-2:calc(var(--g-space-base)*2);--g-space-2-5:calc(var(--g-space-base)*2.5);--g-space-3:calc(var(--g-space-base)*3);--g-space-4:calc(var(--g-space-base)*4);--g-space-4-5:calc(var(--g-space-base)*4.5);--g-space-5:calc(var(--g-space-base)*5);--g-space-6:calc(var(--g-space-base)*6);--g-space-7:calc(var(--g-space-base)*7);--g-space-8:calc(var(--g-space-base)*8);--g-space-10:calc(var(--g-space-base)*10);--g-space-12:calc(var(--g-space-base)*12);--g-space-14:calc(var(--g-space-base)*14);--g-space-20:calc(var(--g-space-base)*20);--g-space-24:calc(var(--g-space-base)*24);--g-space-28:calc(var(--g-space-base)*28);--g-space-32:calc(var(--g-space-base)*32);--g-space-36:calc(var(--g-space-base)*36);--g-space-44:calc(var(--g-space-base)*44);--g-space-48:calc(var(--g-space-base)*48);--g-space-52:calc(var(--g-space-base)*52);--g-space-72:calc(var(--g-space-base)*72);--g-space-96:calc(var(--g-space-base)*96);--g-font-size-50:calc(12/var(--g-px-base)*1rem);--g-font-size-100:calc(14/var(--g-px-base)*1rem);--g-font-size-200:calc(16/var(--g-px-base)*1rem);--g-font-size-300:calc(18/var(--g-px-base)*1rem);--g-font-size-400:calc(20/var(--g-px-base)*1rem);--g-font-size-500:calc(22/var(--g-px-base)*1rem);--g-font-size-600:calc(24/var(--g-px-base)*1rem);--g-font-size-700:calc(32/var(--g-px-base)*1rem);--g-font-size-800:calc(38/var(--g-px-base)*1rem);--g-font-family-mono:"Roboto",'Menlo, Consolas, "Ubuntu Mono", "Roboto Mono", "DejaVu Sans Mono", monospace';--g-font-family-inter-var:"Inter",'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", sans-serif';--g-font-normal:400;--g-font-medium:500;--g-font-semibold:600;--g-font-bold:700;--g-italic:oblique 14deg;--g-line-height-tight:1.25;--g-line-height-snug:1.375;--g-line-height-normal:1.5;--g-border-radius-sm:calc(4/var(--g-px-base)*1rem);--g-border-radius:calc(6/var(--g-px-base)*1rem);--g-border-radius-full:9999px;--g-color-light:#fff;--g-color-transparent:transparent;--g-color-gray-50:#f0f0f0;--g-color-gray-100:#e2e2e2;--g-color-gray-200:#bdbdbd;--g-color-gray-300:#999;--g-color-gray-400:#7c7c7c;--g-color-gray-500:#696969;--g-color-gray-600:#585858;--g-color-gray-700:#292929;--g-color-gray-750:#1f1f1f;--g-color-gray-800:#141414;--g-color-gray-850:#0e0e0e;--g-color-gray-900:#090909;--g-color-green-50:#e7efed;--g-color-green-400:#60ab96;--g-color-green-500:#277b63;--g-color-green-600:#226c57;--g-color-green-900:#144134;--g-color-green-950:#002c20;--g-color-blue-400:#49afeb;--g-color-blue-600:#3e96c9;--g-color-blue-900:#21506b;--g-color-yellow-50:#fff7eb;--g-color-yellow-400:#facc32;--g-color-yellow-600:#fbbf24;--g-color-yellow-900:#7b4807;--g-color-yellow-950:#362600;--g-color-red-400:#eb6c49;--g-color-red-600:#c95c3e;--g-color-red-900:#6b2521;--g-color-purple-400:#7f49eb;--g-color-purple-600:#6c3ec9;--g-color-purple-900:#39216b}@supports (color:color(display-p3 0 0 0%)){:root{--g-color-green-950:#002c20;--g-color-yellow-50:#fff7eb;--g-color-yellow-950:#362600}@media (color-gamut:p3){:root{--g-color-green-950:color(display-p3 0.04602 0.17026 0.1277);--g-color-yellow-50:color(display-p3 0.99709 0.97106 0.92232);--g-color-yellow-950:color(display-p3 0.2031 0.15112 0.01811)}}}:root{--s-color-bg-base:var(--g-color-light,#fff);--s-color-bg-base-dev:var(--g-color-gray-50,#f0f0f0);--s-color-bg-surface-primary:var(--g-color-gray-50,#f0f0f0);--s-color-bg-surface-primary-hover:var(--g-color-gray-100,#f0f0f0);--s-color-bg-surface-secondary:var(--g-color-gray-100,#e2e2e2);--s-color-bg-surface-quaternary:var(--g-color-gray-400,#7c7c7c);--s-color-bg-brand-default:var(--g-color-green-600,#226c57);--s-color-bg-brand-weak:var(--g-color-green-50,#f0f9ff);--s-color-bg-success-default:var(--g-color-green-600,#144134);--s-color-bg-info-default:var(--g-color-blue-600,#21506b);--s-color-bg-warning-default:var(--g-color-yellow-600,#665100);--s-color-bg-warning-weak:var(--g-color-yellow-50,#f9d985);--s-color-bg-warning-action:var(--g-color-yellow-400,#f9d985);--s-color-bg-caution-default:var(--g-color-red-600,#610);--s-color-bg-tip-default:var(--g-color-purple-600,#49216b);--s-color-bg-note-default:var(--g-color-gray-600,#21506b);--s-color-bg-input:var(--g-color-light,#fff);--s-color-text-base:var(--g-color-light,#fff);--s-color-text-primary:var(--g-color-gray-900,#080809);--s-color-text-secondary:var(--g-color-gray-600,#454a4e);--s-color-text-tertiary:var(--g-color-gray-400,#f0f0f0);--s-color-text-tertiary-hover:var(--g-color-gray-600,#e2e2e2);--s-color-text-quaternary:var(--g-color-gray-100,#f0f0f0);--s-color-text-muted:var(--g-color-gray-300,#999);--s-color-text-brand-default:var(--g-color-light,#fff);--s-color-text-link:var(--g-color-green-600,#226c57);--s-color-text-link-hover:var(--g-color-green-600,#226c57);--s-color-text-success:var(--g-color-green-900,#144134);--s-color-text-info:var(--g-color-blue-900,#21506b);--s-color-text-warning:var(--g-color-yellow-900,#665100);--s-color-text-caution:var(--g-color-red-900,#610);--s-color-text-tip:var(--g-color-purple-900,#49216b);--s-color-border-primary:var(--g-color-gray-200,#bdbdbd);--s-color-border-secondary:var(--g-color-gray-100,#e2e2e2);--s-color-border-tertiary:var(--g-color-gray-300,#999);--s-color-border-quaternary:var(--g-color-gray-400,#7c7c7c);--s-color-border-transparent:var(--g-color-transparent,transparent);--s-color-border-input:var(--g-color-gray-300,#999);--s-color-border-brand-default:var(--g-color-green-600,#226c57);--s-color-border-success:var(--g-color-green-600,#144134);--s-color-border-info:var(--g-color-blue-600,#21506b);--s-color-border-warning:var(--g-color-yellow-600,#665100);--s-color-border-error:var(--g-color-red-600,#610);--s-color-border-tip:var(--g-color-purple-600,#49216b);--s-color-border-note:var(--g-color-gray-600,#21506b);--s-rounded-sm:var(--g-border-radius-sm,4px);--s-rounded:var(--g-border-radius,6px);--s-rounded-full:var(--g-border-radius-full,9999px);--s-border:var(--g-space-px,1px) solid var(--s-color-border-primary);--s-border-secondary:var(--g-space-px,1px) solid var(--s-color-border-secondary);--s-logo-hat:var(--g-color-green-600,#226c57);--s-logo-beard:var(--g-color-gray-300,#999)}[data-theme=dark]{--s-color-bg-base:var(--g-color-gray-850);--s-color-bg-base-dev:var(--g-color-gray-800);--s-color-bg-surface-primary:var(--g-color-gray-800);--s-color-bg-surface-primary-hover:var(--g-color-gray-750);--s-color-bg-surface-secondary:var(--g-color-gray-750);--s-color-bg-surface-quaternary:var(--g-color-gray-600);--s-color-bg-brand-weak:var(--g-color-green-950);--s-color-bg-warning-weak:var(--g-color-yellow-950);--s-color-bg-input:var(--g-color-gray-800);--s-color-text-primary:var(--g-color-gray-100);--s-color-text-secondary:var(--g-color-gray-200);--s-color-text-tertiary:var(--g-color-gray-400);--s-color-text-tertiary-hover:var(--g-color-gray-300);--s-color-text-quaternary:var(--g-color-gray-500);--s-color-text-muted:var(--g-color-gray-500);--s-color-text-brand-default:var(--g-color-light);--s-color-text-link:var(--g-color-green-500);--s-color-text-link-hover:var(--g-color-green-400);--s-color-text-success:var(--g-color-green-400);--s-color-text-info:var(--g-color-blue-400);--s-color-text-warning:var(--g-color-yellow-400);--s-color-text-caution:var(--g-color-red-400);--s-color-text-tip:var(--g-color-purple-400);--s-color-border-primary:var(--g-color-gray-700);--s-color-border-secondary:var(--g-color-gray-750);--s-color-border-tertiary:var(--g-color-gray-600);--s-color-border-quaternary:var(--g-color-gray-500);--s-color-border-input:var(--g-color-gray-700);--s-color-border-brand-default:var(--g-color-green-600);--s-color-border-success:var(--g-color-green-400);--s-color-border-info:var(--g-color-blue-400);--s-color-border-warning:var(--g-color-yellow-400);--s-color-border-error:var(--g-color-red-400);--s-color-border-tip:var(--g-color-purple-400);--s-color-border-note:var(--g-color-gray-600);--s-logo-hat:#fff;--s-logo-beard:grey}*,::backdrop,::file-selector-button,:after,:before{border:0 solid;box-sizing:border-box;margin:0;padding:0}html{font-family:ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}h1,h2,h3{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub{bottom:-.25em;font-size:75%;line-height:0;position:relative;vertical-align:baseline}table{border-collapse:collapse;border-color:inherit;text-indent:0}summary{display:list-item}menu,ol,ul{list-style:none}embed,img,object,svg{display:block;vertical-align:middle}img{height:auto;max-width:100%}::file-selector-button,button,input,select,textarea{background-color:transparent;border-radius:0;color:inherit;font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;opacity:1}::file-selector-button{margin-right:4px}::-moz-placeholder{opacity:1}::placeholder{opacity:1}@supports (not (-webkit-appearance:-apple-pay-button)) or (contain-intrinsic-size:1px){::-moz-placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-meridiem-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-year-field{padding-bottom:0;padding-top:0}::-webkit-calendar-picker-indicator{line-height:1}::file-selector-button,button,input:where([type=button],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}@font-face{font-display:swap;font-family:Roboto;font-style:normal;font-weight:900;src:url(fonts/roboto/roboto-mono-normal.woff2) format("woff2"),url(fonts/roboto/roboto-mono-normal.woff) format("woff")}@font-face{font-display:block;font-family:Inter;font-style:oblique 0deg 10deg;font-variant:normal;font-weight:100 900;src:url(fonts/intervar/Intervar.woff2) format("woff2")}html{background-color:var(--s-color-bg-base);color:var(--s-color-text-secondary);font-family:var(--g-font-family-inter-var);font-feature-settings:"kern" on,"liga" on,"calt" off,"zero" on,contextual common-ligatures,"kern";-webkit-font-feature-settings:"kern" on,"liga" on,"calt" off,"zero" on;font-size:calc(var(--g-px-base)*1px);line-height:var(--g-line-height-normal);-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-kerning:normal;font-variant-ligatures:contextual common-ligatures;text-rendering:optimizeLegibility}body{display:flex;flex-direction:column;min-height:100vh}main{background-color:var(--s-color-bg-base);flex-grow:2;width:100%}main.dev-mode{background-color:var(--s-color-bg-base-dev)}main>section{display:grid;grid-auto-flow:dense;grid-template-columns:var(--g-grid-1);grid-column-gap:var(--g-space-20);-moz-column-gap:var(--g-space-20);column-gap:var(--g-space-20);min-height:100%;padding-left:var(--g-space-4);padding-right:var(--g-space-4)}@media (min-width:calc(640 / 16 * 1rem)){main>section{padding-left:var(--g-space-10);padding-right:var(--g-space-10)}}@media (min-width:calc(820 / 16 * 1rem)){main>section{grid-template-columns:var(--g-grid-10)}}@media (min-width:calc(1366 / 16 * 1rem)){main>section{-moz-column-gap:var(--g-space-32);column-gap:var(--g-space-32)}}svg{max-height:100%;max-width:100%}form{margin-bottom:0;margin-top:0}code{font-family:var(--g-font-mono)}summary{cursor:pointer}md-renderer{margin-top:var(--g-space-4);padding-bottom:var(--g-space-24)}@media (min-width:calc(820 / 16 * 1rem)){md-renderer{grid-column:span 7;margin-top:0}}::-moz-selection{background-color:var(--s-color-bg-brand-default);color:var(--s-color-text-base)}::selection{background-color:var(--s-color-bg-brand-default);color:var(--s-color-text-base)}summary::-webkit-details-marker{display:none}summary::marker{display:none}.c-stack{display:flex;flex-direction:column;justify-content:flex-start}.c-stack>*+*{margin-top:var(--g-space-4)}.c-inline{align-items:center;display:inline-flex;gap:var(--g-space-3)}.c-between{align-items:center;display:flex;justify-content:space-between}.c-center{box-sizing:border-box;margin-left:auto;margin-right:auto;max-width:var(--g-breakpoint-max);padding-left:var(--g-space-4);padding-right:var(--g-space-4)}@media (min-width:calc(640 / 16 * 1rem)){.c-center{padding-left:var(--g-space-10);padding-right:var(--g-space-10)}}.c-full-screen{align-items:center;display:flex;flex-direction:column;grid-column:1/-1;height:100%;justify-content:center;margin-top:var(--g-space-10);padding-bottom:var(--g-space-24);width:100%}.c-reel{display:flex;overflow:scroll}.c-icon{flex-shrink:0;height:1.15em;width:1.15em}.c-with-icon{align-items:flex-start;display:inline-flex}.c-with-icon .c-icon,.c-with-icon--inline .c-icon{margin-left:.3em;margin-right:.3em;margin-top:.15em}.c-with-icon--inline{display:inline-block}.c-with-icon--inline>*{vertical-align:middle}.c-with-icon--inline .c-icon{margin-top:0}.c-view-grid{display:flex;flex-direction:column}@media (min-width:calc(640 / 16 * 1rem)){.c-view-grid{-moz-column-gap:var(--g-space-8);column-gap:var(--g-space-8);flex-direction:row}}@media (min-width:calc(820 / 16 * 1rem)){.c-view-grid{display:grid;grid-template-columns:var(--g-grid-10);grid-column-gap:var(--g-space-20);-moz-column-gap:var(--g-space-20);column-gap:var(--g-space-20)}}@media (min-width:calc(1366 / 16 * 1rem)){.c-view-grid{-moz-column-gap:var(--g-space-32);column-gap:var(--g-space-32)}}.c-toggle-btn>input{display:none}.c-toggle-btn label{visibility:hidden}.c-toggle-btn input:checked+label{visibility:visible}.c-readme-view,.c-realm-view{--cr-px-base:var(--g-px-base);--cr-space-mult:1;--cr-space-base:calc(1em/var(--g-space-mult)*var(--cr-space-mult));--cr-space-0:0;--cr-space-0-5:calc(var(--cr-space-base)*0.5);--cr-space-1:var(--cr-space-base);--cr-space-2:calc(var(--cr-space-base)*2);--cr-space-3:calc(var(--cr-space-base)*3);--cr-space-4:calc(var(--cr-space-base)*4);--cr-space-5:calc(var(--cr-space-base)*5);--cr-space-7:calc(var(--cr-space-base)*7);--cr-space-8:calc(var(--cr-space-base)*8);--cr-space-24:calc(var(--cr-space-base)*24);--cr-color-brand-default:var(--s-color-text-link);display:block;font-size:calc(var(--cr-px-base)*1px);padding-top:var(--g-space-4);word-break:break-word}.c-readme-view:empty,.c-realm-view:empty{display:none}.c-realm-view:has(.b-btn:only-child){display:none}.c-readme-view:has(.b-btn:only-child){display:none}@media (min-width:calc(820 / 16 * 1rem)){.c-readme-view,.c-realm-view{grid-row-start:1;padding-top:var(--g-space-6)}}.c-readme-view a,.c-realm-view a{color:var(--cr-color-brand-default);display:inline-block;font-weight:inherit;position:relative;text-wrap:balance;vertical-align:top}.c-readme-view a:hover,.c-realm-view a:hover{-webkit-text-decoration:underline;text-decoration:underline}.c-realm-view a:has(>img){vertical-align:middle}.c-readme-view a:has(>img){vertical-align:middle}.c-readme-view a>span,.c-realm-view a>span{margin-bottom:.1em}.c-readme-view a>.tooltip+.tooltip,.c-realm-view a>.tooltip+.tooltip{margin-left:.2em}.c-readme-view a>.tooltip:last-of-type,.c-realm-view a>.tooltip:last-of-type{margin-right:.2em}.c-realm-view a:has(>img:first-child):has(.tooltip:last-child):not(:has(>:nth-child(3)))>.tooltip{background-color:var(--s-color-bg-base);border-radius:var(--g-border-radius-full);bottom:var(--g-space-2);left:var(--g-space-2);margin-left:0;position:absolute}.c-readme-view a:has(>img:first-child):has(.tooltip:last-child):not(:has(>:nth-child(3)))>.tooltip{background-color:var(--s-color-bg-base);border-radius:var(--g-border-radius-full);bottom:var(--g-space-2);left:var(--g-space-2);margin-left:0;position:absolute}.c-realm-view a:has(>img:first-child):has(.tooltip+.tooltip:last-child):not(:has(>:nth-child(4)))>.tooltip{background-color:var(--s-color-bg-base);border-radius:var(--g-border-radius-full);bottom:var(--g-space-2);left:var(--g-space-2);margin-left:0;position:absolute}.c-readme-view a:has(>img:first-child):has(.tooltip+.tooltip:last-child):not(:has(>:nth-child(4)))>.tooltip{background-color:var(--s-color-bg-base);border-radius:var(--g-border-radius-full);bottom:var(--g-space-2);left:var(--g-space-2);margin-left:0;position:absolute}.c-realm-view a:has(>img:first-child):has(.tooltip+.tooltip:last-child):not(:has(>:nth-child(4)))>.tooltip:first-of-type{bottom:var(--g-space-2);left:var(--g-space-7);position:absolute}.c-readme-view a:has(>img:first-child):has(.tooltip+.tooltip:last-child):not(:has(>:nth-child(4)))>.tooltip:first-of-type{bottom:var(--g-space-2);left:var(--g-space-7);position:absolute}.c-readme-view h1+h2,.c-readme-view h2+h3,.c-readme-view h3+h4,.c-realm-view h1+h2,.c-realm-view h2+h3,.c-realm-view h3+h4{margin-top:var(--cr-space-4)}.c-readme-view h1,.c-readme-view h2,.c-readme-view h3,.c-readme-view h4,.c-realm-view h1,.c-realm-view h2,.c-realm-view h3,.c-realm-view h4{color:var(--s-color-text-primary);line-height:var(--g-line-height-tight);margin-top:var(--cr-space-4)}.c-readme-view h1,.c-realm-view h1{font-size:var(--g-font-size-700);font-weight:var(--g-font-bold);margin-bottom:var(--cr-space-2)}@media (min-width:calc(640 / 16 * 1rem)){.c-readme-view h1,.c-realm-view h1{font-size:var(--g-font-size-800)}}.c-readme-view h2,.c-realm-view h2{font-size:var(--g-font-size-500);font-weight:var(--g-font-bold)}@media (min-width:calc(640 / 16 * 1rem)){.c-readme-view h2,.c-realm-view h2{font-size:var(--g-font-size-600)}}.c-readme-view h2 *,.c-realm-view h2 *{font-weight:var(--g-font-bold)}.c-readme-view h3,.c-readme-view h4,.c-realm-view h3,.c-realm-view h4{color:var(--s-color-text-secondary);font-weight:var(--g-font-semibold)}.c-readme-view h3,.c-realm-view h3{font-size:var(--g-font-size-400);margin-top:var(--cr-space-4)}.c-readme-view h4,.c-realm-view h4{font-size:var(--g-font-size-300);margin-top:var(--cr-space-3)}@media (min-width:calc(640 / 16 * 1rem)){.c-readme-view h4,.c-realm-view h4{font-size:var(--g-font-size-300)}}.c-readme-view h3 *,.c-readme-view h4 *,.c-realm-view h3 *,.c-realm-view h4 *{font-weight:var(--g-font-semibold)}.c-readme-view h5,.c-readme-view h6,.c-realm-view h5,.c-realm-view h6{font-size:var(--g-font-size-300);font-weight:var(--g-font-bold);margin-bottom:var(--cr-space-0);margin-top:var(--cr-space-0)}.c-readme-view h5+p,.c-readme-view h6+p,.c-realm-view h5+p,.c-realm-view h6+p{margin-top:var(--cr-space-0)}.c-readme-view img,.c-realm-view img{border:1px solid var(--s-color-bg-surface-primary);border-radius:var(--g-border-radius-sm);margin-bottom:var(--cr-space-2);margin-top:var(--cr-space-2);max-width:100%;-webkit-user-select:none;-moz-user-select:none;user-select:none}.c-readme-view figure,.c-realm-view figure{margin-bottom:var(--cr-space-3);margin-top:var(--cr-space-3);text-align:center}.c-readme-view figcaption,.c-realm-view figcaption{color:var(--s-color-text-secondary);font-size:var(--g-font-size-100)}.c-readme-view video,.c-realm-view video{margin-bottom:var(--g-space-4);margin-top:var(--g-space-4);max-width:100%}.c-readme-view p,.c-realm-view p{margin-bottom:var(--cr-space-3);margin-top:var(--cr-space-3)}.c-realm-view p:has(>a:only-child>img){margin-bottom:var(--cr-space-4);margin-top:var(--cr-space-4)}.c-readme-view p:has(>a:only-child>img){margin-bottom:var(--cr-space-4);margin-top:var(--cr-space-4)}.c-realm-view p:has(>a:only-child>img) img{margin-bottom:0;margin-top:0}.c-readme-view p:has(>a:only-child>img) img{margin-bottom:0;margin-top:0}.c-readme-view strong,.c-readme-view strong *,.c-realm-view strong,.c-realm-view strong *{font-weight:var(--g-font-bold)}.c-readme-view em,.c-realm-view em{font-style:var(--g-italic)}.c-readme-view blockquote,.c-realm-view blockquote{border-left:solid var(--g-space-0-5) var(--s-color-border-tertiary);color:var(--s-color-text-secondary);margin-bottom:var(--cr-space-4);margin-top:var(--cr-space-4);padding-left:var(--g-space-3)}.c-readme-view blockquote>blockquote,.c-realm-view blockquote>blockquote{margin-bottom:var(--cr-space-7);margin-top:var(--cr-space-7)}.c-readme-view caption,.c-realm-view caption{color:var(--s-color-text-secondary);font-size:var(--g-font-size-100);margin-top:var(--cr-space-2);text-align:left}.c-readme-view q,.c-realm-view q{quotes:"“" "”"}.c-readme-view q:before,.c-realm-view q:before{content:open-quote}.c-readme-view q:after,.c-realm-view q:after{content:close-quote}.c-readme-view details,.c-realm-view details{margin-bottom:var(--cr-space-3);margin-top:var(--cr-space-3)}.c-readme-view summary,.c-realm-view summary{cursor:pointer;font-weight:var(--g-font-bold)}.c-readme-view math,.c-realm-view math{font-family:var(--g-font-family-mono)}.c-readme-view small,.c-realm-view small{font-size:var(--g-font-size-100)}.c-readme-view del,.c-realm-view del{-webkit-text-decoration:line-through;text-decoration:line-through}.c-readme-view sub,.c-realm-view sub{font-size:var(--g-font-size-50);vertical-align:sub}.c-readme-view sup,.c-realm-view sup{font-size:var(--g-font-size-50);padding-left:var(--space-px);vertical-align:middle}.c-readme-view sup>a,.c-realm-view sup>a{vertical-align:middle}.c-readme-view ol,.c-readme-view ul,.c-realm-view ol,.c-realm-view ul{margin-bottom:var(--cr-space-4);margin-top:var(--cr-space-4);padding-left:var(--g-space-4)}.c-readme-view ul,.c-realm-view ul{list-style:disc}.c-readme-view ol,.c-realm-view ol{list-style:decimal}.c-readme-view ol ol,.c-readme-view ol ul,.c-readme-view ul ol,.c-readme-view ul ul,.c-realm-view ol ol,.c-realm-view ol ul,.c-realm-view ul ol,.c-realm-view ul ul{margin-bottom:var(--cr-space-2);margin-top:var(--cr-space-2);padding-left:var(--g-space-4)}.c-readme-view li,.c-realm-view li{margin-bottom:var(--cr-space-1);margin-top:var(--cr-space-1)}.c-readme-view code,.c-readme-view pre,.c-realm-view code,.c-realm-view pre{font-family:var(--g-font-family-mono)}.c-readme-view pre,.c-readme-view pre.chroma-chroma,.c-realm-view pre,.c-realm-view pre.chroma-chroma{background-color:var(--s-color-bg-surface-primary);border-radius:var(--g-border-radius-sm);margin-bottom:var(--cr-space-3);margin-top:var(--cr-space-3);overflow-x:auto;padding:var(--cr-space-4)}.c-readme-view :not(pre)>code,.c-realm-view :not(pre)>code{background-color:var(--s-color-bg-surface-secondary);border-radius:var(--g-border-radius-sm);font-size:.96em;padding:var(--cr-space-0-5) var(--cr-space-1)}.c-readme-view a code,.c-realm-view a code{color:inherit}.c-readme-view hr,.c-realm-view hr{border-top:var(--s-border-secondary);margin-bottom:var(--cr-space-8);margin-top:var(--cr-space-8)}.c-readme-view table,.c-realm-view table{border-collapse:collapse;display:block;margin-bottom:var(--cr-space-5);margin-top:var(--cr-space-5);max-width:100%;width:100%}.c-readme-view td,.c-readme-view th,.c-realm-view td,.c-realm-view th{border:var(--s-border);padding:var(--cr-space-2) var(--cr-space-4);white-space:normal;word-break:break-word}.c-readme-view th,.c-realm-view th{background-color:var(--s-color-bg-surface-secondary);font-weight:var(--g-font-bold)}.c-readme-view button,.c-readme-view input,.c-readme-view select,.c-readme-view textarea,.c-realm-view button,.c-realm-view input,.c-realm-view select,.c-realm-view textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--s-color-bg-input);border:var(--s-border);padding:var(--cr-space-2) var(--cr-space-4)}.c-readme-view>.realm-view__btns:first-child+*,.c-readme-view>:first-child:not(.realm-view__btns),.c-realm-view>.realm-view__btns:first-child+*,.c-realm-view>:first-child:not(.realm-view__btns){margin-top:0!important}.c-readme-view .footnote-backref,.c-readme-view h1:not(.does-not-exist),.c-readme-view h2:not(.does-not-exist),.c-readme-view h3:not(.does-not-exist),.c-readme-view h4:not(.does-not-exist),.c-readme-view sup:not(.does-not-exist),.c-realm-view .footnote-backref,.c-realm-view h1:not(.does-not-exist),.c-realm-view h2:not(.does-not-exist),.c-realm-view h3:not(.does-not-exist),.c-realm-view h4:not(.does-not-exist),.c-realm-view sup:not(.does-not-exist){scroll-margin-top:var(--cr-space-24)}.c-readme-view .b-btn,.c-realm-view .b-btn{color:var(--s-color-text-secondary);display:inline-flex}.c-readme-view .b-btn:hover,.c-realm-view .b-btn:hover{-webkit-text-decoration:none;text-decoration:none}.c-readme-view .b-btn:first-child,.c-realm-view .b-btn:first-child{float:right;margin-top:var(--g-space-4)}.c-readme-view>.b-btn:first-child+*,.c-readme-view>:first-child:not(.b-btn),.c-realm-view>.b-btn:first-child+*,.c-realm-view>:first-child:not(.b-btn){margin-top:0}.c-readme-view{background-color:var(--s-color-bg-base);border-radius:var(--g-border-radius);margin-bottom:var(--g-space-6);padding:var(--g-space-6) var(--g-space-4) var(--g-space-4);width:100%}@media (min-width:calc(820 / 16 * 1rem)){.c-readme-view{grid-row-start:auto}}.b-gnome .hat,.b-logo .hat{fill:var(--s-logo-hat)}.b-gnome .beard,.b-logo .beard{fill:var(--s-logo-beard)}.b-banner{align-items:center;background-color:var(--s-color-bg-brand-default);color:var(--s-color-text-base);display:flex;font-size:var(--g-font-size-50);font-weight:var(--g-font-semibold);justify-content:center;padding:var(--g-space-1-5) var(--g-space-4);text-align:center;-webkit-text-decoration:none;text-decoration:none;width:100%}@media (min-width:calc(640 / 16 * 1rem)){.b-banner{font-size:var(--g-font-size-100)}}a.b-banner:hover{opacity:.9;-webkit-text-decoration:underline;text-decoration:underline}.b-header{background-color:var(--s-color-bg-base);border-bottom:var(--s-border);font-size:var(--g-font-size-100);position:sticky;top:0;z-index:var(--g-z-max)}.b-header nav{align-items:stretch;height:auto}.b-header .main-nav{align-items:stretch;display:flex;flex:1 1 auto;gap:var(--g-space-1);height:100%;min-width:0;padding-bottom:var(--g-space-2);padding-top:var(--g-space-2);width:100%}@media (min-width:calc(820 / 16 * 1rem)){.b-header .main-nav{grid-column:span 7}}.b-header .main-nav--explorer{grid-column:span 10}.b-header .user-picture{border:var(--s-border-secondary);border-radius:var(--s-rounded);cursor:pointer;flex-shrink:0;height:var(--g-space-10);width:var(--g-space-10)}.b-header .user-picture>svg{height:100%;width:100%}.b-main-navigation{color:var(--s-color-text-quaternary);height:auto;position:relative;width:100%}.b-main-navigation>.inner{align-items:center;background-color:var(--s-color-bg-surface-secondary);border:var(--s-border-secondary);border-radius:var(--s-rounded);height:100%;padding-left:var(--g-space-1-5);padding-right:var(--g-space-1-5);position:relative}@media (min-width:calc(640 / 16 * 1rem)){.b-main-navigation>.inner{padding-right:var(--g-space-8)}}.b-main-navigation>.inner:has([data-role=header-input-search]:focus-within){border-color:var(--s-color-border-tertiary)}.b-main-navigation .searchbar{bottom:0;color:var(--s-color-text-secondary);font-size:var(--g-font-size-200);font-weight:var(--g-font-medium);left:0;padding:var(--g-space-1-5);padding-right:var(--g-space-8);position:absolute;right:0;top:0}.b-main-navigation .searchbar>input{background-color:transparent;height:100%;outline:none;width:100%}.b-main-navigation .searchbar:focus-within+.b-breadcrumb{display:none}.b-main-navigation .network-toggle{align-items:center;background-color:var(--g-color-transparent);border-radius:var(--g-border-radius);cursor:pointer;display:none;height:calc(100% - 2px);justify-content:center;padding:var(--g-space-1-5);position:absolute;right:1px;top:1px;z-index:var(--g-z-max)}@media (min-width:calc(640 / 16 * 1rem)){.b-main-navigation .network-toggle{display:flex}}.b-main-navigation .network-toggle>svg{color:var(--s-color-text-tertiary);height:var(--g-space-5);width:var(--g-space-5)}.b-main-navigation .network-toggle:hover>svg{color:var(--s-color-text-tertiary-hover)}.b-main-navigation .b-popup-dialog>.inner{color:var(--s-color-text-tertiary);width:var(--g-space-72)}.b-main-navigation .b-popup-dialog header>span{color:var(--s-color-text-secondary);font-size:var(--g-font-size-100);font-weight:var(--g-font-semibold)}.b-main-navigation .b-popup-dialog .item{display:flex;gap:var(--g-space-1)}.b-main-navigation .b-popup-dialog .item>svg{height:var(--g-space-4);width:var(--g-space-4)}.b-main-navigation .b-popup-dialog .item-content{display:flex;flex-direction:column}.b-main-navigation .b-popup-dialog .item-label{font-size:var(--g-font-size-50)}.b-main-navigation .b-popup-dialog .item-value{color:var(--s-color-text-secondary);font-size:var(--g-font-size-100);font-weight:var(--g-font-semibold)}.b-main-menu{display:flex;flex:0 0 auto;grid-column:span 3;height:var(--g-space-12)}@media (min-width:calc(640 / 16 * 1rem)){.b-main-menu{height:auto}}.b-main-menu .menu-toggle{align-items:center;cursor:pointer;display:flex;margin-left:auto;order:3}.b-main-menu .menu-toggle>svg{height:var(--g-space-5);margin-left:var(--g-space-4);width:var(--g-space-5)}@media (min-width:calc(820 / 16 * 1rem)){.b-main-menu .menu-toggle>svg{margin-left:var(--g-space-2)}}.b-main-menu .menu-toggle-input~.menu-dev{display:none}.b-main-menu .menu-toggle-input:checked~.menu-dev{display:flex}.b-main-menu .menu-toggle-input:checked~.menu-general{display:none}.b-main-menu .menu-dev,.b-main-menu .menu-general{display:flex;height:100%;justify-content:flex-end}.b-menu-link:last-child,.b-menu-link:last-child .link{margin-right:0}.b-menu-link .link{align-items:center;color:var(--s-color-text-tertiary);display:flex;font-size:var(--g-font-size-100);font-weight:var(--g-font-semibold);gap:var(--g-space-1);height:100%;margin-right:var(--g-space-3);position:relative}.b-menu-link .link:hover{color:var(--s-color-text-tertiary-hover)}.b-menu-link .link:after{background-color:var(--s-color-bg-brand-default);border-radius:var(--s-rounded) var(--s-rounded) 0 0;bottom:0;content:"";height:var(--g-space-1);left:0;position:absolute;transition:width var(--g-transition-fast);width:0}.b-menu-link .link>svg{flex-shrink:0;height:var(--g-space-5);min-width:var(--g-space-2);width:var(--g-space-5)}@media (min-width:calc(1020 / 16 * 1rem)){.b-menu-link .link>svg{display:none}}@media (min-width:calc(1366 / 16 * 1rem)){.b-menu-link .link>svg{display:inline-block;height:var(--g-space-4-5);width:var(--g-space-4-5)}}@media (min-width:calc(640 / 16 * 1rem)){.b-menu-link .link{font-weight:var(--g-font-bold)}}@media (min-width:calc(1366 / 16 * 1rem)){.b-menu-link .link{margin-right:var(--g-space-6);padding-right:var(--g-space-1)}}@media (min-width:calc(640 / 16 * 1rem)){.b-menu-link .link-label{display:none}}@media (min-width:calc(1020 / 16 * 1rem)){.b-menu-link .link-label{display:inline}}.b-menu-link .link--icon{font-weight:var(--g-font-regular);margin-right:var(--g-space-4)}@media (min-width:calc(480 / 16 * 1rem)){.b-menu-link .link--icon{margin-right:var(--g-space-6)}}.b-menu-link .link--is-active{color:var(--s-color-text-secondary)}.b-menu-link .link--is-active:after{width:100%}.b-menu-link .link--is-active>svg{color:var(--s-color-bg-brand-default)}.menu-general .link{color:var(--s-color-text-secondary)}.menu-general .link:hover{color:var(--s-color-text-link-hover)}.b-breadcrumb{display:flex}.b-breadcrumb,.b-breadcrumb:after{background-color:var(--s-color-bg-surface-secondary)}.b-breadcrumb:after{bottom:0;content:"";display:block;left:0;pointer-events:none;position:absolute;right:0;top:0}.b-breadcrumb>ol{color:var(--s-color-text-primary);display:flex;font-weight:var(--g-font-semibold);line-height:var(--g-line-height-snug)}.b-breadcrumb .argument,.b-breadcrumb .element,.b-breadcrumb .query{align-items:center;display:flex;white-space:nowrap;z-index:var(--g-z-1)}.b-breadcrumb .argument:not(:first-child):before,.b-breadcrumb .element:not(:first-child):before,.b-breadcrumb .query:not(:first-child):before{color:var(--s-color-text-tertiary);content:"/";line-height:var(--g-line-height-normal);padding-left:.18rem;padding-right:.18rem;padding-top:var(--g-space-px)}.b-breadcrumb .argument a,.b-breadcrumb .element a,.b-breadcrumb .query a{background-color:var(--s-color-bg-base);border:1px solid var(--s-color-border-transparent);border-radius:var(--s-rounded-sm);display:inline-block;min-width:var(--g-space-4);padding:var(--g-space-0-5);text-align:center}.b-breadcrumb .argument a:hover,.b-breadcrumb .element a:hover,.b-breadcrumb .query a:hover{background-color:var(--s-color-bg-brand-default);color:var(--s-color-text-base)}.b-breadcrumb .argument:not(:first-child):before{content:":"}.b-breadcrumb .argument a{background-color:var(--s-color-bg-surface-quaternary);color:var(--s-color-text-base)}.b-breadcrumb .query:not(:first-child):before{content:"&"}.b-breadcrumb .query:nth-child(1 of .query):before{content:"?"}.b-breadcrumb .query label{background-color:var(--s-color-bg-surface-primary);border:var(--s-border);border-radius:var(--s-rounded-sm);color:var(--s-color-text-secondary);cursor:text;display:inline-flex;height:100%;min-width:var(--g-space-4);padding:var(--g-space-0-5) var(--g-space-1);position:relative;text-align:center;width:100%}.b-breadcrumb .query label:focus-within{border-color:var(--s-color-border-quaternary)}.b-breadcrumb .query label:hover{border-color:var(--s-color-border-quaternary)}.b-breadcrumb .query input{background-color:var(--s-color-bg-surface-primary);max-width:10ch;order:3;outline:none;field-sizing:content}@supports not (field-sizing:content){.b-breadcrumb .query input{width:5rem!important}}.b-breadcrumb .query input::-moz-placeholder{opacity:0}.b-breadcrumb .query input::placeholder{opacity:0}.b-breadcrumb .query input:-moz-placeholder{width:var(--g-space-px)}.b-breadcrumb .query input:placeholder-shown{width:var(--g-space-px)}.b-breadcrumb .query input:placeholder-shown::-moz-placeholder{color:var(--g-color-transparent)}.b-breadcrumb .query input:-moz-placeholder::placeholder{color:var(--g-color-transparent)}.b-breadcrumb .query input:placeholder-shown::placeholder{color:var(--g-color-transparent)}.b-footer{border-top:var(--s-border);font-size:var(--g-font-size-100);padding-bottom:var(--g-space-4);padding-top:var(--g-space-4);width:100%}.b-footer>nav{flex-direction:column;row-gap:var(--g-space-8)}@media (min-width:calc(640 / 16 * 1rem)){.b-footer>nav{flex-wrap:wrap}}.b-footer .logo{color:var(--s-color-text-primary);grid-column:1/-1;width:var(--g-space-44)}.b-footer .logo:hover{color:var(--s-color-text-primary);-webkit-text-decoration:none;text-decoration:none}@media (min-width:calc(1020 / 16 * 1rem)){.b-footer .logo{align-self:center;grid-column:1/3;grid-row:1/1;width:60%}}.b-footer .nav-primary{display:flex;gap:var(--g-space-10);grid-column:1/-1;grid-row:2/3}@media (min-width:calc(640 / 16 * 1rem)){.b-footer .nav-primary{align-items:center;flex:1 0 0%;flex-direction:row;gap:var(--g-space-6);justify-content:space-between}}@media (min-width:calc(1020 / 16 * 1rem)){.b-footer .nav-primary{grid-column:2/8;grid-row:1/1}}.b-footer .nav-primary>ul{display:flex;flex:1;flex-direction:column;flex-wrap:wrap;gap:var(--g-space-1) var(--g-space-3)}@media (min-width:calc(640 / 16 * 1rem)){.b-footer .nav-primary>ul{flex:initial;flex-direction:row}.b-footer .nav-social{margin-left:auto}}@media (min-width:calc(820 / 16 * 1rem)){.b-footer .nav-social{grid-column:span 3;justify-self:end;margin-left:0}}.b-footer .nav-theme{align-items:center;display:flex;gap:var(--g-space-2)}@media (min-width:calc(640 / 16 * 1rem)){.b-footer .nav-theme{flex-basis:100%}}@media (min-width:calc(820 / 16 * 1rem)){.b-footer .nav-theme{grid-column:span 3}}.b-footer .nav-theme .nav-theme-label{color:var(--s-color-text-secondary)}.b-footer .nav-theme:has([data-theme-target=sun]:not(.u-hidden)) .nav-theme-label:before{content:"Light"}.b-footer .nav-theme:has([data-theme-target=moon]:not(.u-hidden)) .nav-theme-label:before{content:"Dark"}.b-footer .nav-theme:has([data-theme-target=system]:not(.u-hidden))
.nav-theme-label:before{content:"System"}.b-footer .legal{color:var(--s-color-text-tertiary);font-size:var(--g-font-size-50);margin-top:var(--g-space-3);padding-top:var(--g-space-3)}.b-footer .legal>nav{color:var(--s-color-text-secondary);display:flex;flex-direction:column;flex-wrap:wrap;gap:var(--g-space-1) var(--g-space-3);margin-top:var(--g-space-2)}@media (min-width:calc(640 / 16 * 1rem)){.b-footer .legal>nav{flex-direction:row}.b-footer .legal>nav>a+a:before{color:var(--s-color-text-quaternary);content:"|";margin-right:var(--g-space-3)}}.b-footer .legal>nav:nth-child(3){grid-column:span 2/span 2}.b-footer .legal>:last-child:not(ul),.b-footer .legal>nav li{margin-bottom:var(--g-space-2);margin-top:var(--g-space-2)}.b-footer .legal>:last-child:not(ul){flex-basis:100%}@media (min-width:calc(1020 / 16 * 1rem)){.b-footer .legal>:last-child:not(ul){flex-basis:auto;grid-column:span 1/span 1}}.b-footer a:hover{color:var(--s-color-text-link-hover);-webkit-text-decoration:underline;text-decoration:underline}.b-content-header{display:flex;flex-direction:column;gap:var(--g-space-3);grid-row:span 1/span 1;margin-bottom:var(--g-space-6);margin-top:var(--g-space-10)}@media (min-width:calc(820 / 16 * 1rem)){.b-content-header{grid-column:span 7/span 7;grid-row-start:1;justify-content:space-between;margin-top:var(--g-space-10)}}@media (min-width:calc(1020 / 16 * 1rem)){.b-content-header{align-items:center;flex-direction:row}}.b-content-header .title{align-items:center;display:flex;gap:var(--g-space-3)}.b-content-header .header-info{align-items:center;color:var(--s-color-text-tertiary);display:flex;font-size:var(--g-font-size-100);gap:var(--g-space-12);justify-content:space-between}.b-content-header .b-inline-btn>span{display:none}@media (min-width:calc(1020 / 16 * 1rem)){.b-content-header .b-inline-btn>span{display:inline}}.b-content-h1{font-size:var(--g-font-size-600);text-align:center}.b-content-h1,.b-content-h2{color:var(--s-color-text-primary);font-weight:var(--g-font-bold)}.b-content-h2{font-size:var(--g-font-size-400);margin-bottom:var(--g-space-4)}.b-btns{align-items:center;display:flex;gap:var(--g-space-1)}@media (min-width:calc(1020 / 16 * 1rem)){.b-btns{gap:var(--g-space-2)}}.b-btn{border:var(--s-border);border-radius:var(--s-rounded-sm);cursor:pointer;display:inline-flex;gap:var(--g-space-1-5);min-width:-moz-max-content;min-width:max-content;padding:var(--g-space-1) var(--g-space-2)}.b-btn:hover{background-color:var(--s-color-bg-surface-primary-hover)}.b-btn .c-icon{margin-left:0;margin-right:0}.b-btn--secondary:hover{background-color:var(--s-color-bg-surface-primary)}.b-inline-btn{color:var(--s-color-text-tertiary);cursor:pointer}.b-inline-btn:hover{color:var(--s-color-text-tertiary-hover)}.b-switch input,.b-switch label:last-child{display:none}.b-switch input+label,.b-switch input:checked~label:last-child{display:block}.b-switch input:checked+label{display:none}.b-block-form,.b-inline-form{color:var(--s-color-text-tertiary);display:flex;flex-direction:column;gap:var(--g-space-2) var(--g-space-3)}@media (min-width:calc(820 / 16 * 1rem)){.b-block-form,.b-inline-form{flex-direction:row}}.b-block-form{align-items:stretch}@media (min-width:calc(820 / 16 * 1rem)){.b-block-form{flex-direction:column}}.b-input{border:var(--s-border);border-radius:var(--s-rounded-sm);color:var(--s-color-text-secondary);display:flex;font-size:var(--g-font-size-100);min-width:var(--g-space-48);overflow:hidden;position:relative}.b-input>svg{height:var(--g-space-4);pointer-events:none;position:absolute;top:50%;transform:translateY(-50%);width:var(--g-space-4)}.b-input>svg:first-child{left:var(--g-space-2)}.b-input>svg:last-child{right:var(--g-space-2)}.b-input:hover,.b-input>input:focus,.b-input>input:hover{border-color:var(--s-color-border-tertiary)}.b-input:has(input:focus),.b-input:hover,.b-input>input:focus,.b-input>input:hover{border-color:var(--s-color-border-tertiary)}.b-input:hover>label{background-color:var(--s-color-bg-surface-primary)}.b-input:has(input:focus)>label,.b-input:hover>label{background-color:var(--s-color-bg-surface-primary)}.b-input>label{align-items:center;background-color:var(--s-color-bg-surface-secondary);gap:var(--g-space-3);white-space:nowrap}.b-input>input,.b-input>label,.b-input>select{display:flex;padding:var(--g-space-1-5) var(--g-space-3)}.b-input>input,.b-input>select{color:inherit;outline:none;width:100%}@media (min-width:calc(820 / 16 * 1rem)){.b-input>input,.b-input>select{padding:var(--g-space-1-5) var(--g-space-2)}}.b-input>select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--s-color-bg-surface-secondary);cursor:pointer}.b-input>select:hover{background-color:var(--s-color-bg-surface-primary)}.b-input>input{background-color:var(--s-color-bg-base);border-left:none}.b-input>label+input{border-left:var(--s-border)}.b-list{margin-bottom:var(--g-space-10)}.b-list>li{border-bottom:var(--s-border);color:var(--s-color-text-tertiary)}.b-list>li:first-child{border-top:var(--s-border)}.b-list>li>a{align-items:center;display:flex;justify-content:space-between;padding:var(--g-space-2)}.b-list>li>a:hover{background-color:var(--s-color-bg-surface-primary-hover)}.b-list>li>a .c-icon{margin-left:0}.b-list .name{display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;color:var(--s-color-text-secondary);margin-left:var(--g-space-1);max-width:100%;overflow:hidden;text-overflow:ellipsis}.b-user-sidebar{margin-top:var(--g-space-4)}.b-user-sidebar>*+*{margin-top:var(--g-space-8)}.b-user-sidebar .user-avatar{border:var(--s-border);border-radius:var(--s-rounded);height:var(--g-space-24);width:var(--g-space-24)}@media (min-width:calc(640 / 16 * 1rem)){.b-user-sidebar .user-avatar{height:var(--g-space-36);width:var(--g-space-36)}}.b-user-sidebar .user-avatar img,.b-user-sidebar .user-avatar svg{height:100%;-o-object-fit:cover;object-fit:cover;width:100%}.b-user-sidebar .user-info{align-items:flex-start;display:flex;gap:var(--g-space-6)}@media (min-width:calc(820 / 16 * 1rem)){.b-user-sidebar .user-info{flex-direction:column}}.b-user-sidebar .user-info>div:last-child{align-self:flex-end}@media (min-width:calc(820 / 16 * 1rem)){.b-user-sidebar .user-info>div:last-child{align-self:flex-start}}.b-user-sidebar .title{color:var(--s-color-text-primary);display:bock;font-size:var(--g-font-size-700);font-weight:var(--g-font-bold);line-height:var(--g-line-height-tight);text-transform:capitalize;word-break:break-all}@media (min-width:calc(640 / 16 * 1rem)){.b-user-sidebar .title{font-size:var(--g-font-size-800)}}.b-user-sidebar .subtitle{color:var(--s-color-text-secondary);display:block;font-size:var(--g-font-size-100);line-height:var(--g-line-height-tight);margin-top:var(--g-space-2)}.b-user-sidebar>a{align-items:center;display:flex;justify-content:center}@media (min-width:calc(820 / 16 * 1rem)){.b-user-sidebar>a{display:inline-flex}}.b-sidebar{border-bottom:var(--s-border);grid-column:span 1/span 1;padding-bottom:var(--g-space-10);position:relative}@media (min-width:calc(820 / 16 * 1rem)){.b-sidebar{border-bottom:none;grid-column:span 3/span 3;grid-row:span 2/span 2;grid-row-start:1;height:100%;margin-bottom:0;order:2;padding-bottom:0}.b-sidebar+md-renderer:empty+*{grid-row-start:1;padding-top:var(--g-space-6)}.b-sidebar+md-renderer:empty+*,.b-sidebar+md-renderer:has(.b-btn:only-child)+*{grid-row-start:1;padding-top:var(--g-space-6)}}.b-sidebar:first-child{margin-top:var(--g-space-8)}@media (min-width:calc(820 / 16 * 1rem)){.b-sidebar:first-child{margin-top:0}}.b-sidebar>div{padding-top:var(--g-space-2);position:sticky;top:var(--g-space-14)}.b-sidebar>div:has(.inner):not(:has(nav li)){display:none}@media (min-width:calc(820 / 16 * 1rem)){.b-sidebar>div{padding-bottom:var(--g-space-2)}}.b-sidebar .inner{background-color:var(--s-color-bg-surface-primary);border-radius:var(--s-rounded-sm);max-height:100vh;overflow:scroll;scrollbar-width:none}@media (min-width:calc(820 / 16 * 1rem)){.b-sidebar .inner{background-color:var(--g-color-transparent)}}.b-sidebar .inner>nav{display:none;font-size:var(--g-font-size-100);margin-top:var(--g-space-2);padding:var(--g-space-2) var(--g-space-4) var(--g-space-6)}@media (min-width:calc(820 / 16 * 1rem)){.b-sidebar .inner>nav{display:block;margin-top:0;padding-bottom:var(--g-space-28);padding-left:0;padding-right:0}.b-sidebar .inner>nav>*{padding-left:0}}.b-sidebar .b-expend-btn{align-items:center;background-color:var(--s-color-bg-base);border:var(--s-border);border-radius:var(--s-rounded-sm);cursor:pointer;display:flex;font-size:var(--g-font-size-100);justify-content:space-between;padding:var(--g-space-2) var(--g-space-4)}.b-sidebar .b-expend-btn:hover{background-color:var(--s-color-bg-surface-secondary)}@media (min-width:calc(820 / 16 * 1rem)){.b-sidebar .b-expend-btn{border:none;cursor:default;font-size:var(--g-font-size-200);font-weight:var(--g-font-semibold);margin-top:var(--g-space-10);padding:0}.b-sidebar .b-expend-btn,.b-sidebar .b-expend-btn:hover{background-color:var(--g-color-transparent)}}.b-sidebar .b-expend-btn:has(#toc-expend:checked)+nav{display:block}.b-sidebar .b-expend-btn>input{display:none}.b-sidebar .b-expend-btn>input:checked+.wrapper-icon:before{content:"close"}.b-sidebar .b-expend-btn>input:checked+.wrapper-icon>svg{transform:rotate(180deg)}.b-sidebar .wrapper-icon{align-items:center;display:flex;gap:var(--g-space-1-5)}.b-sidebar .wrapper-icon:before{content:"open"}@media (min-width:calc(820 / 16 * 1rem)){.b-sidebar .wrapper-icon{display:none}}.dev-mode .b-sidebar .b-expend-btn{background-color:var(--s-color-bg-surface-secondary)}@media (min-width:calc(820 / 16 * 1rem)){.dev-mode .b-sidebar .b-expend-btn{background-color:var(--g-color-transparent)}}.dev-mode .b-sidebar .b-expend-btn:hover{background-color:var(--s-color-bg-surface-primary)}.b-source-code{font-family:var(--g-font-mono)}.b-source-code>pre{background-color:var(--s-color-bg-base);border-radius:var(--s-rounded);font-size:var(--g-font-size-100);overflow:scroll;padding:var(--g-space-4) var(--g-space-1)}@media (min-width:calc(640 / 16 * 1rem)){.b-source-code>pre{font-size:var(--g-font-size-200);padding:var(--g-space-8) var(--g-space-3)}}.b-source-code>pre a:hover{-webkit-text-decoration:none;text-decoration:none}[data-theme=dark] .b-source-code>pre{background-color:var(--s-color-bg-base)}.b-toc{list-style:none;margin-top:var(--g-space-2)}.b-toc>*+*{margin-bottom:var(--g-space-1-5);margin-top:var(--g-space-1-5)}.b-toc .b-toc{border-left:1px solid var(--s-color-border-secondary);margin-bottom:var(--g-space-4);padding-left:var(--g-space-4)}.b-toc a>span{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis}.b-toc a:hover{color:var(--s-color-text-link-hover);-webkit-text-decoration:underline;text-decoration:underline}main.dev-mode .b-toc a{word-break:break-all}.b-source-toc>.b-toc{margin-bottom:var(--g-space-4)}.b-source-toc>*+*{margin-top:var(--g-space-1-5)}.b-source-toc .accordion summary>svg{transform:rotate(-90deg)}.b-source-toc .accordion summary:hover{color:var(--s-color-text-link-hover);-webkit-text-decoration:underline;text-decoration:underline}.b-source-toc .accordion[open] summary>svg{transform:rotate(0deg)}.b-source-toc .accordion>.b-toc{padding-left:var(--g-space-5)}.b-source-toc .accordion h3{font-size:var(--g-font-size-100);font-weight:var(--g-font-medium);margin-top:0}.b-action-overview{margin-bottom:var(--g-space-12)}.b-action-overview>p{font-size:var(--g-font-size-200)}.b-action-function{background-color:var(--s-color-bg-surface-secondary);border-radius:var(--s-rounded);margin-bottom:var(--g-space-3);padding:var(--g-space-4)}.b-action-function .title{align-items:baseline;display:flex;flex-wrap:wrap;font-size:var(--g-font-size-50);gap:var(--g-space-1) var(--g-space-4);margin-bottom:var(--g-space-1)}.b-action-function>header{align-items:flex-start;display:flex;font-size:var(--g-font-size-100);justify-content:space-between;margin-bottom:var(--g-space-4)}.b-action-function>header .signature>code{color:var(--s--text-secondary)}@media (min-width:calc(820 / 16 * 1rem)){.b-action-function>header .signature{font-size:var(--g-font-size-50)}}.b-action-function>header h2{color:var(--s-color-text-primary);font-size:var(--g-font-size-300);font-weight:var(--g-font-semibold);line-height:var(--g-line-height-tight)}.b-action-function .description{color:var(--s-color-text-secondary);font-size:var(--g-font-size-200)}.b-action-function .params{align-items:stretch;color:var(--s-color-text-tertiary);display:flex;flex-direction:column;font-size:var(--g-font-size-100);gap:var(--g-space-1);margin-bottom:var(--g-space-1);margin-top:var(--g-space-6);width:100%}.b-action-function .params label{background-color:var(--s-color-bg-surface-primary)}.b-action-function .params .b-input:has(input:focus) label{background-color:var(--s-color-bg-surface-secondary)}.b-action-function .params .b-input:has(input:hover) label{background-color:var(--s-color-bg-surface-secondary)}.b-action-function .b-alert{background-color:var(--s-color-bg-warning-weak);border-left:var(--g-space-1) solid var(--s-color-border-tertiary);border-left-color:var(--s-color-border-warning);border-radius:var(--s-rounded);color:var(--s-color-text-secondary);color:var(--s-color-text-warning);margin-bottom:var(--g-space-10);margin-top:var(--g-space-5);padding:var(--g-space-3) var(--g-space-4)}.b-action-function .b-alert>h1:first-child,.b-action-function .b-alert>h2:first-child,.b-action-function .b-alert>h3:first-child{font-size:var(--g-font-size-200);font-weight:var(--g-font-semibold);margin-bottom:var(--g-space-2)}.b-action-function .b-alert .b-btn,.b-action-function .b-alert label{background-color:var(--s-color-bg-warning-action);border:none;color:var(--s-color-bg-warning-weak);cursor:pointer}.b-action-function .b-alert .b-btn{margin-top:var(--g-space-4)}.b-code{background-color:var(--s-color-bg-base);border-radius:var(--s-rounded);font-size:var(--g-font-size-100);position:relative}.b-code pre{color:var(--s-color-text-secondary);padding:var(--g-space-4);padding-right:var(--g-space-10);white-space:pre-wrap}.b-code .btn-copy{background-color:var(--g-color-transparent);color:var(--s-color-text-tertiary);cursor:pointer;padding:0;position:absolute;right:var(--g-space-2);top:var(--g-space-2)}.b-code .btn-copy:hover{color:var(--s-color-text-primary)}.b-packages{min-height:var(--g-space-96);padding-bottom:var(--g-space-24);scroll-margin-block-start:var(--g-space-24)}@media (min-width:calc(820 / 16 * 1rem)){.b-packages{grid-column:span 7/span 7}}.b-packages .title{color:var(--s-color-text-primary);display:block;font-size:var(--g-font-size-700);font-weight:var(--g-font-bold);margin-bottom:var(--g-space-6)}@media (min-width:calc(640 / 16 * 1rem)){.b-packages .title{font-size:var(--g-font-size-800)}}.b-packages nav{display:grid;grid-template-columns:repeat(4,1fr);grid-gap:var(--g-space-3);gap:var(--g-space-3);margin-bottom:var(--g-space-6)}@media (min-width:calc(640 / 16 * 1rem)){.b-packages nav{border-bottom:var(--s-border);padding-bottom:var(--g-space-2)}}.b-packages .packages-tabs{border-bottom:var(--s-border);color:var(--s-color-text-tertiary);display:flex;font-size:var(--g-font-size-200);font-weight:var(--g-font-semibold);gap:var(--g-space-4);grid-column:span 4/span 4;padding-bottom:var(--g-space-2);width:auto}@media (min-width:calc(640 / 16 * 1rem)){.b-packages .packages-tabs{border-bottom:none;font-size:var(--g-font-size-100);grid-column:span 2/span 2;padding-bottom:0;width:100%}}@media (min-width:calc(1020 / 16 * 1rem)){.b-packages .packages-tabs{gap:var(--g-space-6);margin-left:0;width:100%}}.b-packages .packages-tabs label{align-items:center;cursor:pointer;display:flex;gap:var(--g-space-1);position:relative}.b-packages .packages-tabs label:hover{color:var(--s-color-text-tertiary-hover)}.b-packages .packages-tabs label .b-tag--secondary{display:none}@media (min-width:calc(1020 / 16 * 1rem)){.b-packages .packages-tabs label .b-tag--secondary{display:inline}}.b-packages .packages-filters{align-items:center;color:var(--s-color-text-tertiary);display:flex;font-size:var(--g-font-size-100);gap:var(--g-space-2);grid-column:span 2/span 2}@media (min-width:calc(480 / 16 * 1rem)){.b-packages .packages-filters{grid-column:span 1/span 1}}@media (min-width:calc(640 / 16 * 1rem)){.b-packages .packages-filters{justify-content:flex-end}}.b-packages .packages-filters>div{display:grid}.b-packages .packages-filters label{align-items:center;cursor:pointer;display:flex;gap:var(--g-space-0-5);grid-column:1/1;grid-row:1/1;justify-content:space-between}.b-packages .packages-filters label:hover>*{color:var(--s-color-text-tertiary-hover)}@media (min-width:calc(640 / 16 * 1rem)){.b-packages .packages-filters label span{display:none}}@media (min-width:calc(1366 / 16 * 1rem)){.b-packages .packages-filters label span{display:inline}}.b-packages .packages-search{display:flex;font-size:var(--g-font-size-100);grid-column:span 2/span 2;position:relative}@media (min-width:calc(480 / 16 * 1rem)){.b-packages .packages-search{grid-column:span 3/span 3}}@media (min-width:calc(640 / 16 * 1rem)){.b-packages .packages-search{grid-column:span 1/span 1}}.b-packages .range{display:grid;grid-template-columns:repeat(1,minmax(0,1fr));grid-gap:var(--g-space-2);color:var(--s-color-text-tertiary);font-size:var(--g-font-size-100);gap:var(--g-space-2)}.b-packages .range:before{color:var(--s-color-text-tertiary);display:none;font-size:var(--g-font-size-200);font-weight:var(--g-font-weight-bold);grid-column:1/-1;padding-bottom:var(--g-space-2);padding-top:var(--g-space-2);text-align:center;width:100%}.b-packages .range:after{content:"Add a package to your namespace to get started";display:none;font-size:var(--g-font-size-100);grid-column:1/-1;text-align:center}.b-packages .range:empty:before{content:"No packages found";display:block}.b-packages .range:empty:after{content:"Add a package to your namespace to get started";display:block}.b-packages article{background-color:var(--s-color-bg-surface-primary);border-radius:var(--s-rounded);display:flex;flex-direction:column;gap:var(--g-space-6);padding:var(--g-space-1)}@media (min-width:calc(640 / 16 * 1rem)){.b-packages article{gap:var(--g-space-2)}}.b-packages article .article-content{background-color:var(--s-color-bg-base);border-radius:var(--s-rounded-sm);display:flex;flex-direction:column;height:100%;padding:var(--g-space-2);width:100%}.b-packages article .article-content .title{align-items:center;display:flex;gap:var(--g-space-2);margin-bottom:var(--g-space-1);overflow:hidden;width:100%}.b-packages article .article-content h3{font-size:var(--g-font-size-200);font-weight:var(--g-font-bold);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.b-packages article .article-content h3>a{color:var(--s-color-text-link-hover)}.b-packages article .article-content h3>a:hover{-webkit-text-decoration:underline;text-decoration:underline}.b-packages article .article-content>p{overflow:hidden;text-overflow:ellipsis;width:100%}.b-packages article .article-content>p>a:hover{-webkit-text-decoration:underline;text-decoration:underline}.b-packages article footer{display:flex;font-size:var(--g-font-size-50);gap:var(--g-space-1);justify-content:space-between;padding-bottom:var(--g-space-1);padding-left:var(--g-space-2);padding-right:var(--g-space-2)}.b-packages article footer time{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.b-packages article footer .size{text-align:right}.b-packages article,.b-packages li{display:none}.b-packages:has(input[value=packages]:checked) li{display:flex}.b-packages:has(input[value=packages]:checked) article{display:flex}.b-packages:has(input[value=realms]:checked)
li[data-list-type-value=realm]{display:flex}.b-packages:has(input[value=realms]:checked)
article[data-list-type-value=realm]{display:flex}.b-packages:has(input[value=pures]:checked)
li[data-list-type-value=pure]{display:flex}.b-packages:has(input[value=pures]:checked)
- article[data-list-type-value=pure]{display:flex}.b-packages label:has(input:checked){color:var(--s-color-text-tertiary)}.b-packages label:has(input:checked):after{background-color:var(--s-color-bg-brand-default);border-top-left-radius:var(--s-rounded-sm);border-top-right-radius:var(--s-rounded-sm);bottom:calc(var(--g-space-1)*-2);content:"";height:var(--g-space-1);left:0;position:absolute;width:100%}.b-packages:has(input[value=realms]:checked) .range:not(:has(>[data-list-type-value=realm])):before{display:block}.b-packages:has(input[value=realms]:checked) .range:not(:has(>[data-list-type-value=realm])):after{display:block}.b-packages:has(input[value=realms]:checked) .range:not(:has(>[data-list-type-value=realm])):before{content:"No realms found"}.b-packages:has(input[value=realms]:checked) .range:not(:has(>[data-list-type-value=realm])):after{content:"Add a realm to your namespace to get started"}.b-packages:has(input[value=pures]:checked) .range:not(:has(>[data-list-type-value=pure])):before{display:block}.b-packages:has(input[value=pures]:checked) .range:not(:has(>[data-list-type-value=pure])):after{display:block}.b-packages:has(input[value=pures]:checked) .range:not(:has(>[data-list-type-value=pure])):before{content:"No pures found"}.b-packages:has(input[value=pures]:checked) .range:not(:has(>[data-list-type-value=pure])):after{content:"Add a pure to your namespace to get started"}@media (min-width:calc(640 / 16 * 1rem)){.b-packages:has(input[value=display-grid]:checked) .range{gap:var(--g-space-3);grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:calc(1020 / 16 * 1rem)){.b-packages:has(input[value=display-grid]:checked) .range{grid-template-columns:repeat(4,minmax(0,1fr))}}.b-packages:has(input[value=display-grid]:checked) .range article .article-content p{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden}.b-packages:has(input[value=display-list]:checked) .range article .article-content{display:flex;flex:none;flex-direction:row;gap:var(--g-space-2)}@media (min-width:calc(820 / 16 * 1rem)){.b-packages:has(input[value=display-list]:checked) .range article .article-content{flex:5;min-width:0}}.b-packages:has(input[value=display-list]:checked) .range article .article-content .title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:33.33333%}.b-packages:has(input[value=display-list]:checked) .range article .article-content p{white-space:nowrap}.b-packages:has(input[value=display-list]:checked) .range article footer{display:none;justify-content:center;min-width:0;padding-bottom:0;padding-left:0}@media (min-width:calc(640 / 16 * 1rem)){.b-packages:has(input[value=display-list]:checked) .range article footer{flex:1}}@media (min-width:calc(820 / 16 * 1rem)){.b-packages:has(input[value=display-list]:checked) .range article footer{display:flex}}.b-packages:has(input[value=display-list]:checked) .range article footer .size{display:none;min-width:0}@media (min-width:calc(1020 / 16 * 1rem)){.b-packages:has(input[value=display-list]:checked) .range article footer .size{display:block;flex:2}}.b-packages:has(input[value=display-list]:checked) .range article footer time{min-width:0}@media (min-width:calc(1020 / 16 * 1rem)){.b-packages:has(input[value=display-list]:checked) .range article footer time{flex:5}}.b-icon-action{flex-shrink:0;height:var(--g-space-5);width:var(--g-space-5)}.b-popup-bg,.b-popup-dialog{opacity:0;right:0;top:0;visibility:hidden;z-index:var(--g-z-max)}.b-popup-bg{align-items:center;bottom:0;display:flex;justify-content:center;left:0;position:fixed;right:0;top:0}.b-popup-dialog{position:absolute}.b-popup-dialog>.inner{background-color:var(--s-color-bg-base);border:var(--s-border-secondary);border-radius:var(--s-rounded);padding:var(--g-space-2-5) var(--g-space-4);position:absolute;transform:translateX(-100%);z-index:var(--g-z-max)}.b-popup-dialog>.inner>*+*{margin-top:var(--g-space-2-5)}.b-popup-dialog header{align-items:center;color:var(--s-color-text-secondary);display:flex;justify-content:space-between;width:100%}.b-popup-dialog header>svg{color:var(--s-color-text-tertiary);cursor:pointer;position:absolute;right:var(--g-space-3)}.b-popup-dialog header>svg>svg:hover{color:var(--s-color-text-primary)}.b-popup:checked+.b-popup-bg,.b-popup:checked~.b-popup-dialog{opacity:1;visibility:visible}.b-tag,.b-tag--secondary{align-items:center;border:var(--s-border);border-radius:var(--s-rounded-full);color:var(--s-color-text-secondary);display:flex;font-family:var(--g-font-family-mono);font-size:var(--g-font-size-50);gap:var(--g-space-2);padding:var(--g-space-px) var(--g-space-2)}.b-tag--secondary{background-color:var(--s-color-bg-surface-primary);border-color:transparent;color:var(--s-color-text-primary)}.c-readme-view .gno-columns,.c-realm-view .gno-columns{display:flex;flex-wrap:wrap;gap:var(--g-space-10)}@media (min-width:calc(1366 / 16 * 1rem)){.c-readme-view .gno-columns,.c-realm-view .gno-columns{gap:var(--g-space-12)}}.c-readme-view .gno-columns>*,.c-realm-view .gno-columns>*{flex-basis:var(--g-space-52);flex-grow:1;flex-shrink:1}@media (min-width:calc(820 / 16 * 1rem)){.c-readme-view .gno-columns>*,.c-realm-view .gno-columns>*{flex-basis:var(--g-space-44)}}.c-readme-view .tooltip,.c-realm-view .tooltip{--tooltip-left:0;--tooltip-right:initial;align-items:center;border:var(--s-border);border-radius:var(--s-rounded-full);color:var(--s-color-text-tertiary);display:inline-flex;height:var(--g-space-4);justify-content:center;margin-bottom:var(--g-space-px);position:relative;width:var(--g-space-4)}.c-readme-view .tooltip>svg,.c-realm-view .tooltip>svg{height:var(--g-space-3);width:var(--g-space-3)}.c-readme-view .tooltip:after,.c-realm-view .tooltip:after{background-color:var(--s-color-bg-base);border:var(--s-border-secondary);border-radius:var(--s-rounded);color:var(--s-color-text-secondary);content:attr(data-tooltip);font-size:var(--g-font-size-100);font-weight:var(--g-font-normal);left:var(--tooltip-left);max-width:var(--g-space-48);min-width:var(--g-space-32);opacity:0;padding:var(--g-space-1) var(--g-space-2);position:absolute;right:var(--tooltip-right);scale:0;text-align:center;top:100%;visibility:hidden;width:-moz-fit-content;width:fit-content;z-index:var(--g-z-max)}.c-readme-view .tooltip:hover:after,.c-realm-view .tooltip:hover:after{opacity:1;scale:1;transition-delay:var(--g-transition-fast);visibility:visible}.c-readme-view .tooltip:only-of-type,.c-realm-view .tooltip:only-of-type{margin-left:.3em;margin-right:.3em}.c-realm-view .tooltip:has(+span){margin-left:.3em}.c-readme-view .tooltip:has(+span){margin-left:.3em}.c-readme-view .link-external,.c-realm-view .link-external{font-size:.67em}.c-readme-view .link-internal,.c-realm-view .link-internal{font-size:.75em;font-weight:400}.c-readme-view .link-tx,.c-readme-view .link-user,.c-realm-view .link-tx,.c-realm-view .link-user{font-size:.75em}.c-realm-view ul:has(li>input[type=checkbox]:first-child){list-style:none;padding-left:0}.c-readme-view ul:has(li>input[type=checkbox]:first-child){list-style:none;padding-left:0}.c-realm-view li:has(>input[type=checkbox]:first-child){align-items:center;display:flex;gap:var(--g-space-2)}.c-readme-view li:has(>input[type=checkbox]:first-child){align-items:center;display:flex;gap:var(--g-space-2)}.c-readme-view li>input[type=checkbox]:first-child,.c-realm-view li>input[type=checkbox]:first-child{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:var(--s-border-secondary);border-radius:var(--s-rounded-sm);flex-shrink:0;height:var(--g-space-5);padding:0;position:relative;width:var(--g-space-5)}.c-readme-view li>input[type=checkbox]:first-child:disabled,.c-realm-view li>input[type=checkbox]:first-child:disabled{background-color:var(--s-color-bg-surface-primary);border-color:var(--s-color-border-tertiary);cursor:not-allowed}.c-readme-view li>input[type=checkbox]:first-child:disabled:after,.c-realm-view li>input[type=checkbox]:first-child:disabled:after{background-color:var(--s-color-bg-brand-default)}.c-readme-view li>input[type=checkbox]:first-child:checked:after,.c-realm-view li>input[type=checkbox]:first-child:checked:after{opacity:1}.c-readme-view li>input[type=checkbox]:first-child:after,.c-realm-view li>input[type=checkbox]:first-child:after{background-color:var(--s-color-bg-base);clip-path:polygon(25% 36%,43% 54%,76% 18%,89% 29%,44% 78%,13% 49%);content:"";height:var(--g-space-3);left:50%;margin:auto;opacity:0;position:absolute;top:50%;transform:translate(-50%,-50%);width:var(--g-space-3)}.c-readme-view .footnote-backref,.c-realm-view .footnote-backref{vertical-align:middle}.c-readme-view li[id^="fn:"],.c-realm-view li[id^="fn:"]{position:relative;z-index:var(--g-z-1)}.c-readme-view li[id^="fn:"]:before,.c-realm-view li[id^="fn:"]:before{background-color:var(--s-color-bg-brand-weak);border-radius:var(--s-rounded-sm);bottom:0;content:"";display:block;left:calc(var(--g-space-0-5)*-1);opacity:0;position:absolute;right:calc(var(--g-space-0-5)*-1);top:calc(var(--g-space-0-5)*-1);z-index:var(--g-z-min)}.c-readme-view li[id^="fn:"]:target:before,.c-realm-view li[id^="fn:"]:target:before{opacity:1;transition-delay:var(--g-duration-150);transition-duration:var(--g-duration-75)}.c-readme-view .gno-form,.c-realm-view .gno-form{border:var(--s-border-secondary);border-radius:var(--s-rounded);display:block;margin-bottom:var(--g-space-6);margin-top:var(--g-space-6)}.c-readme-view .gno-form input[type=submit],.c-realm-view .gno-form input[type=submit]{background-color:var(--s-color-bg-brand-default);border-color:var(--s-color-border-brand-default);border-radius:var(--s-rounded-sm);color:var(--s-color-text-base);cursor:pointer;margin-bottom:var(--g-space-2);margin-top:var(--g-space-4);width:100%}.c-readme-view .gno-form input[type=submit]:hover,.c-realm-view .gno-form input[type=submit]:hover{opacity:.9}.c-readme-view .gno-form .command,.c-realm-view .gno-form .command{background-color:var(--s-color-bg-base-dev);margin-top:var(--g-space-6);padding:var(--g-space-4)}.c-readme-view .gno-form .command .title,.c-realm-view .gno-form .command .title{font-size:var(--g-font-size-200);font-weight:var(--g-font-semibold);white-space:nowrap}.c-readme-view .gno-form .command>.b-code,.c-realm-view .gno-form .command>.b-code{background-color:var(--s-color-bg-base-dev)}.c-readme-view .gno-form .command>.b-code>pre,.c-realm-view .gno-form .command>.b-code>pre{background-color:var(--s-color-bg-base);margin-bottom:0}.c-readme-view .gno-form .command .c-between,.c-readme-view .gno-form .command .c-inline,.c-realm-view .gno-form .command .c-between,.c-realm-view .gno-form .command .c-inline{align-items:flex-start;flex-direction:column;gap:var(--g-space-2)}@media (min-width:calc(640 / 16 * 1rem)){.c-readme-view .gno-form .command .c-between,.c-readme-view .gno-form .command .c-inline,.c-realm-view .gno-form .command .c-between,.c-realm-view .gno-form .command .c-inline{align-items:center;flex-direction:row}}.c-readme-view .gno-form .command .c-between>*,.c-readme-view .gno-form .command .c-inline>*,.c-realm-view .gno-form .command .c-between>*,.c-realm-view .gno-form .command .c-inline>*{width:100%}@media (min-width:calc(640 / 16 * 1rem)){.c-readme-view .gno-form .command .c-between>*,.c-readme-view .gno-form .command .c-inline>*,.c-realm-view .gno-form .command .c-between>*,.c-realm-view .gno-form .command .c-inline>*{width:auto}}.c-readme-view .gno-form_header,.c-realm-view .gno-form_header{color:var(--s-color-text-tertiary);display:flex;font-size:var(--g-font-size-50);justify-content:space-between;margin-bottom:var(--g-space-6);padding:var(--g-space-2) var(--g-space-4) 0}.c-readme-view .gno-form_input,.c-readme-view .gno-form_select,.c-realm-view .gno-form_input,.c-realm-view .gno-form_select{padding-left:var(--g-space-4);padding-right:var(--g-space-4);position:relative}.c-readme-view .gno-form_input label,.c-readme-view .gno-form_select label,.c-realm-view .gno-form_input label,.c-realm-view .gno-form_select label{background-color:var(--s-color-bg-input);color:var(--s-color-text-tertiary);display:none;font-size:var(--g-font-size-50);left:var(--g-space-5);padding-left:var(--g-space-1);padding-right:var(--g-space-1);position:absolute;top:0;transform:translateY(-50%)}.c-readme-view .gno-form_input svg,.c-readme-view .gno-form_select svg,.c-realm-view .gno-form_input svg,.c-realm-view .gno-form_select svg{height:var(--g-space-4);pointer-events:none;position:absolute;right:var(--g-space-6);top:50%;transform:translateY(-50%);width:var(--g-space-4)}.c-realm-view .gno-form_input:has(input:focus) label{display:block}.c-readme-view .gno-form_input:has(input:focus) label{display:block}.c-realm-view .gno-form_input:has(input:not(:-moz-placeholder)) label{display:block}.c-realm-view .gno-form_input:has(input:not(:placeholder-shown)) label{display:block}.c-readme-view .gno-form_input:has(input:not(:-moz-placeholder)) label{display:block}.c-readme-view .gno-form_input:has(input:not(:placeholder-shown)) label{display:block}.c-realm-view .gno-form_input:has(textarea:not(:-moz-placeholder)) label{display:block}.c-realm-view .gno-form_input:has(textarea:not(:placeholder-shown)) label{display:block}.c-readme-view .gno-form_input:has(textarea:not(:-moz-placeholder)) label{display:block}.c-readme-view .gno-form_input:has(textarea:not(:placeholder-shown)) label{display:block}.c-realm-view .gno-form_input:has(textarea:focus) label{display:block}.c-readme-view .gno-form_input:has(textarea:focus) label{display:block}.c-realm-view .gno-form_select:has(select:focus) label{display:block}.c-readme-view .gno-form_select:has(select:focus) label{display:block}.c-realm-view .gno-form_select:has(select option:not([value=""]):checked) label{display:block}.c-readme-view .gno-form_select:has(select option:not([value=""]):checked) label{display:block}.c-readme-view .gno-form_input input,.c-readme-view .gno-form_input textarea,.c-readme-view .gno-form_select select,.c-realm-view .gno-form_input input,.c-realm-view .gno-form_input textarea,.c-realm-view .gno-form_select select{background-color:var(--s-color-bg-input);border:var(--g-space-px) solid var(--s-color-border-input);border-radius:var(--s-rounded-sm);color:var(--s-color-text-primary);display:block;margin-bottom:var(--g-space-4);margin-top:var(--g-space-4);outline:none;padding:var(--g-space-2);width:100%}.c-readme-view .gno-form_input input:focus,.c-readme-view .gno-form_input input:hover,.c-readme-view .gno-form_input textarea:focus,.c-readme-view .gno-form_input textarea:hover,.c-readme-view .gno-form_select select:focus,.c-readme-view .gno-form_select select:hover,.c-realm-view .gno-form_input input:focus,.c-realm-view .gno-form_input input:hover,.c-realm-view .gno-form_input textarea:focus,.c-realm-view .gno-form_input textarea:hover,.c-realm-view .gno-form_select select:focus,.c-realm-view .gno-form_select select:hover{border-color:var(--s-color-border-tertiary)}.c-realm-view .gno-form_input input::-moz-placeholder{color:var(--s-color-text-tertiary)}.c-realm-view .gno-form_input input::placeholder{color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_input input::-moz-placeholder{color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_input input::placeholder{color:var(--s-color-text-tertiary)}.c-realm-view .gno-form_input textarea::-moz-placeholder{color:var(--s-color-text-tertiary)}.c-realm-view .gno-form_input textarea::placeholder{color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_input textarea::-moz-placeholder{color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_input textarea::placeholder{color:var(--s-color-text-tertiary)}.c-realm-view .gno-form_select select::-moz-placeholder{color:var(--s-color-text-tertiary)}.c-realm-view .gno-form_select select::placeholder{color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_select select::-moz-placeholder{color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_select select::placeholder{color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_input input:disabled,.c-readme-view .gno-form_input input[readonly],.c-readme-view .gno-form_input textarea:disabled,.c-readme-view .gno-form_input textarea[readonly],.c-readme-view .gno-form_select select:disabled,.c-readme-view .gno-form_select select[readonly],.c-realm-view .gno-form_input input:disabled,.c-realm-view .gno-form_input input[readonly],.c-realm-view .gno-form_input textarea:disabled,.c-realm-view .gno-form_input textarea[readonly],.c-realm-view .gno-form_select select:disabled,.c-realm-view .gno-form_select select[readonly]{background-color:var(--s-color-bg-secondary);color:var(--s-color-text-tertiary);cursor:not-allowed}.c-readme-view .gno-form_input input:disabled:focus,.c-readme-view .gno-form_input input:disabled:hover,.c-readme-view .gno-form_input input[readonly]:focus,.c-readme-view .gno-form_input input[readonly]:hover,.c-readme-view .gno-form_input textarea:disabled:focus,.c-readme-view .gno-form_input textarea:disabled:hover,.c-readme-view .gno-form_input textarea[readonly]:focus,.c-readme-view .gno-form_input textarea[readonly]:hover,.c-readme-view .gno-form_select select:disabled:focus,.c-readme-view .gno-form_select select:disabled:hover,.c-readme-view .gno-form_select select[readonly]:focus,.c-readme-view .gno-form_select select[readonly]:hover,.c-realm-view .gno-form_input input:disabled:focus,.c-realm-view .gno-form_input input:disabled:hover,.c-realm-view .gno-form_input input[readonly]:focus,.c-realm-view .gno-form_input input[readonly]:hover,.c-realm-view .gno-form_input textarea:disabled:focus,.c-realm-view .gno-form_input textarea:disabled:hover,.c-realm-view .gno-form_input textarea[readonly]:focus,.c-realm-view .gno-form_input textarea[readonly]:hover,.c-realm-view .gno-form_select select:disabled:focus,.c-realm-view .gno-form_select select:disabled:hover,.c-realm-view .gno-form_select select[readonly]:focus,.c-realm-view .gno-form_select select[readonly]:hover{border-color:var(--s-color-border-secondary)}.c-realm-view .gno-form_input input:user-invalid{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input input:user-invalid{border-color:var(--s-color-border-error)}.c-realm-view .gno-form_input textarea:user-invalid{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input textarea:user-invalid{border-color:var(--s-color-border-error)}.c-realm-view .gno-form_select select:user-invalid{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_select select:user-invalid{border-color:var(--s-color-border-error)}.c-realm-view .gno-form_input input:user-invalid:focus{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input input:user-invalid:focus{border-color:var(--s-color-border-error)}.c-realm-view .gno-form_input textarea:user-invalid:focus{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input textarea:user-invalid:focus{border-color:var(--s-color-border-error)}.c-realm-view .gno-form_select select:user-invalid:focus{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_select select:user-invalid:focus{border-color:var(--s-color-border-error)}@supports not selector(:user-invalid){.c-realm-view .gno-form_input input:invalid:not(:-moz-placeholder):not(:focus){border-color:var(--s-color-border-error)}.c-realm-view .gno-form_input input:invalid:not(:placeholder-shown):not(:focus){border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input input:invalid:not(:-moz-placeholder):not(:focus){border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input input:invalid:not(:placeholder-shown):not(:focus){border-color:var(--s-color-border-error)}.c-realm-view .gno-form_input textarea:invalid:not(:-moz-placeholder):not(:focus){border-color:var(--s-color-border-error)}.c-realm-view .gno-form_input textarea:invalid:not(:placeholder-shown):not(:focus){border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input textarea:invalid:not(:-moz-placeholder):not(:focus){border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input textarea:invalid:not(:placeholder-shown):not(:focus){border-color:var(--s-color-border-error)}.c-realm-view .gno-form_select select:invalid:not(:-moz-placeholder):not(:focus){border-color:var(--s-color-border-error)}.c-realm-view .gno-form_select select:invalid:not(:placeholder-shown):not(:focus){border-color:var(--s-color-border-error)}.c-readme-view .gno-form_select select:invalid:not(:-moz-placeholder):not(:focus){border-color:var(--s-color-border-error)}.c-readme-view .gno-form_select select:invalid:not(:placeholder-shown):not(:focus){border-color:var(--s-color-border-error)}}.c-realm-view .gno-form_input input:focus::-moz-placeholder{opacity:0}.c-realm-view .gno-form_input input:focus::placeholder{opacity:0}.c-readme-view .gno-form_input input:focus::-moz-placeholder{opacity:0}.c-readme-view .gno-form_input input:focus::placeholder{opacity:0}.c-realm-view .gno-form_input textarea:focus::-moz-placeholder{opacity:0}.c-realm-view .gno-form_input textarea:focus::placeholder{opacity:0}.c-readme-view .gno-form_input textarea:focus::-moz-placeholder{opacity:0}.c-readme-view .gno-form_input textarea:focus::placeholder{opacity:0}.c-readme-view .gno-form textarea,.c-realm-view .gno-form textarea{resize:none}.c-realm-view .gno-form_select select:has(option[value=""]:checked){color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_select select:has(option[value=""]:checked){color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_description,.c-realm-view .gno-form_description{color:var(--s-color-text-secondary);font-weight:var(--g-font-semibold);margin-bottom:var(--g-space-2);margin-top:var(--g-space-5);padding-left:var(--g-space-4)}.c-readme-view .gno-form_info-badge,.c-realm-view .gno-form_info-badge{color:var(--s-color-text-tertiary);font-size:var(--g-font-size-50);font-weight:var(--g-font-normal);margin-left:var(--g-space-1)}.c-readme-view .gno-form_selectable,.c-realm-view .gno-form_selectable{-moz-column-gap:var(--g-space-2);column-gap:var(--g-space-2);display:flex;margin-bottom:var(--g-space-1);padding-left:var(--g-space-4);padding-right:var(--g-space-4)}.c-readme-view .gno-form_selectable input,.c-realm-view .gno-form_selectable input{width:auto}.c-readme-view .gno-form_selectable input[type=checkbox],.c-readme-view .gno-form_selectable input[type=radio],.c-realm-view .gno-form_selectable input[type=checkbox],.c-realm-view .gno-form_selectable input[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:var(--s-border);border-radius:var(--s-rounded-full);flex-shrink:0;height:var(--g-space-5);padding:0;position:relative;width:var(--g-space-5)}.c-readme-view .gno-form_selectable input[type=checkbox]:checked,.c-readme-view .gno-form_selectable input[type=radio]:checked,.c-realm-view .gno-form_selectable input[type=checkbox]:checked,.c-realm-view .gno-form_selectable input[type=radio]:checked{background-color:var(--s-color-bg-brand-default);border-color:transparent}.c-readme-view .gno-form_selectable input[type=checkbox]:checked:after,.c-readme-view .gno-form_selectable input[type=radio]:checked:after,.c-realm-view .gno-form_selectable input[type=checkbox]:checked:after,.c-realm-view .gno-form_selectable input[type=radio]:checked:after{opacity:1}.c-readme-view .gno-form_selectable input[type=checkbox]:focus,.c-readme-view .gno-form_selectable input[type=radio]:focus,.c-realm-view .gno-form_selectable input[type=checkbox]:focus,.c-realm-view .gno-form_selectable input[type=radio]:focus{outline:none}.c-readme-view .gno-form_selectable input[type=checkbox]:focus,.c-readme-view .gno-form_selectable input[type=checkbox]:hover,.c-readme-view .gno-form_selectable input[type=radio]:focus,.c-readme-view .gno-form_selectable input[type=radio]:hover,.c-realm-view .gno-form_selectable input[type=checkbox]:focus,.c-realm-view .gno-form_selectable input[type=checkbox]:hover,.c-realm-view .gno-form_selectable input[type=radio]:focus,.c-realm-view .gno-form_selectable input[type=radio]:hover{border-color:var(--s-color-border-tertiary);color:var(--s-color-text-brand-default)}.c-readme-view .gno-form_selectable input[type=checkbox]:focus+label,.c-readme-view .gno-form_selectable input[type=checkbox]:hover+label,.c-readme-view .gno-form_selectable input[type=radio]:focus+label,.c-readme-view .gno-form_selectable input[type=radio]:hover+label,.c-realm-view .gno-form_selectable input[type=checkbox]:focus+label,.c-realm-view .gno-form_selectable input[type=checkbox]:hover+label,.c-realm-view .gno-form_selectable input[type=radio]:focus+label,.c-realm-view .gno-form_selectable input[type=radio]:hover+label{color:var(--s-color-text-brand-default);-webkit-text-decoration:underline;text-decoration:underline}.c-readme-view .gno-form_selectable input[type=checkbox]:disabled,.c-readme-view .gno-form_selectable input[type=radio]:disabled,.c-realm-view .gno-form_selectable input[type=checkbox]:disabled,.c-realm-view .gno-form_selectable input[type=radio]:disabled{cursor:not-allowed;opacity:var(--g-opacity-50);-webkit-text-decoration:line-through;text-decoration:line-through}.c-readme-view .gno-form_selectable input[type=checkbox]:disabled+label,.c-readme-view .gno-form_selectable input[type=radio]:disabled+label,.c-realm-view .gno-form_selectable input[type=checkbox]:disabled+label,.c-realm-view .gno-form_selectable input[type=radio]:disabled+label{cursor:not-allowed;opacity:var(--g-opacity-50);-webkit-text-decoration:none;text-decoration:none}.c-realm-view .gno-form_selectable input[type=radio]:user-invalid{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_selectable input[type=radio]:user-invalid{border-color:var(--s-color-border-error)}.c-realm-view .gno-form_selectable input[type=checkbox]:user-invalid{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_selectable input[type=checkbox]:user-invalid{border-color:var(--s-color-border-error)}@supports not selector(:user-invalid){.c-realm-view .gno-form_selectable input[type=radio]:invalid{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_selectable input[type=radio]:invalid{border-color:var(--s-color-border-error)}.c-realm-view .gno-form_selectable input[type=checkbox]:invalid{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_selectable input[type=checkbox]:invalid{border-color:var(--s-color-border-error)}}.c-readme-view .gno-form_selectable input[type=radio]:after,.c-realm-view .gno-form_selectable input[type=radio]:after{background-color:var(--s-color-bg-brand-default);border:var(--s-border-secondary);border-radius:var(--s-rounded-full);border-width:calc(var(--g-space-px)*2);content:"";height:var(--g-space-4);left:50%;opacity:0;position:absolute;top:50%;transform:translate(-50%,-50%);width:var(--g-space-4)}.c-readme-view .gno-form_selectable input[type=checkbox],.c-realm-view .gno-form_selectable input[type=checkbox]{border-radius:var(--s-rounded-sm)}.c-readme-view .gno-form_selectable input[type=checkbox]:after,.c-realm-view .gno-form_selectable input[type=checkbox]:after{background-color:var(--s-color-bg-base);clip-path:polygon(25% 36%,43% 54%,76% 18%,89% 29%,44% 78%,13% 49%);content:"";height:var(--g-space-3);left:50%;margin:auto;opacity:0;position:absolute;top:50%;transform:translate(-50%,-50%);width:var(--g-space-3)}.c-readme-view .gno-form_selectable label,.c-realm-view .gno-form_selectable label{color:var(--s-color-text-secondary);cursor:pointer;display:block;position:relative}.c-readme-view .gno-form_selectable label:first-letter,.c-realm-view .gno-form_selectable label:first-letter{text-transform:capitalize}.c-readme-view .gno-alert,.c-realm-view .gno-alert{border-left:var(--g-space-1) solid var(--s-color-border-tertiary);border-radius:var(--s-rounded);color:var(--s-color-text-secondary);margin-bottom:var(--g-space-10);margin-top:var(--g-space-5);padding:var(--g-space-3) var(--g-space-4)}.c-readme-view .gno-alert>div>:first-child,.c-realm-view .gno-alert>div>:first-child{margin-top:var(--g-space-2)}.c-readme-view .gno-alert>div>:last-child,.c-realm-view .gno-alert>div>:last-child{margin-bottom:0}.c-readme-view .gno-alert>summary,.c-realm-view .gno-alert>summary{align-items:center;display:flex;font-weight:var(--g-font-bold);gap:var(--g-space-2);list-style:none;margin-bottom:var(--g-space-1);margin-top:var(--g-space-1)}.c-readme-view .gno-alert>summary svg,.c-realm-view .gno-alert>summary svg{height:var(--g-space-6);width:var(--g-space-6)}.c-readme-view .gno-alert>summary svg:last-of-type,.c-realm-view .gno-alert>summary svg:last-of-type{height:var(--g-space-4);margin-left:auto;transform:rotate(-90deg);width:var(--g-space-4)}.c-readme-view .gno-alert>summary a,.c-readme-view .gno-alert>summary svg,.c-realm-view .gno-alert>summary a,.c-realm-view .gno-alert>summary svg{color:inherit}.c-realm-view .gno-alert>summary::marker{display:none}.c-readme-view .gno-alert>summary::marker{display:none}.c-readme-view .gno-alert>summary::-webkit-details-marker,.c-realm-view .gno-alert>summary::-webkit-details-marker{display:none}.c-readme-view .gno-alert[open]>summary svg:last-of-type,.c-realm-view .gno-alert[open]>summary svg:last-of-type{transform:rotate(0)}.c-readme-view .gno-alert.gno-alert-info,.c-realm-view .gno-alert.gno-alert-info{background-color:color-mix(in srgb,var(--s-color-bg-info-default) 10%,transparent);border-left-color:var(--s-color-border-info);color:var(--s-color-text-info)}.c-readme-view .gno-alert.gno-alert-note,.c-realm-view .gno-alert.gno-alert-note{background-color:color-mix(in srgb,var(--s-color-bg-note-default) 10%,transparent);border-left-color:var(--s-color-border-note);color:var(--s-color-text-note)}.c-readme-view .gno-alert.gno-alert-success,.c-realm-view .gno-alert.gno-alert-success{background-color:color-mix(in srgb,var(--s-color-bg-success-default) 10%,transparent);border-left-color:var(--s-color-border-success);color:var(--s-color-text-success)}.c-readme-view .gno-alert.gno-alert-warning,.c-realm-view .gno-alert.gno-alert-warning{background-color:color-mix(in srgb,var(--s-color-bg-warning-default) 10%,transparent);border-left-color:var(--s-color-border-warning);color:var(--s-color-text-warning)}.c-readme-view .gno-alert.gno-alert-caution,.c-realm-view .gno-alert.gno-alert-caution{background-color:color-mix(in srgb,var(--s-color-bg-caution-default) 10%,transparent);border-left-color:var(--s-color-border-error);color:var(--s-color-text-caution)}.c-readme-view .gno-alert.gno-alert-tip,.c-realm-view .gno-alert.gno-alert-tip{background-color:color-mix(in srgb,var(--s-color-bg-tip-default) 10%,transparent);border-left-color:var(--s-color-border-tip);color:var(--s-color-text-tip)}.u-hidden{display:none}.u-inline{display:inline}.u-sr-only{height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;clip:rect(0,0,0,0);border-width:0;white-space:nowrap}.u-color-valid{color:var(--s-color-bg-brand-default)}.u-color-danger{color:var(--s-color-text-caution)}.u-text-stroke{-webkit-text-stroke:currentColor;-webkit-text-stroke-width:.6px}.u-font-mono{font-family:var(--g-font-family-mono)}.u-capitalize{text-transform:capitalize}.u-text-center{text-align:center}.u-no-scrollbar::-webkit-scrollbar{display:none}.u-no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.u-is-loading{opacity:0}.u-icon-static{height:var(--g-space-4);width:var(--g-space-4)}.u-gap-0{gap:0}.u-mt-4{margin-top:var(--g-space-4)}.u-mb-0{margin-bottom:0}.u-mb-2{margin-bottom:var(--g-space-2)}@media (min-width:calc(820 / 16 * 1rem)){.u-lg-mb-4{margin-bottom:var(--g-space-4)}}.u-grid-full{grid-column:1/-1}
\ No newline at end of file
+ article[data-list-type-value=pure]{display:flex}.b-packages label:has(input:checked){color:var(--s-color-text-tertiary)}.b-packages label:has(input:checked):after{background-color:var(--s-color-bg-brand-default);border-top-left-radius:var(--s-rounded-sm);border-top-right-radius:var(--s-rounded-sm);bottom:calc(var(--g-space-1)*-2);content:"";height:var(--g-space-1);left:0;position:absolute;width:100%}.b-packages:has(input[value=realms]:checked) .range:not(:has(>[data-list-type-value=realm])):before{display:block}.b-packages:has(input[value=realms]:checked) .range:not(:has(>[data-list-type-value=realm])):after{display:block}.b-packages:has(input[value=realms]:checked) .range:not(:has(>[data-list-type-value=realm])):before{content:"No realms found"}.b-packages:has(input[value=realms]:checked) .range:not(:has(>[data-list-type-value=realm])):after{content:"Add a realm to your namespace to get started"}.b-packages:has(input[value=pures]:checked) .range:not(:has(>[data-list-type-value=pure])):before{display:block}.b-packages:has(input[value=pures]:checked) .range:not(:has(>[data-list-type-value=pure])):after{display:block}.b-packages:has(input[value=pures]:checked) .range:not(:has(>[data-list-type-value=pure])):before{content:"No pures found"}.b-packages:has(input[value=pures]:checked) .range:not(:has(>[data-list-type-value=pure])):after{content:"Add a pure to your namespace to get started"}@media (min-width:calc(640 / 16 * 1rem)){.b-packages:has(input[value=display-grid]:checked) .range{gap:var(--g-space-3);grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:calc(1020 / 16 * 1rem)){.b-packages:has(input[value=display-grid]:checked) .range{grid-template-columns:repeat(4,minmax(0,1fr))}}.b-packages:has(input[value=display-grid]:checked) .range article .article-content p{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden}.b-packages:has(input[value=display-list]:checked) .range article .article-content{display:flex;flex:none;flex-direction:row;gap:var(--g-space-2)}@media (min-width:calc(820 / 16 * 1rem)){.b-packages:has(input[value=display-list]:checked) .range article .article-content{flex:5;min-width:0}}.b-packages:has(input[value=display-list]:checked) .range article .article-content .title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:33.33333%}.b-packages:has(input[value=display-list]:checked) .range article .article-content p{white-space:nowrap}.b-packages:has(input[value=display-list]:checked) .range article footer{display:none;justify-content:center;min-width:0;padding-bottom:0;padding-left:0}@media (min-width:calc(640 / 16 * 1rem)){.b-packages:has(input[value=display-list]:checked) .range article footer{flex:1}}@media (min-width:calc(820 / 16 * 1rem)){.b-packages:has(input[value=display-list]:checked) .range article footer{display:flex}}.b-packages:has(input[value=display-list]:checked) .range article footer .size{display:none;min-width:0}@media (min-width:calc(1020 / 16 * 1rem)){.b-packages:has(input[value=display-list]:checked) .range article footer .size{display:block;flex:2}}.b-packages:has(input[value=display-list]:checked) .range article footer time{min-width:0}@media (min-width:calc(1020 / 16 * 1rem)){.b-packages:has(input[value=display-list]:checked) .range article footer time{flex:5}}.b-icon-action{flex-shrink:0;height:var(--g-space-5);width:var(--g-space-5)}.b-popup-bg,.b-popup-dialog{opacity:0;right:0;top:0;visibility:hidden;z-index:var(--g-z-max)}.b-popup-bg{align-items:center;bottom:0;display:flex;justify-content:center;left:0;position:fixed;right:0;top:0}.b-popup-dialog{position:absolute}.b-popup-dialog>.inner{background-color:var(--s-color-bg-base);border:var(--s-border-secondary);border-radius:var(--s-rounded);padding:var(--g-space-2-5) var(--g-space-4);position:absolute;transform:translateX(-100%);z-index:var(--g-z-max)}.b-popup-dialog>.inner>*+*{margin-top:var(--g-space-2-5)}.b-popup-dialog header{align-items:center;color:var(--s-color-text-secondary);display:flex;justify-content:space-between;width:100%}.b-popup-dialog header>svg{color:var(--s-color-text-tertiary);cursor:pointer;position:absolute;right:var(--g-space-3)}.b-popup-dialog header>svg>svg:hover{color:var(--s-color-text-primary)}.b-popup:checked+.b-popup-bg,.b-popup:checked~.b-popup-dialog{opacity:1;visibility:visible}.b-tag,.b-tag--secondary{align-items:center;border:var(--s-border);border-radius:var(--s-rounded-full);color:var(--s-color-text-secondary);display:flex;font-family:var(--g-font-family-mono);font-size:var(--g-font-size-50);gap:var(--g-space-2);padding:var(--g-space-px) var(--g-space-2)}.b-tag--secondary{background-color:var(--s-color-bg-surface-primary);border-color:transparent;color:var(--s-color-text-primary)}.c-readme-view .gno-columns,.c-realm-view .gno-columns{display:flex;flex-wrap:wrap;gap:var(--g-space-10)}@media (min-width:calc(1366 / 16 * 1rem)){.c-readme-view .gno-columns,.c-realm-view .gno-columns{gap:var(--g-space-12)}}.c-readme-view .gno-columns>*,.c-realm-view .gno-columns>*{flex-basis:var(--g-space-52);flex-grow:1;flex-shrink:1}@media (min-width:calc(820 / 16 * 1rem)){.c-readme-view .gno-columns>*,.c-realm-view .gno-columns>*{flex-basis:var(--g-space-44)}}.c-readme-view .tooltip,.c-realm-view .tooltip{--tooltip-left:0;--tooltip-right:initial;align-items:center;border:var(--s-border);border-radius:var(--s-rounded-full);color:var(--s-color-text-tertiary);display:inline-flex;height:var(--g-space-4);justify-content:center;margin-bottom:var(--g-space-px);position:relative;width:var(--g-space-4)}.c-readme-view .tooltip>svg,.c-realm-view .tooltip>svg{height:var(--g-space-3);width:var(--g-space-3)}.c-readme-view .tooltip:after,.c-realm-view .tooltip:after{background-color:var(--s-color-bg-base);border:var(--s-border-secondary);border-radius:var(--s-rounded);color:var(--s-color-text-secondary);content:attr(data-tooltip);font-size:var(--g-font-size-100);font-weight:var(--g-font-normal);left:var(--tooltip-left);max-width:var(--g-space-48);min-width:var(--g-space-32);opacity:0;padding:var(--g-space-1) var(--g-space-2);position:absolute;right:var(--tooltip-right);scale:0;text-align:center;top:100%;visibility:hidden;width:-moz-fit-content;width:fit-content;z-index:var(--g-z-max)}.c-readme-view .tooltip:hover:after,.c-realm-view .tooltip:hover:after{opacity:1;scale:1;transition-delay:var(--g-transition-fast);visibility:visible}.c-readme-view .tooltip:only-of-type,.c-realm-view .tooltip:only-of-type{margin-left:.3em;margin-right:.3em}.c-realm-view .tooltip:has(+span){margin-left:.3em}.c-readme-view .tooltip:has(+span){margin-left:.3em}.c-readme-view .link-external,.c-realm-view .link-external{font-size:.67em}.c-readme-view .link-internal,.c-realm-view .link-internal{font-size:.75em;font-weight:400}.c-readme-view .link-tx,.c-readme-view .link-user,.c-realm-view .link-tx,.c-realm-view .link-user{font-size:.75em}.c-realm-view ul:has(li>input[type=checkbox]:first-child){list-style:none;padding-left:0}.c-readme-view ul:has(li>input[type=checkbox]:first-child){list-style:none;padding-left:0}.c-realm-view li:has(>input[type=checkbox]:first-child){align-items:center;display:flex;gap:var(--g-space-2)}.c-readme-view li:has(>input[type=checkbox]:first-child){align-items:center;display:flex;gap:var(--g-space-2)}.c-readme-view li>input[type=checkbox]:first-child,.c-realm-view li>input[type=checkbox]:first-child{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:var(--s-border-secondary);border-radius:var(--s-rounded-sm);flex-shrink:0;height:var(--g-space-5);padding:0;position:relative;width:var(--g-space-5)}.c-readme-view li>input[type=checkbox]:first-child:disabled,.c-realm-view li>input[type=checkbox]:first-child:disabled{background-color:var(--s-color-bg-surface-primary);border-color:var(--s-color-border-tertiary);cursor:not-allowed}.c-readme-view li>input[type=checkbox]:first-child:disabled:after,.c-realm-view li>input[type=checkbox]:first-child:disabled:after{background-color:var(--s-color-bg-brand-default)}.c-readme-view li>input[type=checkbox]:first-child:checked:after,.c-realm-view li>input[type=checkbox]:first-child:checked:after{opacity:1}.c-readme-view li>input[type=checkbox]:first-child:after,.c-realm-view li>input[type=checkbox]:first-child:after{background-color:var(--s-color-bg-base);clip-path:polygon(25% 36%,43% 54%,76% 18%,89% 29%,44% 78%,13% 49%);content:"";height:var(--g-space-3);left:50%;margin:auto;opacity:0;position:absolute;top:50%;transform:translate(-50%,-50%);width:var(--g-space-3)}.c-readme-view .footnote-backref,.c-realm-view .footnote-backref{vertical-align:middle}.c-readme-view li[id^="fn:"],.c-realm-view li[id^="fn:"]{position:relative;z-index:var(--g-z-1)}.c-readme-view li[id^="fn:"]:before,.c-realm-view li[id^="fn:"]:before{background-color:var(--s-color-bg-brand-weak);border-radius:var(--s-rounded-sm);bottom:0;content:"";display:block;left:calc(var(--g-space-0-5)*-1);opacity:0;position:absolute;right:calc(var(--g-space-0-5)*-1);top:calc(var(--g-space-0-5)*-1);z-index:var(--g-z-min)}.c-readme-view li[id^="fn:"]:target:before,.c-realm-view li[id^="fn:"]:target:before{opacity:1;transition-delay:var(--g-duration-150);transition-duration:var(--g-duration-75)}.c-readme-view .gno-form,.c-realm-view .gno-form{border:var(--s-border-secondary);border-radius:var(--s-rounded);display:block;margin-bottom:var(--g-space-6);margin-top:var(--g-space-6)}.c-readme-view .gno-form input[type=submit],.c-realm-view .gno-form input[type=submit]{background-color:var(--s-color-bg-brand-default);border-color:var(--s-color-border-brand-default);border-radius:var(--s-rounded-sm);color:var(--s-color-text-base);cursor:pointer;margin-bottom:var(--g-space-2);margin-top:var(--g-space-4);width:100%}.c-readme-view .gno-form input[type=submit]:hover,.c-realm-view .gno-form input[type=submit]:hover{opacity:.9}.c-readme-view .gno-form .command,.c-realm-view .gno-form .command{background-color:var(--s-color-bg-base-dev);margin-top:var(--g-space-6);padding:var(--g-space-4)}.c-readme-view .gno-form .command .title,.c-realm-view .gno-form .command .title{font-size:var(--g-font-size-200);font-weight:var(--g-font-semibold);white-space:nowrap}.c-readme-view .gno-form .command>.b-code,.c-realm-view .gno-form .command>.b-code{background-color:var(--s-color-bg-base-dev)}.c-readme-view .gno-form .command>.b-code>pre,.c-realm-view .gno-form .command>.b-code>pre{background-color:var(--s-color-bg-base);margin-bottom:0}.c-readme-view .gno-form .command .c-between,.c-readme-view .gno-form .command .c-inline,.c-realm-view .gno-form .command .c-between,.c-realm-view .gno-form .command .c-inline{align-items:flex-start;flex-direction:column;gap:var(--g-space-2)}@media (min-width:calc(640 / 16 * 1rem)){.c-readme-view .gno-form .command .c-between,.c-readme-view .gno-form .command .c-inline,.c-realm-view .gno-form .command .c-between,.c-realm-view .gno-form .command .c-inline{align-items:center;flex-direction:row}}.c-readme-view .gno-form .command .c-between>*,.c-readme-view .gno-form .command .c-inline>*,.c-realm-view .gno-form .command .c-between>*,.c-realm-view .gno-form .command .c-inline>*{width:100%}@media (min-width:calc(640 / 16 * 1rem)){.c-readme-view .gno-form .command .c-between>*,.c-readme-view .gno-form .command .c-inline>*,.c-realm-view .gno-form .command .c-between>*,.c-realm-view .gno-form .command .c-inline>*{width:auto}}.c-readme-view .gno-form_header,.c-realm-view .gno-form_header{color:var(--s-color-text-tertiary);display:flex;font-size:var(--g-font-size-50);justify-content:space-between;margin-bottom:var(--g-space-6);padding:var(--g-space-2) var(--g-space-4) 0}.c-readme-view .gno-form_input,.c-readme-view .gno-form_select,.c-realm-view .gno-form_input,.c-realm-view .gno-form_select{padding-left:var(--g-space-4);padding-right:var(--g-space-4);position:relative}.c-readme-view .gno-form_input label,.c-readme-view .gno-form_select label,.c-realm-view .gno-form_input label,.c-realm-view .gno-form_select label{background-color:var(--s-color-bg-input);color:var(--s-color-text-tertiary);display:none;font-size:var(--g-font-size-50);left:var(--g-space-5);padding-left:var(--g-space-1);padding-right:var(--g-space-1);position:absolute;top:0;transform:translateY(-50%)}.c-readme-view .gno-form_input svg,.c-readme-view .gno-form_select svg,.c-realm-view .gno-form_input svg,.c-realm-view .gno-form_select svg{height:var(--g-space-4);pointer-events:none;position:absolute;right:var(--g-space-6);top:50%;transform:translateY(-50%);width:var(--g-space-4)}.c-realm-view .gno-form_input:has(input:focus) label{display:block}.c-readme-view .gno-form_input:has(input:focus) label{display:block}.c-realm-view .gno-form_input:has(input:not(:-moz-placeholder)) label{display:block}.c-realm-view .gno-form_input:has(input:not(:placeholder-shown)) label{display:block}.c-readme-view .gno-form_input:has(input:not(:-moz-placeholder)) label{display:block}.c-readme-view .gno-form_input:has(input:not(:placeholder-shown)) label{display:block}.c-realm-view .gno-form_input:has(textarea:not(:-moz-placeholder)) label{display:block}.c-realm-view .gno-form_input:has(textarea:not(:placeholder-shown)) label{display:block}.c-readme-view .gno-form_input:has(textarea:not(:-moz-placeholder)) label{display:block}.c-readme-view .gno-form_input:has(textarea:not(:placeholder-shown)) label{display:block}.c-realm-view .gno-form_input:has(textarea:focus) label{display:block}.c-readme-view .gno-form_input:has(textarea:focus) label{display:block}.c-realm-view .gno-form_select:has(select:focus) label{display:block}.c-readme-view .gno-form_select:has(select:focus) label{display:block}.c-realm-view .gno-form_select:has(select option:not([value=""]):checked) label{display:block}.c-readme-view .gno-form_select:has(select option:not([value=""]):checked) label{display:block}.c-readme-view .gno-form_input input,.c-readme-view .gno-form_input textarea,.c-readme-view .gno-form_select select,.c-realm-view .gno-form_input input,.c-realm-view .gno-form_input textarea,.c-realm-view .gno-form_select select{background-color:var(--s-color-bg-input);border:var(--g-space-px) solid var(--s-color-border-input);border-radius:var(--s-rounded-sm);color:var(--s-color-text-primary);display:block;margin-bottom:var(--g-space-4);margin-top:var(--g-space-4);outline:none;padding:var(--g-space-2);width:100%}.c-readme-view .gno-form_input input:focus,.c-readme-view .gno-form_input input:hover,.c-readme-view .gno-form_input textarea:focus,.c-readme-view .gno-form_input textarea:hover,.c-readme-view .gno-form_select select:focus,.c-readme-view .gno-form_select select:hover,.c-realm-view .gno-form_input input:focus,.c-realm-view .gno-form_input input:hover,.c-realm-view .gno-form_input textarea:focus,.c-realm-view .gno-form_input textarea:hover,.c-realm-view .gno-form_select select:focus,.c-realm-view .gno-form_select select:hover{border-color:var(--s-color-border-tertiary)}.c-realm-view .gno-form_input input::-moz-placeholder{color:var(--s-color-text-tertiary)}.c-realm-view .gno-form_input input::placeholder{color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_input input::-moz-placeholder{color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_input input::placeholder{color:var(--s-color-text-tertiary)}.c-realm-view .gno-form_input textarea::-moz-placeholder{color:var(--s-color-text-tertiary)}.c-realm-view .gno-form_input textarea::placeholder{color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_input textarea::-moz-placeholder{color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_input textarea::placeholder{color:var(--s-color-text-tertiary)}.c-realm-view .gno-form_select select::-moz-placeholder{color:var(--s-color-text-tertiary)}.c-realm-view .gno-form_select select::placeholder{color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_select select::-moz-placeholder{color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_select select::placeholder{color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_input input:disabled,.c-readme-view .gno-form_input input[readonly],.c-readme-view .gno-form_input textarea:disabled,.c-readme-view .gno-form_input textarea[readonly],.c-readme-view .gno-form_select select:disabled,.c-readme-view .gno-form_select select[readonly],.c-realm-view .gno-form_input input:disabled,.c-realm-view .gno-form_input input[readonly],.c-realm-view .gno-form_input textarea:disabled,.c-realm-view .gno-form_input textarea[readonly],.c-realm-view .gno-form_select select:disabled,.c-realm-view .gno-form_select select[readonly]{background-color:var(--s-color-bg-secondary);color:var(--s-color-text-tertiary);cursor:not-allowed}.c-readme-view .gno-form_input input:disabled:focus,.c-readme-view .gno-form_input input:disabled:hover,.c-readme-view .gno-form_input input[readonly]:focus,.c-readme-view .gno-form_input input[readonly]:hover,.c-readme-view .gno-form_input textarea:disabled:focus,.c-readme-view .gno-form_input textarea:disabled:hover,.c-readme-view .gno-form_input textarea[readonly]:focus,.c-readme-view .gno-form_input textarea[readonly]:hover,.c-readme-view .gno-form_select select:disabled:focus,.c-readme-view .gno-form_select select:disabled:hover,.c-readme-view .gno-form_select select[readonly]:focus,.c-readme-view .gno-form_select select[readonly]:hover,.c-realm-view .gno-form_input input:disabled:focus,.c-realm-view .gno-form_input input:disabled:hover,.c-realm-view .gno-form_input input[readonly]:focus,.c-realm-view .gno-form_input input[readonly]:hover,.c-realm-view .gno-form_input textarea:disabled:focus,.c-realm-view .gno-form_input textarea:disabled:hover,.c-realm-view .gno-form_input textarea[readonly]:focus,.c-realm-view .gno-form_input textarea[readonly]:hover,.c-realm-view .gno-form_select select:disabled:focus,.c-realm-view .gno-form_select select:disabled:hover,.c-realm-view .gno-form_select select[readonly]:focus,.c-realm-view .gno-form_select select[readonly]:hover{border-color:var(--s-color-border-secondary)}.c-realm-view .gno-form_input input:user-invalid{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input input:user-invalid{border-color:var(--s-color-border-error)}.c-realm-view .gno-form_input textarea:user-invalid{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input textarea:user-invalid{border-color:var(--s-color-border-error)}.c-realm-view .gno-form_select select:user-invalid{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_select select:user-invalid{border-color:var(--s-color-border-error)}.c-realm-view .gno-form_input input:user-invalid:focus{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input input:user-invalid:focus{border-color:var(--s-color-border-error)}.c-realm-view .gno-form_input textarea:user-invalid:focus{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input textarea:user-invalid:focus{border-color:var(--s-color-border-error)}.c-realm-view .gno-form_select select:user-invalid:focus{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_select select:user-invalid:focus{border-color:var(--s-color-border-error)}@supports not selector(:user-invalid){.c-realm-view .gno-form_input input:invalid:not(:-moz-placeholder):not(:focus){border-color:var(--s-color-border-error)}.c-realm-view .gno-form_input input:invalid:not(:placeholder-shown):not(:focus){border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input input:invalid:not(:-moz-placeholder):not(:focus){border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input input:invalid:not(:placeholder-shown):not(:focus){border-color:var(--s-color-border-error)}.c-realm-view .gno-form_input textarea:invalid:not(:-moz-placeholder):not(:focus){border-color:var(--s-color-border-error)}.c-realm-view .gno-form_input textarea:invalid:not(:placeholder-shown):not(:focus){border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input textarea:invalid:not(:-moz-placeholder):not(:focus){border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input textarea:invalid:not(:placeholder-shown):not(:focus){border-color:var(--s-color-border-error)}.c-realm-view .gno-form_select select:invalid:not(:-moz-placeholder):not(:focus){border-color:var(--s-color-border-error)}.c-realm-view .gno-form_select select:invalid:not(:placeholder-shown):not(:focus){border-color:var(--s-color-border-error)}.c-readme-view .gno-form_select select:invalid:not(:-moz-placeholder):not(:focus){border-color:var(--s-color-border-error)}.c-readme-view .gno-form_select select:invalid:not(:placeholder-shown):not(:focus){border-color:var(--s-color-border-error)}}.c-realm-view .gno-form_input input:focus::-moz-placeholder{opacity:0}.c-realm-view .gno-form_input input:focus::placeholder{opacity:0}.c-readme-view .gno-form_input input:focus::-moz-placeholder{opacity:0}.c-readme-view .gno-form_input input:focus::placeholder{opacity:0}.c-realm-view .gno-form_input textarea:focus::-moz-placeholder{opacity:0}.c-realm-view .gno-form_input textarea:focus::placeholder{opacity:0}.c-readme-view .gno-form_input textarea:focus::-moz-placeholder{opacity:0}.c-readme-view .gno-form_input textarea:focus::placeholder{opacity:0}.c-readme-view .gno-form textarea,.c-realm-view .gno-form textarea{resize:none}.c-realm-view .gno-form_select select:has(option[value=""]:checked){color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_select select:has(option[value=""]:checked){color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_description,.c-realm-view .gno-form_description{color:var(--s-color-text-secondary);font-weight:var(--g-font-semibold);margin-bottom:var(--g-space-2);margin-top:var(--g-space-5);padding-left:var(--g-space-4)}.c-readme-view .gno-form_info-badge,.c-realm-view .gno-form_info-badge{color:var(--s-color-text-tertiary);font-size:var(--g-font-size-50);font-weight:var(--g-font-normal);margin-left:var(--g-space-1)}.c-readme-view .gno-form_selectable,.c-realm-view .gno-form_selectable{-moz-column-gap:var(--g-space-2);column-gap:var(--g-space-2);display:flex;margin-bottom:var(--g-space-1);padding-left:var(--g-space-4);padding-right:var(--g-space-4)}.c-readme-view .gno-form_selectable input,.c-realm-view .gno-form_selectable input{width:auto}.c-readme-view .gno-form_selectable input[type=checkbox],.c-readme-view .gno-form_selectable input[type=radio],.c-realm-view .gno-form_selectable input[type=checkbox],.c-realm-view .gno-form_selectable input[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:var(--s-border);border-radius:var(--s-rounded-full);flex-shrink:0;height:var(--g-space-5);padding:0;position:relative;width:var(--g-space-5)}.c-readme-view .gno-form_selectable input[type=checkbox]:checked,.c-readme-view .gno-form_selectable input[type=radio]:checked,.c-realm-view .gno-form_selectable input[type=checkbox]:checked,.c-realm-view .gno-form_selectable input[type=radio]:checked{background-color:var(--s-color-bg-brand-default);border-color:transparent}.c-readme-view .gno-form_selectable input[type=checkbox]:checked:after,.c-readme-view .gno-form_selectable input[type=radio]:checked:after,.c-realm-view .gno-form_selectable input[type=checkbox]:checked:after,.c-realm-view .gno-form_selectable input[type=radio]:checked:after{opacity:1}.c-readme-view .gno-form_selectable input[type=checkbox]:focus,.c-readme-view .gno-form_selectable input[type=radio]:focus,.c-realm-view .gno-form_selectable input[type=checkbox]:focus,.c-realm-view .gno-form_selectable input[type=radio]:focus{outline:none}.c-readme-view .gno-form_selectable input[type=checkbox]:focus,.c-readme-view .gno-form_selectable input[type=checkbox]:hover,.c-readme-view .gno-form_selectable input[type=radio]:focus,.c-readme-view .gno-form_selectable input[type=radio]:hover,.c-realm-view .gno-form_selectable input[type=checkbox]:focus,.c-realm-view .gno-form_selectable input[type=checkbox]:hover,.c-realm-view .gno-form_selectable input[type=radio]:focus,.c-realm-view .gno-form_selectable input[type=radio]:hover{border-color:var(--s-color-border-tertiary);color:var(--s-color-text-brand-default)}.c-readme-view .gno-form_selectable input[type=checkbox]:focus+label,.c-readme-view .gno-form_selectable input[type=checkbox]:hover+label,.c-readme-view .gno-form_selectable input[type=radio]:focus+label,.c-readme-view .gno-form_selectable input[type=radio]:hover+label,.c-realm-view .gno-form_selectable input[type=checkbox]:focus+label,.c-realm-view .gno-form_selectable input[type=checkbox]:hover+label,.c-realm-view .gno-form_selectable input[type=radio]:focus+label,.c-realm-view .gno-form_selectable input[type=radio]:hover+label{color:var(--s-color-text-brand-default);-webkit-text-decoration:underline;text-decoration:underline}.c-readme-view .gno-form_selectable input[type=checkbox]:disabled,.c-readme-view .gno-form_selectable input[type=radio]:disabled,.c-realm-view .gno-form_selectable input[type=checkbox]:disabled,.c-realm-view .gno-form_selectable input[type=radio]:disabled{cursor:not-allowed;opacity:var(--g-opacity-50);-webkit-text-decoration:line-through;text-decoration:line-through}.c-readme-view .gno-form_selectable input[type=checkbox]:disabled+label,.c-readme-view .gno-form_selectable input[type=radio]:disabled+label,.c-realm-view .gno-form_selectable input[type=checkbox]:disabled+label,.c-realm-view .gno-form_selectable input[type=radio]:disabled+label{cursor:not-allowed;opacity:var(--g-opacity-50);-webkit-text-decoration:none;text-decoration:none}.c-realm-view .gno-form_selectable input[type=radio]:user-invalid{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_selectable input[type=radio]:user-invalid{border-color:var(--s-color-border-error)}.c-realm-view .gno-form_selectable input[type=checkbox]:user-invalid{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_selectable input[type=checkbox]:user-invalid{border-color:var(--s-color-border-error)}@supports not selector(:user-invalid){.c-realm-view .gno-form_selectable input[type=radio]:invalid{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_selectable input[type=radio]:invalid{border-color:var(--s-color-border-error)}.c-realm-view .gno-form_selectable input[type=checkbox]:invalid{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_selectable input[type=checkbox]:invalid{border-color:var(--s-color-border-error)}}.c-readme-view .gno-form_selectable input[type=radio]:after,.c-realm-view .gno-form_selectable input[type=radio]:after{background-color:var(--s-color-bg-brand-default);border:var(--s-border-secondary);border-radius:var(--s-rounded-full);border-width:calc(var(--g-space-px)*2);content:"";height:var(--g-space-4);left:50%;opacity:0;position:absolute;top:50%;transform:translate(-50%,-50%);width:var(--g-space-4)}.c-readme-view .gno-form_selectable input[type=checkbox],.c-realm-view .gno-form_selectable input[type=checkbox]{border-radius:var(--s-rounded-sm)}.c-readme-view .gno-form_selectable input[type=checkbox]:after,.c-realm-view .gno-form_selectable input[type=checkbox]:after{background-color:var(--s-color-bg-base);clip-path:polygon(25% 36%,43% 54%,76% 18%,89% 29%,44% 78%,13% 49%);content:"";height:var(--g-space-3);left:50%;margin:auto;opacity:0;position:absolute;top:50%;transform:translate(-50%,-50%);width:var(--g-space-3)}.c-readme-view .gno-form_selectable label,.c-realm-view .gno-form_selectable label{color:var(--s-color-text-secondary);cursor:pointer;display:block;position:relative}.c-readme-view .gno-form_selectable label:first-letter,.c-realm-view .gno-form_selectable label:first-letter{text-transform:capitalize}.c-readme-view .gno-alert,.c-realm-view .gno-alert{border-left:var(--g-space-1) solid var(--s-color-border-tertiary);border-radius:var(--s-rounded);color:var(--s-color-text-secondary);margin-bottom:var(--g-space-10);margin-top:var(--g-space-5);padding:var(--g-space-3) var(--g-space-4)}.c-readme-view .gno-alert>div>:first-child,.c-realm-view .gno-alert>div>:first-child{margin-top:var(--g-space-2)}.c-readme-view .gno-alert>div>:last-child,.c-realm-view .gno-alert>div>:last-child{margin-bottom:0}.c-readme-view .gno-alert>summary,.c-realm-view .gno-alert>summary{align-items:center;display:flex;font-weight:var(--g-font-bold);gap:var(--g-space-2);list-style:none;margin-bottom:var(--g-space-1);margin-top:var(--g-space-1)}.c-readme-view .gno-alert>summary svg,.c-realm-view .gno-alert>summary svg{height:var(--g-space-6);width:var(--g-space-6)}.c-readme-view .gno-alert>summary svg:last-of-type,.c-realm-view .gno-alert>summary svg:last-of-type{height:var(--g-space-4);margin-left:auto;transform:rotate(-90deg);width:var(--g-space-4)}.c-readme-view .gno-alert>summary a,.c-readme-view .gno-alert>summary svg,.c-realm-view .gno-alert>summary a,.c-realm-view .gno-alert>summary svg{color:inherit}.c-realm-view .gno-alert>summary::marker{display:none}.c-readme-view .gno-alert>summary::marker{display:none}.c-readme-view .gno-alert>summary::-webkit-details-marker,.c-realm-view .gno-alert>summary::-webkit-details-marker{display:none}.c-readme-view .gno-alert[open]>summary svg:last-of-type,.c-realm-view .gno-alert[open]>summary svg:last-of-type{transform:rotate(0)}.c-readme-view .gno-alert.gno-alert-info,.c-realm-view .gno-alert.gno-alert-info{background-color:color-mix(in srgb,var(--s-color-bg-info-default) 10%,transparent);border-left-color:var(--s-color-border-info);color:var(--s-color-text-info)}.c-readme-view .gno-alert.gno-alert-note,.c-realm-view .gno-alert.gno-alert-note{background-color:color-mix(in srgb,var(--s-color-bg-note-default) 10%,transparent);border-left-color:var(--s-color-border-note);color:var(--s-color-text-note)}.c-readme-view .gno-alert.gno-alert-success,.c-realm-view .gno-alert.gno-alert-success{background-color:color-mix(in srgb,var(--s-color-bg-success-default) 10%,transparent);border-left-color:var(--s-color-border-success);color:var(--s-color-text-success)}.c-readme-view .gno-alert.gno-alert-warning,.c-realm-view .gno-alert.gno-alert-warning{background-color:color-mix(in srgb,var(--s-color-bg-warning-default) 10%,transparent);border-left-color:var(--s-color-border-warning);color:var(--s-color-text-warning)}.c-readme-view .gno-alert.gno-alert-caution,.c-realm-view .gno-alert.gno-alert-caution{background-color:color-mix(in srgb,var(--s-color-bg-caution-default) 10%,transparent);border-left-color:var(--s-color-border-error);color:var(--s-color-text-caution)}.c-readme-view .gno-alert.gno-alert-tip,.c-realm-view .gno-alert.gno-alert-tip{background-color:color-mix(in srgb,var(--s-color-bg-tip-default) 10%,transparent);border-left-color:var(--s-color-border-tip);color:var(--s-color-text-tip)}.b-playground{display:flex;flex-direction:column;gap:var(--g-space-2);min-height:70vh}.b-playground-toolbar{flex-wrap:wrap;justify-content:space-between}.b-playground-toolbar,.b-playground-toolbar-left{align-items:center;display:flex;gap:var(--g-space-2)}.b-playground-toolbar-right{align-items:center;display:flex;gap:var(--g-space-1)}.b-playground-title{font-size:var(--g-font-size-200);font-weight:var(--g-font-semibold)}.b-playground-editor-area{border:1px solid var(--s-color-border-default);border-radius:var(--g-radius-2);display:flex;flex:1;flex-direction:column;min-height:40vh;overflow:hidden}.b-playground-tabs{align-items:center;background-color:var(--s-color-bg-muted);border-bottom:1px solid var(--s-color-border-default);display:flex;gap:0;overflow-x:auto}.b-playground-tab{background:none;border:none;border-bottom:2px solid transparent;color:var(--s-color-text-muted);cursor:pointer;font-family:var(--g-font-mono);font-size:var(--g-font-size-50);padding:var(--g-space-1) var(--g-space-2);white-space:nowrap}.b-playground-tab:hover{background-color:var(--s-color-bg-default);color:var(--s-color-text-base)}--active.b-playground-tab{border-bottom-color:var(--s-color-border-brand);color:var(--s-color-text-base);font-weight:var(--g-font-semibold)}.b-playground-tab-add{background:none;border:none;color:var(--s-color-text-muted);cursor:pointer;font-size:var(--g-font-size-100);padding:var(--g-space-1) var(--g-space-2)}.b-playground-tab-add:hover{color:var(--s-color-text-base)}.b-playground-editor{display:flex;flex:1}.b-playground-code{background-color:var(--s-color-bg-default);border:none;color:var(--s-color-text-base);flex:1;font-family:var(--g-font-mono);font-size:var(--g-font-size-75);line-height:1.6;min-height:300px;outline:none;padding:var(--g-space-2);resize:none;-moz-tab-size:4;-o-tab-size:4;tab-size:4;width:100%}.b-playground-output{border:1px solid var(--s-color-border-default);border-radius:var(--g-radius-2);overflow:hidden}.b-playground-output-header{align-items:center;background-color:var(--s-color-bg-muted);border-bottom:1px solid var(--s-color-border-default);display:flex;justify-content:space-between;padding:var(--g-space-1) var(--g-space-2)}.b-playground-output-title{font-size:var(--g-font-size-50);font-weight:var(--g-font-semibold);letter-spacing:.05em;text-transform:uppercase}.b-playground-output-clear{background:none;border:none;color:var(--s-color-text-muted);cursor:pointer;font-size:var(--g-font-size-50)}.b-playground-output-clear:hover{color:var(--s-color-text-base)}.b-playground-output-content{font-family:var(--g-font-mono);font-size:var(--g-font-size-75);line-height:1.6;margin:0;max-height:300px;min-height:80px;overflow-y:auto;padding:var(--g-space-2);white-space:pre-wrap;word-break:break-word}.b-eval{display:flex;flex-direction:column;gap:var(--g-space-3)}.b-eval-header{margin-bottom:var(--g-space-1)}.b-eval-form{align-items:flex-end;display:flex;gap:var(--g-space-2)}.b-eval-form .b-input--full{flex:1}.b-eval-result{border:1px solid var(--s-color-border-default);border-radius:var(--g-radius-2);overflow:hidden}.b-eval-result-header{align-items:center;background-color:var(--s-color-bg-muted);border-bottom:1px solid var(--s-color-border-default);display:flex;font-size:var(--g-font-size-50);font-weight:var(--g-font-semibold);justify-content:space-between;letter-spacing:.05em;padding:var(--g-space-1) var(--g-space-2);text-transform:uppercase}.b-eval-result-clear{background:none;border:none;color:var(--s-color-text-muted);cursor:pointer;font-size:var(--g-font-size-50)}.b-eval-result-clear:hover{color:var(--s-color-text-base)}.b-eval-result-content{font-family:var(--g-font-mono);font-size:var(--g-font-size-75);line-height:1.6;margin:0;max-height:400px;min-height:60px;overflow-y:auto;padding:var(--g-space-2);white-space:pre-wrap;word-break:break-word}.b-eval-func{align-items:center;display:flex;gap:var(--g-space-2)}.b-eval-history-entry{border:1px solid var(--s-color-border-default);border-radius:var(--g-radius-1);padding:var(--g-space-1-5)}.b-eval-history-entry+.b-eval-history-entry{margin-top:var(--g-space-1)}.b-eval-history-expr{align-items:center;display:flex;justify-content:space-between;margin-bottom:var(--g-space-1)}.b-eval-history-rerun{background:none;border:none;color:var(--s-color-text-muted);cursor:pointer;font-size:var(--g-font-size-100)}.b-eval-history-rerun:hover{color:var(--s-color-text-base)}.b-eval-history-result{background-color:var(--s-color-bg-muted);border-radius:var(--g-radius-1);font-family:var(--g-font-mono);font-size:var(--g-font-size-50);margin:0;padding:var(--g-space-1);white-space:pre-wrap;word-break:break-word}.u-hidden{display:none}.u-inline{display:inline}.u-sr-only{height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;clip:rect(0,0,0,0);border-width:0;white-space:nowrap}.u-color-valid{color:var(--s-color-bg-brand-default)}.u-color-danger{color:var(--s-color-text-caution)}.u-text-stroke{-webkit-text-stroke:currentColor;-webkit-text-stroke-width:.6px}.u-font-mono{font-family:var(--g-font-family-mono)}.u-capitalize{text-transform:capitalize}.u-text-center{text-align:center}.u-no-scrollbar::-webkit-scrollbar{display:none}.u-no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.u-is-loading{opacity:0}.u-icon-static{height:var(--g-space-4);width:var(--g-space-4)}.u-gap-0{gap:0}.u-mt-4{margin-top:var(--g-space-4)}.u-mb-0{margin-bottom:0}.u-mb-2{margin-bottom:var(--g-space-2)}@media (min-width:calc(820 / 16 * 1rem)){.u-lg-mb-4{margin-bottom:var(--g-space-4)}}.u-grid-full{grid-column:1/-1}
\ No newline at end of file
From 6f327fbcdd8aa06cf4235947870fd4046ed2da54 Mon Sep 17 00:00:00 2001
From: moul <94029+moul@users.noreply.github.com>
Date: Fri, 3 Apr 2026 19:32:50 +0000
Subject: [PATCH 02/40] fix(gnoweb): eval uses server-side API, fix playground
layout
---
.../pkg/gnoweb/frontend/css/06-blocks.css | 2 ++
.../pkg/gnoweb/frontend/js/controller-eval.ts | 29 +++++++++++--------
.../pkg/gnoweb/public/js/controller-eval.js | 2 +-
gno.land/pkg/gnoweb/public/main.css | 2 +-
4 files changed, 21 insertions(+), 14 deletions(-)
diff --git a/gno.land/pkg/gnoweb/frontend/css/06-blocks.css b/gno.land/pkg/gnoweb/frontend/css/06-blocks.css
index 0e2a3c1183d..005a346a31c 100644
--- a/gno.land/pkg/gnoweb/frontend/css/06-blocks.css
+++ b/gno.land/pkg/gnoweb/frontend/css/06-blocks.css
@@ -2527,6 +2527,7 @@ main.dev-mode .b-toc a {
flex-direction: column;
gap: var(--g-space-2);
min-height: 70vh;
+ grid-column: 1 / -1;
}
.b-playground-toolbar {
@@ -2684,6 +2685,7 @@ main.dev-mode .b-toc a {
display: flex;
flex-direction: column;
gap: var(--g-space-3);
+ grid-column: 1 / -1;
}
.b-eval-header {
diff --git a/gno.land/pkg/gnoweb/frontend/js/controller-eval.ts b/gno.land/pkg/gnoweb/frontend/js/controller-eval.ts
index b9d9d86e806..b5ec39139c0 100644
--- a/gno.land/pkg/gnoweb/frontend/js/controller-eval.ts
+++ b/gno.land/pkg/gnoweb/frontend/js/controller-eval.ts
@@ -70,36 +70,41 @@ export class EvalController extends BaseController {
}
private async _doEval(expression: string): Promise {
- const remote = this.getValue("remote");
const pkgPath = this.getValue("pkg-path");
+ const domain = this.getValue("domain");
this._resultEl.textContent = "Evaluating...";
this._resultEl.classList.remove("u-color-danger");
try {
- const data = `${pkgPath}.${expression}`;
- const url = `${remote}/abci_query?path=vm%2fqeval&data=${btoa(data)}`;
- const response = await fetch(url);
+ // Strip domain prefix from pkgPath for the API (it expects relative path)
+ const relPath = pkgPath.startsWith(domain + "/")
+ ? pkgPath.slice(domain.length + 1)
+ : pkgPath;
+
+ const response = await fetch("/_/api/eval", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ pkg_path: relPath,
+ expression: expression,
+ }),
+ });
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const json = await response.json();
- const responseBase = json.result.response.ResponseBase;
let result: string;
let isError: boolean;
- if (responseBase.Data) {
- result = atob(responseBase.Data);
- isError = false;
- } else if (responseBase.Error) {
- const errorValue = responseBase.Error.value || responseBase.Error;
- result = `Error: ${typeof errorValue === "string" ? errorValue : JSON.stringify(errorValue)}`;
+ if (json.error) {
+ result = `Error: ${json.error}`;
isError = true;
} else {
- result = "(empty result)";
+ result = json.result || "(empty result)";
isError = false;
}
diff --git a/gno.land/pkg/gnoweb/public/js/controller-eval.js b/gno.land/pkg/gnoweb/public/js/controller-eval.js
index a088b64e35d..2c7e3e78dce 100644
--- a/gno.land/pkg/gnoweb/public/js/controller-eval.js
+++ b/gno.land/pkg/gnoweb/public/js/controller-eval.js
@@ -1 +1 @@
-import{BaseController as p}from"./controller.js";var h=class extends p{_inputEl;_resultEl;_historyListEl;_history=[];connect(){this._inputEl=this.getTarget("input"),this._resultEl=this.getTarget("result"),this._historyListEl=this.getTarget("history-list"),this._inputEl?.focus(),this._inputEl?.addEventListener("keydown",t=>{t.key==="ArrowUp"&&this._history.length>0&&(t.preventDefault(),this._inputEl.value=this._history[this._history.length-1].expression)})}evalExpression(t){t.preventDefault();let e=this._inputEl.value.trim();e&&this._doEval(e)}quickCall(t){let e=t.params?.funcName,a=t.params?.funcSig;if(e){let s=a?.match(/\(([^)]*)\)/),r=s?s[1]:"";r?(this._inputEl.value=`${e}(${r.split(",").map(i=>{let o=i.trim().split(/\s+/);return o[o.length-1]==="string"?'""':"0"}).join(", ")})`,this._inputEl.focus(),this._inputEl.setSelectionRange(e.length+1,this._inputEl.value.length-1)):(this._inputEl.value=`${e}()`,this._doEval(`${e}()`))}}clearResult(){this._resultEl.textContent="// Enter an expression above",this._resultEl.classList.remove("u-color-danger")}async _doEval(t){let e=this.getValue("remote"),a=this.getValue("pkg-path");this._resultEl.textContent="Evaluating...",this._resultEl.classList.remove("u-color-danger");try{let s=`${a}.${t}`,r=`${e}/abci_query?path=vm%2fqeval&data=${btoa(s)}`,i=await fetch(r);if(!i.ok)throw new Error(`HTTP ${i.status}`);let n=(await i.json()).result.response.ResponseBase,l,u;if(n.Data)l=atob(n.Data),u=!1;else if(n.Error){let c=n.Error.value||n.Error;l=`Error: ${typeof c=="string"?c:JSON.stringify(c)}`,u=!0}else l="(empty result)",u=!1;this._resultEl.textContent=l,this._resultEl.classList.toggle("u-color-danger",u),this._addToHistory(t,l,u)}catch(s){let r=`Error: ${s instanceof Error?s.message:String(s)}`;this._resultEl.textContent=r,this._resultEl.classList.add("u-color-danger"),this._addToHistory(t,r,!0)}}_addToHistory(t,e,a){if(this._history.push({expression:t,result:e,isError:a}),this._historyListEl){let s=document.createElement("div");s.className="b-eval-history-entry";let r=document.createElement("div");r.className="b-eval-history-expr";let i=document.createElement("code");i.className="u-font-mono",i.textContent=t,r.appendChild(i);let o=document.createElement("button");o.className="b-eval-history-rerun",o.textContent="\u21A9",o.title="Re-run",o.addEventListener("click",()=>{this._inputEl.value=t,this._doEval(t)}),r.appendChild(o);let n=document.createElement("pre");n.className=`b-eval-history-result${a?" u-color-danger":""}`,n.textContent=e.length>200?e.substring(0,200)+"...":e,s.appendChild(r),s.appendChild(n),this._historyListEl.prepend(s)}}};export{h as EvalController};
+import{BaseController as h}from"./controller.js";var a=class extends h{_inputEl;_resultEl;_historyListEl;_history=[];connect(){this._inputEl=this.getTarget("input"),this._resultEl=this.getTarget("result"),this._historyListEl=this.getTarget("history-list"),this._inputEl?.focus(),this._inputEl?.addEventListener("keydown",e=>{e.key==="ArrowUp"&&this._history.length>0&&(e.preventDefault(),this._inputEl.value=this._history[this._history.length-1].expression)})}evalExpression(e){e.preventDefault();let t=this._inputEl.value.trim();t&&this._doEval(t)}quickCall(e){let t=e.params?.funcName,o=e.params?.funcSig;if(t){let i=o?.match(/\(([^)]*)\)/),s=i?i[1]:"";s?(this._inputEl.value=`${t}(${s.split(",").map(r=>{let n=r.trim().split(/\s+/);return n[n.length-1]==="string"?'""':"0"}).join(", ")})`,this._inputEl.focus(),this._inputEl.setSelectionRange(t.length+1,this._inputEl.value.length-1)):(this._inputEl.value=`${t}()`,this._doEval(`${t}()`))}}clearResult(){this._resultEl.textContent="// Enter an expression above",this._resultEl.classList.remove("u-color-danger")}async _doEval(e){let t=this.getValue("pkg-path"),o=this.getValue("domain");this._resultEl.textContent="Evaluating...",this._resultEl.classList.remove("u-color-danger");try{let i=t.startsWith(o+"/")?t.slice(o.length+1):t,s=await fetch("/_/api/eval",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pkg_path:i,expression:e})});if(!s.ok)throw new Error(`HTTP ${s.status}`);let r=await s.json(),n,l;r.error?(n=`Error: ${r.error}`,l=!0):(n=r.result||"(empty result)",l=!1),this._resultEl.textContent=n,this._resultEl.classList.toggle("u-color-danger",l),this._addToHistory(e,n,l)}catch(i){let s=`Error: ${i instanceof Error?i.message:String(i)}`;this._resultEl.textContent=s,this._resultEl.classList.add("u-color-danger"),this._addToHistory(e,s,!0)}}_addToHistory(e,t,o){if(this._history.push({expression:e,result:t,isError:o}),this._historyListEl){let i=document.createElement("div");i.className="b-eval-history-entry";let s=document.createElement("div");s.className="b-eval-history-expr";let r=document.createElement("code");r.className="u-font-mono",r.textContent=e,s.appendChild(r);let n=document.createElement("button");n.className="b-eval-history-rerun",n.textContent="\u21A9",n.title="Re-run",n.addEventListener("click",()=>{this._inputEl.value=e,this._doEval(e)}),s.appendChild(n);let l=document.createElement("pre");l.className=`b-eval-history-result${o?" u-color-danger":""}`,l.textContent=t.length>200?t.substring(0,200)+"...":t,i.appendChild(s),i.appendChild(l),this._historyListEl.prepend(i)}}};export{a as EvalController};
diff --git a/gno.land/pkg/gnoweb/public/main.css b/gno.land/pkg/gnoweb/public/main.css
index 693ac8d5d18..4afa0710d2c 100644
--- a/gno.land/pkg/gnoweb/public/main.css
+++ b/gno.land/pkg/gnoweb/public/main.css
@@ -3,4 +3,4 @@
li[data-list-type-value=realm]{display:flex}.b-packages:has(input[value=realms]:checked)
article[data-list-type-value=realm]{display:flex}.b-packages:has(input[value=pures]:checked)
li[data-list-type-value=pure]{display:flex}.b-packages:has(input[value=pures]:checked)
- article[data-list-type-value=pure]{display:flex}.b-packages label:has(input:checked){color:var(--s-color-text-tertiary)}.b-packages label:has(input:checked):after{background-color:var(--s-color-bg-brand-default);border-top-left-radius:var(--s-rounded-sm);border-top-right-radius:var(--s-rounded-sm);bottom:calc(var(--g-space-1)*-2);content:"";height:var(--g-space-1);left:0;position:absolute;width:100%}.b-packages:has(input[value=realms]:checked) .range:not(:has(>[data-list-type-value=realm])):before{display:block}.b-packages:has(input[value=realms]:checked) .range:not(:has(>[data-list-type-value=realm])):after{display:block}.b-packages:has(input[value=realms]:checked) .range:not(:has(>[data-list-type-value=realm])):before{content:"No realms found"}.b-packages:has(input[value=realms]:checked) .range:not(:has(>[data-list-type-value=realm])):after{content:"Add a realm to your namespace to get started"}.b-packages:has(input[value=pures]:checked) .range:not(:has(>[data-list-type-value=pure])):before{display:block}.b-packages:has(input[value=pures]:checked) .range:not(:has(>[data-list-type-value=pure])):after{display:block}.b-packages:has(input[value=pures]:checked) .range:not(:has(>[data-list-type-value=pure])):before{content:"No pures found"}.b-packages:has(input[value=pures]:checked) .range:not(:has(>[data-list-type-value=pure])):after{content:"Add a pure to your namespace to get started"}@media (min-width:calc(640 / 16 * 1rem)){.b-packages:has(input[value=display-grid]:checked) .range{gap:var(--g-space-3);grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:calc(1020 / 16 * 1rem)){.b-packages:has(input[value=display-grid]:checked) .range{grid-template-columns:repeat(4,minmax(0,1fr))}}.b-packages:has(input[value=display-grid]:checked) .range article .article-content p{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden}.b-packages:has(input[value=display-list]:checked) .range article .article-content{display:flex;flex:none;flex-direction:row;gap:var(--g-space-2)}@media (min-width:calc(820 / 16 * 1rem)){.b-packages:has(input[value=display-list]:checked) .range article .article-content{flex:5;min-width:0}}.b-packages:has(input[value=display-list]:checked) .range article .article-content .title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:33.33333%}.b-packages:has(input[value=display-list]:checked) .range article .article-content p{white-space:nowrap}.b-packages:has(input[value=display-list]:checked) .range article footer{display:none;justify-content:center;min-width:0;padding-bottom:0;padding-left:0}@media (min-width:calc(640 / 16 * 1rem)){.b-packages:has(input[value=display-list]:checked) .range article footer{flex:1}}@media (min-width:calc(820 / 16 * 1rem)){.b-packages:has(input[value=display-list]:checked) .range article footer{display:flex}}.b-packages:has(input[value=display-list]:checked) .range article footer .size{display:none;min-width:0}@media (min-width:calc(1020 / 16 * 1rem)){.b-packages:has(input[value=display-list]:checked) .range article footer .size{display:block;flex:2}}.b-packages:has(input[value=display-list]:checked) .range article footer time{min-width:0}@media (min-width:calc(1020 / 16 * 1rem)){.b-packages:has(input[value=display-list]:checked) .range article footer time{flex:5}}.b-icon-action{flex-shrink:0;height:var(--g-space-5);width:var(--g-space-5)}.b-popup-bg,.b-popup-dialog{opacity:0;right:0;top:0;visibility:hidden;z-index:var(--g-z-max)}.b-popup-bg{align-items:center;bottom:0;display:flex;justify-content:center;left:0;position:fixed;right:0;top:0}.b-popup-dialog{position:absolute}.b-popup-dialog>.inner{background-color:var(--s-color-bg-base);border:var(--s-border-secondary);border-radius:var(--s-rounded);padding:var(--g-space-2-5) var(--g-space-4);position:absolute;transform:translateX(-100%);z-index:var(--g-z-max)}.b-popup-dialog>.inner>*+*{margin-top:var(--g-space-2-5)}.b-popup-dialog header{align-items:center;color:var(--s-color-text-secondary);display:flex;justify-content:space-between;width:100%}.b-popup-dialog header>svg{color:var(--s-color-text-tertiary);cursor:pointer;position:absolute;right:var(--g-space-3)}.b-popup-dialog header>svg>svg:hover{color:var(--s-color-text-primary)}.b-popup:checked+.b-popup-bg,.b-popup:checked~.b-popup-dialog{opacity:1;visibility:visible}.b-tag,.b-tag--secondary{align-items:center;border:var(--s-border);border-radius:var(--s-rounded-full);color:var(--s-color-text-secondary);display:flex;font-family:var(--g-font-family-mono);font-size:var(--g-font-size-50);gap:var(--g-space-2);padding:var(--g-space-px) var(--g-space-2)}.b-tag--secondary{background-color:var(--s-color-bg-surface-primary);border-color:transparent;color:var(--s-color-text-primary)}.c-readme-view .gno-columns,.c-realm-view .gno-columns{display:flex;flex-wrap:wrap;gap:var(--g-space-10)}@media (min-width:calc(1366 / 16 * 1rem)){.c-readme-view .gno-columns,.c-realm-view .gno-columns{gap:var(--g-space-12)}}.c-readme-view .gno-columns>*,.c-realm-view .gno-columns>*{flex-basis:var(--g-space-52);flex-grow:1;flex-shrink:1}@media (min-width:calc(820 / 16 * 1rem)){.c-readme-view .gno-columns>*,.c-realm-view .gno-columns>*{flex-basis:var(--g-space-44)}}.c-readme-view .tooltip,.c-realm-view .tooltip{--tooltip-left:0;--tooltip-right:initial;align-items:center;border:var(--s-border);border-radius:var(--s-rounded-full);color:var(--s-color-text-tertiary);display:inline-flex;height:var(--g-space-4);justify-content:center;margin-bottom:var(--g-space-px);position:relative;width:var(--g-space-4)}.c-readme-view .tooltip>svg,.c-realm-view .tooltip>svg{height:var(--g-space-3);width:var(--g-space-3)}.c-readme-view .tooltip:after,.c-realm-view .tooltip:after{background-color:var(--s-color-bg-base);border:var(--s-border-secondary);border-radius:var(--s-rounded);color:var(--s-color-text-secondary);content:attr(data-tooltip);font-size:var(--g-font-size-100);font-weight:var(--g-font-normal);left:var(--tooltip-left);max-width:var(--g-space-48);min-width:var(--g-space-32);opacity:0;padding:var(--g-space-1) var(--g-space-2);position:absolute;right:var(--tooltip-right);scale:0;text-align:center;top:100%;visibility:hidden;width:-moz-fit-content;width:fit-content;z-index:var(--g-z-max)}.c-readme-view .tooltip:hover:after,.c-realm-view .tooltip:hover:after{opacity:1;scale:1;transition-delay:var(--g-transition-fast);visibility:visible}.c-readme-view .tooltip:only-of-type,.c-realm-view .tooltip:only-of-type{margin-left:.3em;margin-right:.3em}.c-realm-view .tooltip:has(+span){margin-left:.3em}.c-readme-view .tooltip:has(+span){margin-left:.3em}.c-readme-view .link-external,.c-realm-view .link-external{font-size:.67em}.c-readme-view .link-internal,.c-realm-view .link-internal{font-size:.75em;font-weight:400}.c-readme-view .link-tx,.c-readme-view .link-user,.c-realm-view .link-tx,.c-realm-view .link-user{font-size:.75em}.c-realm-view ul:has(li>input[type=checkbox]:first-child){list-style:none;padding-left:0}.c-readme-view ul:has(li>input[type=checkbox]:first-child){list-style:none;padding-left:0}.c-realm-view li:has(>input[type=checkbox]:first-child){align-items:center;display:flex;gap:var(--g-space-2)}.c-readme-view li:has(>input[type=checkbox]:first-child){align-items:center;display:flex;gap:var(--g-space-2)}.c-readme-view li>input[type=checkbox]:first-child,.c-realm-view li>input[type=checkbox]:first-child{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:var(--s-border-secondary);border-radius:var(--s-rounded-sm);flex-shrink:0;height:var(--g-space-5);padding:0;position:relative;width:var(--g-space-5)}.c-readme-view li>input[type=checkbox]:first-child:disabled,.c-realm-view li>input[type=checkbox]:first-child:disabled{background-color:var(--s-color-bg-surface-primary);border-color:var(--s-color-border-tertiary);cursor:not-allowed}.c-readme-view li>input[type=checkbox]:first-child:disabled:after,.c-realm-view li>input[type=checkbox]:first-child:disabled:after{background-color:var(--s-color-bg-brand-default)}.c-readme-view li>input[type=checkbox]:first-child:checked:after,.c-realm-view li>input[type=checkbox]:first-child:checked:after{opacity:1}.c-readme-view li>input[type=checkbox]:first-child:after,.c-realm-view li>input[type=checkbox]:first-child:after{background-color:var(--s-color-bg-base);clip-path:polygon(25% 36%,43% 54%,76% 18%,89% 29%,44% 78%,13% 49%);content:"";height:var(--g-space-3);left:50%;margin:auto;opacity:0;position:absolute;top:50%;transform:translate(-50%,-50%);width:var(--g-space-3)}.c-readme-view .footnote-backref,.c-realm-view .footnote-backref{vertical-align:middle}.c-readme-view li[id^="fn:"],.c-realm-view li[id^="fn:"]{position:relative;z-index:var(--g-z-1)}.c-readme-view li[id^="fn:"]:before,.c-realm-view li[id^="fn:"]:before{background-color:var(--s-color-bg-brand-weak);border-radius:var(--s-rounded-sm);bottom:0;content:"";display:block;left:calc(var(--g-space-0-5)*-1);opacity:0;position:absolute;right:calc(var(--g-space-0-5)*-1);top:calc(var(--g-space-0-5)*-1);z-index:var(--g-z-min)}.c-readme-view li[id^="fn:"]:target:before,.c-realm-view li[id^="fn:"]:target:before{opacity:1;transition-delay:var(--g-duration-150);transition-duration:var(--g-duration-75)}.c-readme-view .gno-form,.c-realm-view .gno-form{border:var(--s-border-secondary);border-radius:var(--s-rounded);display:block;margin-bottom:var(--g-space-6);margin-top:var(--g-space-6)}.c-readme-view .gno-form input[type=submit],.c-realm-view .gno-form input[type=submit]{background-color:var(--s-color-bg-brand-default);border-color:var(--s-color-border-brand-default);border-radius:var(--s-rounded-sm);color:var(--s-color-text-base);cursor:pointer;margin-bottom:var(--g-space-2);margin-top:var(--g-space-4);width:100%}.c-readme-view .gno-form input[type=submit]:hover,.c-realm-view .gno-form input[type=submit]:hover{opacity:.9}.c-readme-view .gno-form .command,.c-realm-view .gno-form .command{background-color:var(--s-color-bg-base-dev);margin-top:var(--g-space-6);padding:var(--g-space-4)}.c-readme-view .gno-form .command .title,.c-realm-view .gno-form .command .title{font-size:var(--g-font-size-200);font-weight:var(--g-font-semibold);white-space:nowrap}.c-readme-view .gno-form .command>.b-code,.c-realm-view .gno-form .command>.b-code{background-color:var(--s-color-bg-base-dev)}.c-readme-view .gno-form .command>.b-code>pre,.c-realm-view .gno-form .command>.b-code>pre{background-color:var(--s-color-bg-base);margin-bottom:0}.c-readme-view .gno-form .command .c-between,.c-readme-view .gno-form .command .c-inline,.c-realm-view .gno-form .command .c-between,.c-realm-view .gno-form .command .c-inline{align-items:flex-start;flex-direction:column;gap:var(--g-space-2)}@media (min-width:calc(640 / 16 * 1rem)){.c-readme-view .gno-form .command .c-between,.c-readme-view .gno-form .command .c-inline,.c-realm-view .gno-form .command .c-between,.c-realm-view .gno-form .command .c-inline{align-items:center;flex-direction:row}}.c-readme-view .gno-form .command .c-between>*,.c-readme-view .gno-form .command .c-inline>*,.c-realm-view .gno-form .command .c-between>*,.c-realm-view .gno-form .command .c-inline>*{width:100%}@media (min-width:calc(640 / 16 * 1rem)){.c-readme-view .gno-form .command .c-between>*,.c-readme-view .gno-form .command .c-inline>*,.c-realm-view .gno-form .command .c-between>*,.c-realm-view .gno-form .command .c-inline>*{width:auto}}.c-readme-view .gno-form_header,.c-realm-view .gno-form_header{color:var(--s-color-text-tertiary);display:flex;font-size:var(--g-font-size-50);justify-content:space-between;margin-bottom:var(--g-space-6);padding:var(--g-space-2) var(--g-space-4) 0}.c-readme-view .gno-form_input,.c-readme-view .gno-form_select,.c-realm-view .gno-form_input,.c-realm-view .gno-form_select{padding-left:var(--g-space-4);padding-right:var(--g-space-4);position:relative}.c-readme-view .gno-form_input label,.c-readme-view .gno-form_select label,.c-realm-view .gno-form_input label,.c-realm-view .gno-form_select label{background-color:var(--s-color-bg-input);color:var(--s-color-text-tertiary);display:none;font-size:var(--g-font-size-50);left:var(--g-space-5);padding-left:var(--g-space-1);padding-right:var(--g-space-1);position:absolute;top:0;transform:translateY(-50%)}.c-readme-view .gno-form_input svg,.c-readme-view .gno-form_select svg,.c-realm-view .gno-form_input svg,.c-realm-view .gno-form_select svg{height:var(--g-space-4);pointer-events:none;position:absolute;right:var(--g-space-6);top:50%;transform:translateY(-50%);width:var(--g-space-4)}.c-realm-view .gno-form_input:has(input:focus) label{display:block}.c-readme-view .gno-form_input:has(input:focus) label{display:block}.c-realm-view .gno-form_input:has(input:not(:-moz-placeholder)) label{display:block}.c-realm-view .gno-form_input:has(input:not(:placeholder-shown)) label{display:block}.c-readme-view .gno-form_input:has(input:not(:-moz-placeholder)) label{display:block}.c-readme-view .gno-form_input:has(input:not(:placeholder-shown)) label{display:block}.c-realm-view .gno-form_input:has(textarea:not(:-moz-placeholder)) label{display:block}.c-realm-view .gno-form_input:has(textarea:not(:placeholder-shown)) label{display:block}.c-readme-view .gno-form_input:has(textarea:not(:-moz-placeholder)) label{display:block}.c-readme-view .gno-form_input:has(textarea:not(:placeholder-shown)) label{display:block}.c-realm-view .gno-form_input:has(textarea:focus) label{display:block}.c-readme-view .gno-form_input:has(textarea:focus) label{display:block}.c-realm-view .gno-form_select:has(select:focus) label{display:block}.c-readme-view .gno-form_select:has(select:focus) label{display:block}.c-realm-view .gno-form_select:has(select option:not([value=""]):checked) label{display:block}.c-readme-view .gno-form_select:has(select option:not([value=""]):checked) label{display:block}.c-readme-view .gno-form_input input,.c-readme-view .gno-form_input textarea,.c-readme-view .gno-form_select select,.c-realm-view .gno-form_input input,.c-realm-view .gno-form_input textarea,.c-realm-view .gno-form_select select{background-color:var(--s-color-bg-input);border:var(--g-space-px) solid var(--s-color-border-input);border-radius:var(--s-rounded-sm);color:var(--s-color-text-primary);display:block;margin-bottom:var(--g-space-4);margin-top:var(--g-space-4);outline:none;padding:var(--g-space-2);width:100%}.c-readme-view .gno-form_input input:focus,.c-readme-view .gno-form_input input:hover,.c-readme-view .gno-form_input textarea:focus,.c-readme-view .gno-form_input textarea:hover,.c-readme-view .gno-form_select select:focus,.c-readme-view .gno-form_select select:hover,.c-realm-view .gno-form_input input:focus,.c-realm-view .gno-form_input input:hover,.c-realm-view .gno-form_input textarea:focus,.c-realm-view .gno-form_input textarea:hover,.c-realm-view .gno-form_select select:focus,.c-realm-view .gno-form_select select:hover{border-color:var(--s-color-border-tertiary)}.c-realm-view .gno-form_input input::-moz-placeholder{color:var(--s-color-text-tertiary)}.c-realm-view .gno-form_input input::placeholder{color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_input input::-moz-placeholder{color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_input input::placeholder{color:var(--s-color-text-tertiary)}.c-realm-view .gno-form_input textarea::-moz-placeholder{color:var(--s-color-text-tertiary)}.c-realm-view .gno-form_input textarea::placeholder{color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_input textarea::-moz-placeholder{color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_input textarea::placeholder{color:var(--s-color-text-tertiary)}.c-realm-view .gno-form_select select::-moz-placeholder{color:var(--s-color-text-tertiary)}.c-realm-view .gno-form_select select::placeholder{color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_select select::-moz-placeholder{color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_select select::placeholder{color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_input input:disabled,.c-readme-view .gno-form_input input[readonly],.c-readme-view .gno-form_input textarea:disabled,.c-readme-view .gno-form_input textarea[readonly],.c-readme-view .gno-form_select select:disabled,.c-readme-view .gno-form_select select[readonly],.c-realm-view .gno-form_input input:disabled,.c-realm-view .gno-form_input input[readonly],.c-realm-view .gno-form_input textarea:disabled,.c-realm-view .gno-form_input textarea[readonly],.c-realm-view .gno-form_select select:disabled,.c-realm-view .gno-form_select select[readonly]{background-color:var(--s-color-bg-secondary);color:var(--s-color-text-tertiary);cursor:not-allowed}.c-readme-view .gno-form_input input:disabled:focus,.c-readme-view .gno-form_input input:disabled:hover,.c-readme-view .gno-form_input input[readonly]:focus,.c-readme-view .gno-form_input input[readonly]:hover,.c-readme-view .gno-form_input textarea:disabled:focus,.c-readme-view .gno-form_input textarea:disabled:hover,.c-readme-view .gno-form_input textarea[readonly]:focus,.c-readme-view .gno-form_input textarea[readonly]:hover,.c-readme-view .gno-form_select select:disabled:focus,.c-readme-view .gno-form_select select:disabled:hover,.c-readme-view .gno-form_select select[readonly]:focus,.c-readme-view .gno-form_select select[readonly]:hover,.c-realm-view .gno-form_input input:disabled:focus,.c-realm-view .gno-form_input input:disabled:hover,.c-realm-view .gno-form_input input[readonly]:focus,.c-realm-view .gno-form_input input[readonly]:hover,.c-realm-view .gno-form_input textarea:disabled:focus,.c-realm-view .gno-form_input textarea:disabled:hover,.c-realm-view .gno-form_input textarea[readonly]:focus,.c-realm-view .gno-form_input textarea[readonly]:hover,.c-realm-view .gno-form_select select:disabled:focus,.c-realm-view .gno-form_select select:disabled:hover,.c-realm-view .gno-form_select select[readonly]:focus,.c-realm-view .gno-form_select select[readonly]:hover{border-color:var(--s-color-border-secondary)}.c-realm-view .gno-form_input input:user-invalid{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input input:user-invalid{border-color:var(--s-color-border-error)}.c-realm-view .gno-form_input textarea:user-invalid{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input textarea:user-invalid{border-color:var(--s-color-border-error)}.c-realm-view .gno-form_select select:user-invalid{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_select select:user-invalid{border-color:var(--s-color-border-error)}.c-realm-view .gno-form_input input:user-invalid:focus{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input input:user-invalid:focus{border-color:var(--s-color-border-error)}.c-realm-view .gno-form_input textarea:user-invalid:focus{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input textarea:user-invalid:focus{border-color:var(--s-color-border-error)}.c-realm-view .gno-form_select select:user-invalid:focus{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_select select:user-invalid:focus{border-color:var(--s-color-border-error)}@supports not selector(:user-invalid){.c-realm-view .gno-form_input input:invalid:not(:-moz-placeholder):not(:focus){border-color:var(--s-color-border-error)}.c-realm-view .gno-form_input input:invalid:not(:placeholder-shown):not(:focus){border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input input:invalid:not(:-moz-placeholder):not(:focus){border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input input:invalid:not(:placeholder-shown):not(:focus){border-color:var(--s-color-border-error)}.c-realm-view .gno-form_input textarea:invalid:not(:-moz-placeholder):not(:focus){border-color:var(--s-color-border-error)}.c-realm-view .gno-form_input textarea:invalid:not(:placeholder-shown):not(:focus){border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input textarea:invalid:not(:-moz-placeholder):not(:focus){border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input textarea:invalid:not(:placeholder-shown):not(:focus){border-color:var(--s-color-border-error)}.c-realm-view .gno-form_select select:invalid:not(:-moz-placeholder):not(:focus){border-color:var(--s-color-border-error)}.c-realm-view .gno-form_select select:invalid:not(:placeholder-shown):not(:focus){border-color:var(--s-color-border-error)}.c-readme-view .gno-form_select select:invalid:not(:-moz-placeholder):not(:focus){border-color:var(--s-color-border-error)}.c-readme-view .gno-form_select select:invalid:not(:placeholder-shown):not(:focus){border-color:var(--s-color-border-error)}}.c-realm-view .gno-form_input input:focus::-moz-placeholder{opacity:0}.c-realm-view .gno-form_input input:focus::placeholder{opacity:0}.c-readme-view .gno-form_input input:focus::-moz-placeholder{opacity:0}.c-readme-view .gno-form_input input:focus::placeholder{opacity:0}.c-realm-view .gno-form_input textarea:focus::-moz-placeholder{opacity:0}.c-realm-view .gno-form_input textarea:focus::placeholder{opacity:0}.c-readme-view .gno-form_input textarea:focus::-moz-placeholder{opacity:0}.c-readme-view .gno-form_input textarea:focus::placeholder{opacity:0}.c-readme-view .gno-form textarea,.c-realm-view .gno-form textarea{resize:none}.c-realm-view .gno-form_select select:has(option[value=""]:checked){color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_select select:has(option[value=""]:checked){color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_description,.c-realm-view .gno-form_description{color:var(--s-color-text-secondary);font-weight:var(--g-font-semibold);margin-bottom:var(--g-space-2);margin-top:var(--g-space-5);padding-left:var(--g-space-4)}.c-readme-view .gno-form_info-badge,.c-realm-view .gno-form_info-badge{color:var(--s-color-text-tertiary);font-size:var(--g-font-size-50);font-weight:var(--g-font-normal);margin-left:var(--g-space-1)}.c-readme-view .gno-form_selectable,.c-realm-view .gno-form_selectable{-moz-column-gap:var(--g-space-2);column-gap:var(--g-space-2);display:flex;margin-bottom:var(--g-space-1);padding-left:var(--g-space-4);padding-right:var(--g-space-4)}.c-readme-view .gno-form_selectable input,.c-realm-view .gno-form_selectable input{width:auto}.c-readme-view .gno-form_selectable input[type=checkbox],.c-readme-view .gno-form_selectable input[type=radio],.c-realm-view .gno-form_selectable input[type=checkbox],.c-realm-view .gno-form_selectable input[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:var(--s-border);border-radius:var(--s-rounded-full);flex-shrink:0;height:var(--g-space-5);padding:0;position:relative;width:var(--g-space-5)}.c-readme-view .gno-form_selectable input[type=checkbox]:checked,.c-readme-view .gno-form_selectable input[type=radio]:checked,.c-realm-view .gno-form_selectable input[type=checkbox]:checked,.c-realm-view .gno-form_selectable input[type=radio]:checked{background-color:var(--s-color-bg-brand-default);border-color:transparent}.c-readme-view .gno-form_selectable input[type=checkbox]:checked:after,.c-readme-view .gno-form_selectable input[type=radio]:checked:after,.c-realm-view .gno-form_selectable input[type=checkbox]:checked:after,.c-realm-view .gno-form_selectable input[type=radio]:checked:after{opacity:1}.c-readme-view .gno-form_selectable input[type=checkbox]:focus,.c-readme-view .gno-form_selectable input[type=radio]:focus,.c-realm-view .gno-form_selectable input[type=checkbox]:focus,.c-realm-view .gno-form_selectable input[type=radio]:focus{outline:none}.c-readme-view .gno-form_selectable input[type=checkbox]:focus,.c-readme-view .gno-form_selectable input[type=checkbox]:hover,.c-readme-view .gno-form_selectable input[type=radio]:focus,.c-readme-view .gno-form_selectable input[type=radio]:hover,.c-realm-view .gno-form_selectable input[type=checkbox]:focus,.c-realm-view .gno-form_selectable input[type=checkbox]:hover,.c-realm-view .gno-form_selectable input[type=radio]:focus,.c-realm-view .gno-form_selectable input[type=radio]:hover{border-color:var(--s-color-border-tertiary);color:var(--s-color-text-brand-default)}.c-readme-view .gno-form_selectable input[type=checkbox]:focus+label,.c-readme-view .gno-form_selectable input[type=checkbox]:hover+label,.c-readme-view .gno-form_selectable input[type=radio]:focus+label,.c-readme-view .gno-form_selectable input[type=radio]:hover+label,.c-realm-view .gno-form_selectable input[type=checkbox]:focus+label,.c-realm-view .gno-form_selectable input[type=checkbox]:hover+label,.c-realm-view .gno-form_selectable input[type=radio]:focus+label,.c-realm-view .gno-form_selectable input[type=radio]:hover+label{color:var(--s-color-text-brand-default);-webkit-text-decoration:underline;text-decoration:underline}.c-readme-view .gno-form_selectable input[type=checkbox]:disabled,.c-readme-view .gno-form_selectable input[type=radio]:disabled,.c-realm-view .gno-form_selectable input[type=checkbox]:disabled,.c-realm-view .gno-form_selectable input[type=radio]:disabled{cursor:not-allowed;opacity:var(--g-opacity-50);-webkit-text-decoration:line-through;text-decoration:line-through}.c-readme-view .gno-form_selectable input[type=checkbox]:disabled+label,.c-readme-view .gno-form_selectable input[type=radio]:disabled+label,.c-realm-view .gno-form_selectable input[type=checkbox]:disabled+label,.c-realm-view .gno-form_selectable input[type=radio]:disabled+label{cursor:not-allowed;opacity:var(--g-opacity-50);-webkit-text-decoration:none;text-decoration:none}.c-realm-view .gno-form_selectable input[type=radio]:user-invalid{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_selectable input[type=radio]:user-invalid{border-color:var(--s-color-border-error)}.c-realm-view .gno-form_selectable input[type=checkbox]:user-invalid{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_selectable input[type=checkbox]:user-invalid{border-color:var(--s-color-border-error)}@supports not selector(:user-invalid){.c-realm-view .gno-form_selectable input[type=radio]:invalid{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_selectable input[type=radio]:invalid{border-color:var(--s-color-border-error)}.c-realm-view .gno-form_selectable input[type=checkbox]:invalid{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_selectable input[type=checkbox]:invalid{border-color:var(--s-color-border-error)}}.c-readme-view .gno-form_selectable input[type=radio]:after,.c-realm-view .gno-form_selectable input[type=radio]:after{background-color:var(--s-color-bg-brand-default);border:var(--s-border-secondary);border-radius:var(--s-rounded-full);border-width:calc(var(--g-space-px)*2);content:"";height:var(--g-space-4);left:50%;opacity:0;position:absolute;top:50%;transform:translate(-50%,-50%);width:var(--g-space-4)}.c-readme-view .gno-form_selectable input[type=checkbox],.c-realm-view .gno-form_selectable input[type=checkbox]{border-radius:var(--s-rounded-sm)}.c-readme-view .gno-form_selectable input[type=checkbox]:after,.c-realm-view .gno-form_selectable input[type=checkbox]:after{background-color:var(--s-color-bg-base);clip-path:polygon(25% 36%,43% 54%,76% 18%,89% 29%,44% 78%,13% 49%);content:"";height:var(--g-space-3);left:50%;margin:auto;opacity:0;position:absolute;top:50%;transform:translate(-50%,-50%);width:var(--g-space-3)}.c-readme-view .gno-form_selectable label,.c-realm-view .gno-form_selectable label{color:var(--s-color-text-secondary);cursor:pointer;display:block;position:relative}.c-readme-view .gno-form_selectable label:first-letter,.c-realm-view .gno-form_selectable label:first-letter{text-transform:capitalize}.c-readme-view .gno-alert,.c-realm-view .gno-alert{border-left:var(--g-space-1) solid var(--s-color-border-tertiary);border-radius:var(--s-rounded);color:var(--s-color-text-secondary);margin-bottom:var(--g-space-10);margin-top:var(--g-space-5);padding:var(--g-space-3) var(--g-space-4)}.c-readme-view .gno-alert>div>:first-child,.c-realm-view .gno-alert>div>:first-child{margin-top:var(--g-space-2)}.c-readme-view .gno-alert>div>:last-child,.c-realm-view .gno-alert>div>:last-child{margin-bottom:0}.c-readme-view .gno-alert>summary,.c-realm-view .gno-alert>summary{align-items:center;display:flex;font-weight:var(--g-font-bold);gap:var(--g-space-2);list-style:none;margin-bottom:var(--g-space-1);margin-top:var(--g-space-1)}.c-readme-view .gno-alert>summary svg,.c-realm-view .gno-alert>summary svg{height:var(--g-space-6);width:var(--g-space-6)}.c-readme-view .gno-alert>summary svg:last-of-type,.c-realm-view .gno-alert>summary svg:last-of-type{height:var(--g-space-4);margin-left:auto;transform:rotate(-90deg);width:var(--g-space-4)}.c-readme-view .gno-alert>summary a,.c-readme-view .gno-alert>summary svg,.c-realm-view .gno-alert>summary a,.c-realm-view .gno-alert>summary svg{color:inherit}.c-realm-view .gno-alert>summary::marker{display:none}.c-readme-view .gno-alert>summary::marker{display:none}.c-readme-view .gno-alert>summary::-webkit-details-marker,.c-realm-view .gno-alert>summary::-webkit-details-marker{display:none}.c-readme-view .gno-alert[open]>summary svg:last-of-type,.c-realm-view .gno-alert[open]>summary svg:last-of-type{transform:rotate(0)}.c-readme-view .gno-alert.gno-alert-info,.c-realm-view .gno-alert.gno-alert-info{background-color:color-mix(in srgb,var(--s-color-bg-info-default) 10%,transparent);border-left-color:var(--s-color-border-info);color:var(--s-color-text-info)}.c-readme-view .gno-alert.gno-alert-note,.c-realm-view .gno-alert.gno-alert-note{background-color:color-mix(in srgb,var(--s-color-bg-note-default) 10%,transparent);border-left-color:var(--s-color-border-note);color:var(--s-color-text-note)}.c-readme-view .gno-alert.gno-alert-success,.c-realm-view .gno-alert.gno-alert-success{background-color:color-mix(in srgb,var(--s-color-bg-success-default) 10%,transparent);border-left-color:var(--s-color-border-success);color:var(--s-color-text-success)}.c-readme-view .gno-alert.gno-alert-warning,.c-realm-view .gno-alert.gno-alert-warning{background-color:color-mix(in srgb,var(--s-color-bg-warning-default) 10%,transparent);border-left-color:var(--s-color-border-warning);color:var(--s-color-text-warning)}.c-readme-view .gno-alert.gno-alert-caution,.c-realm-view .gno-alert.gno-alert-caution{background-color:color-mix(in srgb,var(--s-color-bg-caution-default) 10%,transparent);border-left-color:var(--s-color-border-error);color:var(--s-color-text-caution)}.c-readme-view .gno-alert.gno-alert-tip,.c-realm-view .gno-alert.gno-alert-tip{background-color:color-mix(in srgb,var(--s-color-bg-tip-default) 10%,transparent);border-left-color:var(--s-color-border-tip);color:var(--s-color-text-tip)}.b-playground{display:flex;flex-direction:column;gap:var(--g-space-2);min-height:70vh}.b-playground-toolbar{flex-wrap:wrap;justify-content:space-between}.b-playground-toolbar,.b-playground-toolbar-left{align-items:center;display:flex;gap:var(--g-space-2)}.b-playground-toolbar-right{align-items:center;display:flex;gap:var(--g-space-1)}.b-playground-title{font-size:var(--g-font-size-200);font-weight:var(--g-font-semibold)}.b-playground-editor-area{border:1px solid var(--s-color-border-default);border-radius:var(--g-radius-2);display:flex;flex:1;flex-direction:column;min-height:40vh;overflow:hidden}.b-playground-tabs{align-items:center;background-color:var(--s-color-bg-muted);border-bottom:1px solid var(--s-color-border-default);display:flex;gap:0;overflow-x:auto}.b-playground-tab{background:none;border:none;border-bottom:2px solid transparent;color:var(--s-color-text-muted);cursor:pointer;font-family:var(--g-font-mono);font-size:var(--g-font-size-50);padding:var(--g-space-1) var(--g-space-2);white-space:nowrap}.b-playground-tab:hover{background-color:var(--s-color-bg-default);color:var(--s-color-text-base)}--active.b-playground-tab{border-bottom-color:var(--s-color-border-brand);color:var(--s-color-text-base);font-weight:var(--g-font-semibold)}.b-playground-tab-add{background:none;border:none;color:var(--s-color-text-muted);cursor:pointer;font-size:var(--g-font-size-100);padding:var(--g-space-1) var(--g-space-2)}.b-playground-tab-add:hover{color:var(--s-color-text-base)}.b-playground-editor{display:flex;flex:1}.b-playground-code{background-color:var(--s-color-bg-default);border:none;color:var(--s-color-text-base);flex:1;font-family:var(--g-font-mono);font-size:var(--g-font-size-75);line-height:1.6;min-height:300px;outline:none;padding:var(--g-space-2);resize:none;-moz-tab-size:4;-o-tab-size:4;tab-size:4;width:100%}.b-playground-output{border:1px solid var(--s-color-border-default);border-radius:var(--g-radius-2);overflow:hidden}.b-playground-output-header{align-items:center;background-color:var(--s-color-bg-muted);border-bottom:1px solid var(--s-color-border-default);display:flex;justify-content:space-between;padding:var(--g-space-1) var(--g-space-2)}.b-playground-output-title{font-size:var(--g-font-size-50);font-weight:var(--g-font-semibold);letter-spacing:.05em;text-transform:uppercase}.b-playground-output-clear{background:none;border:none;color:var(--s-color-text-muted);cursor:pointer;font-size:var(--g-font-size-50)}.b-playground-output-clear:hover{color:var(--s-color-text-base)}.b-playground-output-content{font-family:var(--g-font-mono);font-size:var(--g-font-size-75);line-height:1.6;margin:0;max-height:300px;min-height:80px;overflow-y:auto;padding:var(--g-space-2);white-space:pre-wrap;word-break:break-word}.b-eval{display:flex;flex-direction:column;gap:var(--g-space-3)}.b-eval-header{margin-bottom:var(--g-space-1)}.b-eval-form{align-items:flex-end;display:flex;gap:var(--g-space-2)}.b-eval-form .b-input--full{flex:1}.b-eval-result{border:1px solid var(--s-color-border-default);border-radius:var(--g-radius-2);overflow:hidden}.b-eval-result-header{align-items:center;background-color:var(--s-color-bg-muted);border-bottom:1px solid var(--s-color-border-default);display:flex;font-size:var(--g-font-size-50);font-weight:var(--g-font-semibold);justify-content:space-between;letter-spacing:.05em;padding:var(--g-space-1) var(--g-space-2);text-transform:uppercase}.b-eval-result-clear{background:none;border:none;color:var(--s-color-text-muted);cursor:pointer;font-size:var(--g-font-size-50)}.b-eval-result-clear:hover{color:var(--s-color-text-base)}.b-eval-result-content{font-family:var(--g-font-mono);font-size:var(--g-font-size-75);line-height:1.6;margin:0;max-height:400px;min-height:60px;overflow-y:auto;padding:var(--g-space-2);white-space:pre-wrap;word-break:break-word}.b-eval-func{align-items:center;display:flex;gap:var(--g-space-2)}.b-eval-history-entry{border:1px solid var(--s-color-border-default);border-radius:var(--g-radius-1);padding:var(--g-space-1-5)}.b-eval-history-entry+.b-eval-history-entry{margin-top:var(--g-space-1)}.b-eval-history-expr{align-items:center;display:flex;justify-content:space-between;margin-bottom:var(--g-space-1)}.b-eval-history-rerun{background:none;border:none;color:var(--s-color-text-muted);cursor:pointer;font-size:var(--g-font-size-100)}.b-eval-history-rerun:hover{color:var(--s-color-text-base)}.b-eval-history-result{background-color:var(--s-color-bg-muted);border-radius:var(--g-radius-1);font-family:var(--g-font-mono);font-size:var(--g-font-size-50);margin:0;padding:var(--g-space-1);white-space:pre-wrap;word-break:break-word}.u-hidden{display:none}.u-inline{display:inline}.u-sr-only{height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;clip:rect(0,0,0,0);border-width:0;white-space:nowrap}.u-color-valid{color:var(--s-color-bg-brand-default)}.u-color-danger{color:var(--s-color-text-caution)}.u-text-stroke{-webkit-text-stroke:currentColor;-webkit-text-stroke-width:.6px}.u-font-mono{font-family:var(--g-font-family-mono)}.u-capitalize{text-transform:capitalize}.u-text-center{text-align:center}.u-no-scrollbar::-webkit-scrollbar{display:none}.u-no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.u-is-loading{opacity:0}.u-icon-static{height:var(--g-space-4);width:var(--g-space-4)}.u-gap-0{gap:0}.u-mt-4{margin-top:var(--g-space-4)}.u-mb-0{margin-bottom:0}.u-mb-2{margin-bottom:var(--g-space-2)}@media (min-width:calc(820 / 16 * 1rem)){.u-lg-mb-4{margin-bottom:var(--g-space-4)}}.u-grid-full{grid-column:1/-1}
\ No newline at end of file
+ article[data-list-type-value=pure]{display:flex}.b-packages label:has(input:checked){color:var(--s-color-text-tertiary)}.b-packages label:has(input:checked):after{background-color:var(--s-color-bg-brand-default);border-top-left-radius:var(--s-rounded-sm);border-top-right-radius:var(--s-rounded-sm);bottom:calc(var(--g-space-1)*-2);content:"";height:var(--g-space-1);left:0;position:absolute;width:100%}.b-packages:has(input[value=realms]:checked) .range:not(:has(>[data-list-type-value=realm])):before{display:block}.b-packages:has(input[value=realms]:checked) .range:not(:has(>[data-list-type-value=realm])):after{display:block}.b-packages:has(input[value=realms]:checked) .range:not(:has(>[data-list-type-value=realm])):before{content:"No realms found"}.b-packages:has(input[value=realms]:checked) .range:not(:has(>[data-list-type-value=realm])):after{content:"Add a realm to your namespace to get started"}.b-packages:has(input[value=pures]:checked) .range:not(:has(>[data-list-type-value=pure])):before{display:block}.b-packages:has(input[value=pures]:checked) .range:not(:has(>[data-list-type-value=pure])):after{display:block}.b-packages:has(input[value=pures]:checked) .range:not(:has(>[data-list-type-value=pure])):before{content:"No pures found"}.b-packages:has(input[value=pures]:checked) .range:not(:has(>[data-list-type-value=pure])):after{content:"Add a pure to your namespace to get started"}@media (min-width:calc(640 / 16 * 1rem)){.b-packages:has(input[value=display-grid]:checked) .range{gap:var(--g-space-3);grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:calc(1020 / 16 * 1rem)){.b-packages:has(input[value=display-grid]:checked) .range{grid-template-columns:repeat(4,minmax(0,1fr))}}.b-packages:has(input[value=display-grid]:checked) .range article .article-content p{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden}.b-packages:has(input[value=display-list]:checked) .range article .article-content{display:flex;flex:none;flex-direction:row;gap:var(--g-space-2)}@media (min-width:calc(820 / 16 * 1rem)){.b-packages:has(input[value=display-list]:checked) .range article .article-content{flex:5;min-width:0}}.b-packages:has(input[value=display-list]:checked) .range article .article-content .title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:33.33333%}.b-packages:has(input[value=display-list]:checked) .range article .article-content p{white-space:nowrap}.b-packages:has(input[value=display-list]:checked) .range article footer{display:none;justify-content:center;min-width:0;padding-bottom:0;padding-left:0}@media (min-width:calc(640 / 16 * 1rem)){.b-packages:has(input[value=display-list]:checked) .range article footer{flex:1}}@media (min-width:calc(820 / 16 * 1rem)){.b-packages:has(input[value=display-list]:checked) .range article footer{display:flex}}.b-packages:has(input[value=display-list]:checked) .range article footer .size{display:none;min-width:0}@media (min-width:calc(1020 / 16 * 1rem)){.b-packages:has(input[value=display-list]:checked) .range article footer .size{display:block;flex:2}}.b-packages:has(input[value=display-list]:checked) .range article footer time{min-width:0}@media (min-width:calc(1020 / 16 * 1rem)){.b-packages:has(input[value=display-list]:checked) .range article footer time{flex:5}}.b-icon-action{flex-shrink:0;height:var(--g-space-5);width:var(--g-space-5)}.b-popup-bg,.b-popup-dialog{opacity:0;right:0;top:0;visibility:hidden;z-index:var(--g-z-max)}.b-popup-bg{align-items:center;bottom:0;display:flex;justify-content:center;left:0;position:fixed;right:0;top:0}.b-popup-dialog{position:absolute}.b-popup-dialog>.inner{background-color:var(--s-color-bg-base);border:var(--s-border-secondary);border-radius:var(--s-rounded);padding:var(--g-space-2-5) var(--g-space-4);position:absolute;transform:translateX(-100%);z-index:var(--g-z-max)}.b-popup-dialog>.inner>*+*{margin-top:var(--g-space-2-5)}.b-popup-dialog header{align-items:center;color:var(--s-color-text-secondary);display:flex;justify-content:space-between;width:100%}.b-popup-dialog header>svg{color:var(--s-color-text-tertiary);cursor:pointer;position:absolute;right:var(--g-space-3)}.b-popup-dialog header>svg>svg:hover{color:var(--s-color-text-primary)}.b-popup:checked+.b-popup-bg,.b-popup:checked~.b-popup-dialog{opacity:1;visibility:visible}.b-tag,.b-tag--secondary{align-items:center;border:var(--s-border);border-radius:var(--s-rounded-full);color:var(--s-color-text-secondary);display:flex;font-family:var(--g-font-family-mono);font-size:var(--g-font-size-50);gap:var(--g-space-2);padding:var(--g-space-px) var(--g-space-2)}.b-tag--secondary{background-color:var(--s-color-bg-surface-primary);border-color:transparent;color:var(--s-color-text-primary)}.c-readme-view .gno-columns,.c-realm-view .gno-columns{display:flex;flex-wrap:wrap;gap:var(--g-space-10)}@media (min-width:calc(1366 / 16 * 1rem)){.c-readme-view .gno-columns,.c-realm-view .gno-columns{gap:var(--g-space-12)}}.c-readme-view .gno-columns>*,.c-realm-view .gno-columns>*{flex-basis:var(--g-space-52);flex-grow:1;flex-shrink:1}@media (min-width:calc(820 / 16 * 1rem)){.c-readme-view .gno-columns>*,.c-realm-view .gno-columns>*{flex-basis:var(--g-space-44)}}.c-readme-view .tooltip,.c-realm-view .tooltip{--tooltip-left:0;--tooltip-right:initial;align-items:center;border:var(--s-border);border-radius:var(--s-rounded-full);color:var(--s-color-text-tertiary);display:inline-flex;height:var(--g-space-4);justify-content:center;margin-bottom:var(--g-space-px);position:relative;width:var(--g-space-4)}.c-readme-view .tooltip>svg,.c-realm-view .tooltip>svg{height:var(--g-space-3);width:var(--g-space-3)}.c-readme-view .tooltip:after,.c-realm-view .tooltip:after{background-color:var(--s-color-bg-base);border:var(--s-border-secondary);border-radius:var(--s-rounded);color:var(--s-color-text-secondary);content:attr(data-tooltip);font-size:var(--g-font-size-100);font-weight:var(--g-font-normal);left:var(--tooltip-left);max-width:var(--g-space-48);min-width:var(--g-space-32);opacity:0;padding:var(--g-space-1) var(--g-space-2);position:absolute;right:var(--tooltip-right);scale:0;text-align:center;top:100%;visibility:hidden;width:-moz-fit-content;width:fit-content;z-index:var(--g-z-max)}.c-readme-view .tooltip:hover:after,.c-realm-view .tooltip:hover:after{opacity:1;scale:1;transition-delay:var(--g-transition-fast);visibility:visible}.c-readme-view .tooltip:only-of-type,.c-realm-view .tooltip:only-of-type{margin-left:.3em;margin-right:.3em}.c-realm-view .tooltip:has(+span){margin-left:.3em}.c-readme-view .tooltip:has(+span){margin-left:.3em}.c-readme-view .link-external,.c-realm-view .link-external{font-size:.67em}.c-readme-view .link-internal,.c-realm-view .link-internal{font-size:.75em;font-weight:400}.c-readme-view .link-tx,.c-readme-view .link-user,.c-realm-view .link-tx,.c-realm-view .link-user{font-size:.75em}.c-realm-view ul:has(li>input[type=checkbox]:first-child){list-style:none;padding-left:0}.c-readme-view ul:has(li>input[type=checkbox]:first-child){list-style:none;padding-left:0}.c-realm-view li:has(>input[type=checkbox]:first-child){align-items:center;display:flex;gap:var(--g-space-2)}.c-readme-view li:has(>input[type=checkbox]:first-child){align-items:center;display:flex;gap:var(--g-space-2)}.c-readme-view li>input[type=checkbox]:first-child,.c-realm-view li>input[type=checkbox]:first-child{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:var(--s-border-secondary);border-radius:var(--s-rounded-sm);flex-shrink:0;height:var(--g-space-5);padding:0;position:relative;width:var(--g-space-5)}.c-readme-view li>input[type=checkbox]:first-child:disabled,.c-realm-view li>input[type=checkbox]:first-child:disabled{background-color:var(--s-color-bg-surface-primary);border-color:var(--s-color-border-tertiary);cursor:not-allowed}.c-readme-view li>input[type=checkbox]:first-child:disabled:after,.c-realm-view li>input[type=checkbox]:first-child:disabled:after{background-color:var(--s-color-bg-brand-default)}.c-readme-view li>input[type=checkbox]:first-child:checked:after,.c-realm-view li>input[type=checkbox]:first-child:checked:after{opacity:1}.c-readme-view li>input[type=checkbox]:first-child:after,.c-realm-view li>input[type=checkbox]:first-child:after{background-color:var(--s-color-bg-base);clip-path:polygon(25% 36%,43% 54%,76% 18%,89% 29%,44% 78%,13% 49%);content:"";height:var(--g-space-3);left:50%;margin:auto;opacity:0;position:absolute;top:50%;transform:translate(-50%,-50%);width:var(--g-space-3)}.c-readme-view .footnote-backref,.c-realm-view .footnote-backref{vertical-align:middle}.c-readme-view li[id^="fn:"],.c-realm-view li[id^="fn:"]{position:relative;z-index:var(--g-z-1)}.c-readme-view li[id^="fn:"]:before,.c-realm-view li[id^="fn:"]:before{background-color:var(--s-color-bg-brand-weak);border-radius:var(--s-rounded-sm);bottom:0;content:"";display:block;left:calc(var(--g-space-0-5)*-1);opacity:0;position:absolute;right:calc(var(--g-space-0-5)*-1);top:calc(var(--g-space-0-5)*-1);z-index:var(--g-z-min)}.c-readme-view li[id^="fn:"]:target:before,.c-realm-view li[id^="fn:"]:target:before{opacity:1;transition-delay:var(--g-duration-150);transition-duration:var(--g-duration-75)}.c-readme-view .gno-form,.c-realm-view .gno-form{border:var(--s-border-secondary);border-radius:var(--s-rounded);display:block;margin-bottom:var(--g-space-6);margin-top:var(--g-space-6)}.c-readme-view .gno-form input[type=submit],.c-realm-view .gno-form input[type=submit]{background-color:var(--s-color-bg-brand-default);border-color:var(--s-color-border-brand-default);border-radius:var(--s-rounded-sm);color:var(--s-color-text-base);cursor:pointer;margin-bottom:var(--g-space-2);margin-top:var(--g-space-4);width:100%}.c-readme-view .gno-form input[type=submit]:hover,.c-realm-view .gno-form input[type=submit]:hover{opacity:.9}.c-readme-view .gno-form .command,.c-realm-view .gno-form .command{background-color:var(--s-color-bg-base-dev);margin-top:var(--g-space-6);padding:var(--g-space-4)}.c-readme-view .gno-form .command .title,.c-realm-view .gno-form .command .title{font-size:var(--g-font-size-200);font-weight:var(--g-font-semibold);white-space:nowrap}.c-readme-view .gno-form .command>.b-code,.c-realm-view .gno-form .command>.b-code{background-color:var(--s-color-bg-base-dev)}.c-readme-view .gno-form .command>.b-code>pre,.c-realm-view .gno-form .command>.b-code>pre{background-color:var(--s-color-bg-base);margin-bottom:0}.c-readme-view .gno-form .command .c-between,.c-readme-view .gno-form .command .c-inline,.c-realm-view .gno-form .command .c-between,.c-realm-view .gno-form .command .c-inline{align-items:flex-start;flex-direction:column;gap:var(--g-space-2)}@media (min-width:calc(640 / 16 * 1rem)){.c-readme-view .gno-form .command .c-between,.c-readme-view .gno-form .command .c-inline,.c-realm-view .gno-form .command .c-between,.c-realm-view .gno-form .command .c-inline{align-items:center;flex-direction:row}}.c-readme-view .gno-form .command .c-between>*,.c-readme-view .gno-form .command .c-inline>*,.c-realm-view .gno-form .command .c-between>*,.c-realm-view .gno-form .command .c-inline>*{width:100%}@media (min-width:calc(640 / 16 * 1rem)){.c-readme-view .gno-form .command .c-between>*,.c-readme-view .gno-form .command .c-inline>*,.c-realm-view .gno-form .command .c-between>*,.c-realm-view .gno-form .command .c-inline>*{width:auto}}.c-readme-view .gno-form_header,.c-realm-view .gno-form_header{color:var(--s-color-text-tertiary);display:flex;font-size:var(--g-font-size-50);justify-content:space-between;margin-bottom:var(--g-space-6);padding:var(--g-space-2) var(--g-space-4) 0}.c-readme-view .gno-form_input,.c-readme-view .gno-form_select,.c-realm-view .gno-form_input,.c-realm-view .gno-form_select{padding-left:var(--g-space-4);padding-right:var(--g-space-4);position:relative}.c-readme-view .gno-form_input label,.c-readme-view .gno-form_select label,.c-realm-view .gno-form_input label,.c-realm-view .gno-form_select label{background-color:var(--s-color-bg-input);color:var(--s-color-text-tertiary);display:none;font-size:var(--g-font-size-50);left:var(--g-space-5);padding-left:var(--g-space-1);padding-right:var(--g-space-1);position:absolute;top:0;transform:translateY(-50%)}.c-readme-view .gno-form_input svg,.c-readme-view .gno-form_select svg,.c-realm-view .gno-form_input svg,.c-realm-view .gno-form_select svg{height:var(--g-space-4);pointer-events:none;position:absolute;right:var(--g-space-6);top:50%;transform:translateY(-50%);width:var(--g-space-4)}.c-realm-view .gno-form_input:has(input:focus) label{display:block}.c-readme-view .gno-form_input:has(input:focus) label{display:block}.c-realm-view .gno-form_input:has(input:not(:-moz-placeholder)) label{display:block}.c-realm-view .gno-form_input:has(input:not(:placeholder-shown)) label{display:block}.c-readme-view .gno-form_input:has(input:not(:-moz-placeholder)) label{display:block}.c-readme-view .gno-form_input:has(input:not(:placeholder-shown)) label{display:block}.c-realm-view .gno-form_input:has(textarea:not(:-moz-placeholder)) label{display:block}.c-realm-view .gno-form_input:has(textarea:not(:placeholder-shown)) label{display:block}.c-readme-view .gno-form_input:has(textarea:not(:-moz-placeholder)) label{display:block}.c-readme-view .gno-form_input:has(textarea:not(:placeholder-shown)) label{display:block}.c-realm-view .gno-form_input:has(textarea:focus) label{display:block}.c-readme-view .gno-form_input:has(textarea:focus) label{display:block}.c-realm-view .gno-form_select:has(select:focus) label{display:block}.c-readme-view .gno-form_select:has(select:focus) label{display:block}.c-realm-view .gno-form_select:has(select option:not([value=""]):checked) label{display:block}.c-readme-view .gno-form_select:has(select option:not([value=""]):checked) label{display:block}.c-readme-view .gno-form_input input,.c-readme-view .gno-form_input textarea,.c-readme-view .gno-form_select select,.c-realm-view .gno-form_input input,.c-realm-view .gno-form_input textarea,.c-realm-view .gno-form_select select{background-color:var(--s-color-bg-input);border:var(--g-space-px) solid var(--s-color-border-input);border-radius:var(--s-rounded-sm);color:var(--s-color-text-primary);display:block;margin-bottom:var(--g-space-4);margin-top:var(--g-space-4);outline:none;padding:var(--g-space-2);width:100%}.c-readme-view .gno-form_input input:focus,.c-readme-view .gno-form_input input:hover,.c-readme-view .gno-form_input textarea:focus,.c-readme-view .gno-form_input textarea:hover,.c-readme-view .gno-form_select select:focus,.c-readme-view .gno-form_select select:hover,.c-realm-view .gno-form_input input:focus,.c-realm-view .gno-form_input input:hover,.c-realm-view .gno-form_input textarea:focus,.c-realm-view .gno-form_input textarea:hover,.c-realm-view .gno-form_select select:focus,.c-realm-view .gno-form_select select:hover{border-color:var(--s-color-border-tertiary)}.c-realm-view .gno-form_input input::-moz-placeholder{color:var(--s-color-text-tertiary)}.c-realm-view .gno-form_input input::placeholder{color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_input input::-moz-placeholder{color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_input input::placeholder{color:var(--s-color-text-tertiary)}.c-realm-view .gno-form_input textarea::-moz-placeholder{color:var(--s-color-text-tertiary)}.c-realm-view .gno-form_input textarea::placeholder{color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_input textarea::-moz-placeholder{color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_input textarea::placeholder{color:var(--s-color-text-tertiary)}.c-realm-view .gno-form_select select::-moz-placeholder{color:var(--s-color-text-tertiary)}.c-realm-view .gno-form_select select::placeholder{color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_select select::-moz-placeholder{color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_select select::placeholder{color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_input input:disabled,.c-readme-view .gno-form_input input[readonly],.c-readme-view .gno-form_input textarea:disabled,.c-readme-view .gno-form_input textarea[readonly],.c-readme-view .gno-form_select select:disabled,.c-readme-view .gno-form_select select[readonly],.c-realm-view .gno-form_input input:disabled,.c-realm-view .gno-form_input input[readonly],.c-realm-view .gno-form_input textarea:disabled,.c-realm-view .gno-form_input textarea[readonly],.c-realm-view .gno-form_select select:disabled,.c-realm-view .gno-form_select select[readonly]{background-color:var(--s-color-bg-secondary);color:var(--s-color-text-tertiary);cursor:not-allowed}.c-readme-view .gno-form_input input:disabled:focus,.c-readme-view .gno-form_input input:disabled:hover,.c-readme-view .gno-form_input input[readonly]:focus,.c-readme-view .gno-form_input input[readonly]:hover,.c-readme-view .gno-form_input textarea:disabled:focus,.c-readme-view .gno-form_input textarea:disabled:hover,.c-readme-view .gno-form_input textarea[readonly]:focus,.c-readme-view .gno-form_input textarea[readonly]:hover,.c-readme-view .gno-form_select select:disabled:focus,.c-readme-view .gno-form_select select:disabled:hover,.c-readme-view .gno-form_select select[readonly]:focus,.c-readme-view .gno-form_select select[readonly]:hover,.c-realm-view .gno-form_input input:disabled:focus,.c-realm-view .gno-form_input input:disabled:hover,.c-realm-view .gno-form_input input[readonly]:focus,.c-realm-view .gno-form_input input[readonly]:hover,.c-realm-view .gno-form_input textarea:disabled:focus,.c-realm-view .gno-form_input textarea:disabled:hover,.c-realm-view .gno-form_input textarea[readonly]:focus,.c-realm-view .gno-form_input textarea[readonly]:hover,.c-realm-view .gno-form_select select:disabled:focus,.c-realm-view .gno-form_select select:disabled:hover,.c-realm-view .gno-form_select select[readonly]:focus,.c-realm-view .gno-form_select select[readonly]:hover{border-color:var(--s-color-border-secondary)}.c-realm-view .gno-form_input input:user-invalid{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input input:user-invalid{border-color:var(--s-color-border-error)}.c-realm-view .gno-form_input textarea:user-invalid{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input textarea:user-invalid{border-color:var(--s-color-border-error)}.c-realm-view .gno-form_select select:user-invalid{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_select select:user-invalid{border-color:var(--s-color-border-error)}.c-realm-view .gno-form_input input:user-invalid:focus{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input input:user-invalid:focus{border-color:var(--s-color-border-error)}.c-realm-view .gno-form_input textarea:user-invalid:focus{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input textarea:user-invalid:focus{border-color:var(--s-color-border-error)}.c-realm-view .gno-form_select select:user-invalid:focus{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_select select:user-invalid:focus{border-color:var(--s-color-border-error)}@supports not selector(:user-invalid){.c-realm-view .gno-form_input input:invalid:not(:-moz-placeholder):not(:focus){border-color:var(--s-color-border-error)}.c-realm-view .gno-form_input input:invalid:not(:placeholder-shown):not(:focus){border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input input:invalid:not(:-moz-placeholder):not(:focus){border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input input:invalid:not(:placeholder-shown):not(:focus){border-color:var(--s-color-border-error)}.c-realm-view .gno-form_input textarea:invalid:not(:-moz-placeholder):not(:focus){border-color:var(--s-color-border-error)}.c-realm-view .gno-form_input textarea:invalid:not(:placeholder-shown):not(:focus){border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input textarea:invalid:not(:-moz-placeholder):not(:focus){border-color:var(--s-color-border-error)}.c-readme-view .gno-form_input textarea:invalid:not(:placeholder-shown):not(:focus){border-color:var(--s-color-border-error)}.c-realm-view .gno-form_select select:invalid:not(:-moz-placeholder):not(:focus){border-color:var(--s-color-border-error)}.c-realm-view .gno-form_select select:invalid:not(:placeholder-shown):not(:focus){border-color:var(--s-color-border-error)}.c-readme-view .gno-form_select select:invalid:not(:-moz-placeholder):not(:focus){border-color:var(--s-color-border-error)}.c-readme-view .gno-form_select select:invalid:not(:placeholder-shown):not(:focus){border-color:var(--s-color-border-error)}}.c-realm-view .gno-form_input input:focus::-moz-placeholder{opacity:0}.c-realm-view .gno-form_input input:focus::placeholder{opacity:0}.c-readme-view .gno-form_input input:focus::-moz-placeholder{opacity:0}.c-readme-view .gno-form_input input:focus::placeholder{opacity:0}.c-realm-view .gno-form_input textarea:focus::-moz-placeholder{opacity:0}.c-realm-view .gno-form_input textarea:focus::placeholder{opacity:0}.c-readme-view .gno-form_input textarea:focus::-moz-placeholder{opacity:0}.c-readme-view .gno-form_input textarea:focus::placeholder{opacity:0}.c-readme-view .gno-form textarea,.c-realm-view .gno-form textarea{resize:none}.c-realm-view .gno-form_select select:has(option[value=""]:checked){color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_select select:has(option[value=""]:checked){color:var(--s-color-text-tertiary)}.c-readme-view .gno-form_description,.c-realm-view .gno-form_description{color:var(--s-color-text-secondary);font-weight:var(--g-font-semibold);margin-bottom:var(--g-space-2);margin-top:var(--g-space-5);padding-left:var(--g-space-4)}.c-readme-view .gno-form_info-badge,.c-realm-view .gno-form_info-badge{color:var(--s-color-text-tertiary);font-size:var(--g-font-size-50);font-weight:var(--g-font-normal);margin-left:var(--g-space-1)}.c-readme-view .gno-form_selectable,.c-realm-view .gno-form_selectable{-moz-column-gap:var(--g-space-2);column-gap:var(--g-space-2);display:flex;margin-bottom:var(--g-space-1);padding-left:var(--g-space-4);padding-right:var(--g-space-4)}.c-readme-view .gno-form_selectable input,.c-realm-view .gno-form_selectable input{width:auto}.c-readme-view .gno-form_selectable input[type=checkbox],.c-readme-view .gno-form_selectable input[type=radio],.c-realm-view .gno-form_selectable input[type=checkbox],.c-realm-view .gno-form_selectable input[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:var(--s-border);border-radius:var(--s-rounded-full);flex-shrink:0;height:var(--g-space-5);padding:0;position:relative;width:var(--g-space-5)}.c-readme-view .gno-form_selectable input[type=checkbox]:checked,.c-readme-view .gno-form_selectable input[type=radio]:checked,.c-realm-view .gno-form_selectable input[type=checkbox]:checked,.c-realm-view .gno-form_selectable input[type=radio]:checked{background-color:var(--s-color-bg-brand-default);border-color:transparent}.c-readme-view .gno-form_selectable input[type=checkbox]:checked:after,.c-readme-view .gno-form_selectable input[type=radio]:checked:after,.c-realm-view .gno-form_selectable input[type=checkbox]:checked:after,.c-realm-view .gno-form_selectable input[type=radio]:checked:after{opacity:1}.c-readme-view .gno-form_selectable input[type=checkbox]:focus,.c-readme-view .gno-form_selectable input[type=radio]:focus,.c-realm-view .gno-form_selectable input[type=checkbox]:focus,.c-realm-view .gno-form_selectable input[type=radio]:focus{outline:none}.c-readme-view .gno-form_selectable input[type=checkbox]:focus,.c-readme-view .gno-form_selectable input[type=checkbox]:hover,.c-readme-view .gno-form_selectable input[type=radio]:focus,.c-readme-view .gno-form_selectable input[type=radio]:hover,.c-realm-view .gno-form_selectable input[type=checkbox]:focus,.c-realm-view .gno-form_selectable input[type=checkbox]:hover,.c-realm-view .gno-form_selectable input[type=radio]:focus,.c-realm-view .gno-form_selectable input[type=radio]:hover{border-color:var(--s-color-border-tertiary);color:var(--s-color-text-brand-default)}.c-readme-view .gno-form_selectable input[type=checkbox]:focus+label,.c-readme-view .gno-form_selectable input[type=checkbox]:hover+label,.c-readme-view .gno-form_selectable input[type=radio]:focus+label,.c-readme-view .gno-form_selectable input[type=radio]:hover+label,.c-realm-view .gno-form_selectable input[type=checkbox]:focus+label,.c-realm-view .gno-form_selectable input[type=checkbox]:hover+label,.c-realm-view .gno-form_selectable input[type=radio]:focus+label,.c-realm-view .gno-form_selectable input[type=radio]:hover+label{color:var(--s-color-text-brand-default);-webkit-text-decoration:underline;text-decoration:underline}.c-readme-view .gno-form_selectable input[type=checkbox]:disabled,.c-readme-view .gno-form_selectable input[type=radio]:disabled,.c-realm-view .gno-form_selectable input[type=checkbox]:disabled,.c-realm-view .gno-form_selectable input[type=radio]:disabled{cursor:not-allowed;opacity:var(--g-opacity-50);-webkit-text-decoration:line-through;text-decoration:line-through}.c-readme-view .gno-form_selectable input[type=checkbox]:disabled+label,.c-readme-view .gno-form_selectable input[type=radio]:disabled+label,.c-realm-view .gno-form_selectable input[type=checkbox]:disabled+label,.c-realm-view .gno-form_selectable input[type=radio]:disabled+label{cursor:not-allowed;opacity:var(--g-opacity-50);-webkit-text-decoration:none;text-decoration:none}.c-realm-view .gno-form_selectable input[type=radio]:user-invalid{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_selectable input[type=radio]:user-invalid{border-color:var(--s-color-border-error)}.c-realm-view .gno-form_selectable input[type=checkbox]:user-invalid{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_selectable input[type=checkbox]:user-invalid{border-color:var(--s-color-border-error)}@supports not selector(:user-invalid){.c-realm-view .gno-form_selectable input[type=radio]:invalid{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_selectable input[type=radio]:invalid{border-color:var(--s-color-border-error)}.c-realm-view .gno-form_selectable input[type=checkbox]:invalid{border-color:var(--s-color-border-error)}.c-readme-view .gno-form_selectable input[type=checkbox]:invalid{border-color:var(--s-color-border-error)}}.c-readme-view .gno-form_selectable input[type=radio]:after,.c-realm-view .gno-form_selectable input[type=radio]:after{background-color:var(--s-color-bg-brand-default);border:var(--s-border-secondary);border-radius:var(--s-rounded-full);border-width:calc(var(--g-space-px)*2);content:"";height:var(--g-space-4);left:50%;opacity:0;position:absolute;top:50%;transform:translate(-50%,-50%);width:var(--g-space-4)}.c-readme-view .gno-form_selectable input[type=checkbox],.c-realm-view .gno-form_selectable input[type=checkbox]{border-radius:var(--s-rounded-sm)}.c-readme-view .gno-form_selectable input[type=checkbox]:after,.c-realm-view .gno-form_selectable input[type=checkbox]:after{background-color:var(--s-color-bg-base);clip-path:polygon(25% 36%,43% 54%,76% 18%,89% 29%,44% 78%,13% 49%);content:"";height:var(--g-space-3);left:50%;margin:auto;opacity:0;position:absolute;top:50%;transform:translate(-50%,-50%);width:var(--g-space-3)}.c-readme-view .gno-form_selectable label,.c-realm-view .gno-form_selectable label{color:var(--s-color-text-secondary);cursor:pointer;display:block;position:relative}.c-readme-view .gno-form_selectable label:first-letter,.c-realm-view .gno-form_selectable label:first-letter{text-transform:capitalize}.c-readme-view .gno-alert,.c-realm-view .gno-alert{border-left:var(--g-space-1) solid var(--s-color-border-tertiary);border-radius:var(--s-rounded);color:var(--s-color-text-secondary);margin-bottom:var(--g-space-10);margin-top:var(--g-space-5);padding:var(--g-space-3) var(--g-space-4)}.c-readme-view .gno-alert>div>:first-child,.c-realm-view .gno-alert>div>:first-child{margin-top:var(--g-space-2)}.c-readme-view .gno-alert>div>:last-child,.c-realm-view .gno-alert>div>:last-child{margin-bottom:0}.c-readme-view .gno-alert>summary,.c-realm-view .gno-alert>summary{align-items:center;display:flex;font-weight:var(--g-font-bold);gap:var(--g-space-2);list-style:none;margin-bottom:var(--g-space-1);margin-top:var(--g-space-1)}.c-readme-view .gno-alert>summary svg,.c-realm-view .gno-alert>summary svg{height:var(--g-space-6);width:var(--g-space-6)}.c-readme-view .gno-alert>summary svg:last-of-type,.c-realm-view .gno-alert>summary svg:last-of-type{height:var(--g-space-4);margin-left:auto;transform:rotate(-90deg);width:var(--g-space-4)}.c-readme-view .gno-alert>summary a,.c-readme-view .gno-alert>summary svg,.c-realm-view .gno-alert>summary a,.c-realm-view .gno-alert>summary svg{color:inherit}.c-realm-view .gno-alert>summary::marker{display:none}.c-readme-view .gno-alert>summary::marker{display:none}.c-readme-view .gno-alert>summary::-webkit-details-marker,.c-realm-view .gno-alert>summary::-webkit-details-marker{display:none}.c-readme-view .gno-alert[open]>summary svg:last-of-type,.c-realm-view .gno-alert[open]>summary svg:last-of-type{transform:rotate(0)}.c-readme-view .gno-alert.gno-alert-info,.c-realm-view .gno-alert.gno-alert-info{background-color:color-mix(in srgb,var(--s-color-bg-info-default) 10%,transparent);border-left-color:var(--s-color-border-info);color:var(--s-color-text-info)}.c-readme-view .gno-alert.gno-alert-note,.c-realm-view .gno-alert.gno-alert-note{background-color:color-mix(in srgb,var(--s-color-bg-note-default) 10%,transparent);border-left-color:var(--s-color-border-note);color:var(--s-color-text-note)}.c-readme-view .gno-alert.gno-alert-success,.c-realm-view .gno-alert.gno-alert-success{background-color:color-mix(in srgb,var(--s-color-bg-success-default) 10%,transparent);border-left-color:var(--s-color-border-success);color:var(--s-color-text-success)}.c-readme-view .gno-alert.gno-alert-warning,.c-realm-view .gno-alert.gno-alert-warning{background-color:color-mix(in srgb,var(--s-color-bg-warning-default) 10%,transparent);border-left-color:var(--s-color-border-warning);color:var(--s-color-text-warning)}.c-readme-view .gno-alert.gno-alert-caution,.c-realm-view .gno-alert.gno-alert-caution{background-color:color-mix(in srgb,var(--s-color-bg-caution-default) 10%,transparent);border-left-color:var(--s-color-border-error);color:var(--s-color-text-caution)}.c-readme-view .gno-alert.gno-alert-tip,.c-realm-view .gno-alert.gno-alert-tip{background-color:color-mix(in srgb,var(--s-color-bg-tip-default) 10%,transparent);border-left-color:var(--s-color-border-tip);color:var(--s-color-text-tip)}.b-playground{display:flex;flex-direction:column;gap:var(--g-space-2);grid-column:1/-1;min-height:70vh}.b-playground-toolbar{flex-wrap:wrap;justify-content:space-between}.b-playground-toolbar,.b-playground-toolbar-left{align-items:center;display:flex;gap:var(--g-space-2)}.b-playground-toolbar-right{align-items:center;display:flex;gap:var(--g-space-1)}.b-playground-title{font-size:var(--g-font-size-200);font-weight:var(--g-font-semibold)}.b-playground-editor-area{border:1px solid var(--s-color-border-default);border-radius:var(--g-radius-2);display:flex;flex:1;flex-direction:column;min-height:40vh;overflow:hidden}.b-playground-tabs{align-items:center;background-color:var(--s-color-bg-muted);border-bottom:1px solid var(--s-color-border-default);display:flex;gap:0;overflow-x:auto}.b-playground-tab{background:none;border:none;border-bottom:2px solid transparent;color:var(--s-color-text-muted);cursor:pointer;font-family:var(--g-font-mono);font-size:var(--g-font-size-50);padding:var(--g-space-1) var(--g-space-2);white-space:nowrap}.b-playground-tab:hover{background-color:var(--s-color-bg-default);color:var(--s-color-text-base)}--active.b-playground-tab{border-bottom-color:var(--s-color-border-brand);color:var(--s-color-text-base);font-weight:var(--g-font-semibold)}.b-playground-tab-add{background:none;border:none;color:var(--s-color-text-muted);cursor:pointer;font-size:var(--g-font-size-100);padding:var(--g-space-1) var(--g-space-2)}.b-playground-tab-add:hover{color:var(--s-color-text-base)}.b-playground-editor{display:flex;flex:1}.b-playground-code{background-color:var(--s-color-bg-default);border:none;color:var(--s-color-text-base);flex:1;font-family:var(--g-font-mono);font-size:var(--g-font-size-75);line-height:1.6;min-height:300px;outline:none;padding:var(--g-space-2);resize:none;-moz-tab-size:4;-o-tab-size:4;tab-size:4;width:100%}.b-playground-output{border:1px solid var(--s-color-border-default);border-radius:var(--g-radius-2);overflow:hidden}.b-playground-output-header{align-items:center;background-color:var(--s-color-bg-muted);border-bottom:1px solid var(--s-color-border-default);display:flex;justify-content:space-between;padding:var(--g-space-1) var(--g-space-2)}.b-playground-output-title{font-size:var(--g-font-size-50);font-weight:var(--g-font-semibold);letter-spacing:.05em;text-transform:uppercase}.b-playground-output-clear{background:none;border:none;color:var(--s-color-text-muted);cursor:pointer;font-size:var(--g-font-size-50)}.b-playground-output-clear:hover{color:var(--s-color-text-base)}.b-playground-output-content{font-family:var(--g-font-mono);font-size:var(--g-font-size-75);line-height:1.6;margin:0;max-height:300px;min-height:80px;overflow-y:auto;padding:var(--g-space-2);white-space:pre-wrap;word-break:break-word}.b-eval{display:flex;flex-direction:column;gap:var(--g-space-3);grid-column:1/-1}.b-eval-header{margin-bottom:var(--g-space-1)}.b-eval-form{align-items:flex-end;display:flex;gap:var(--g-space-2)}.b-eval-form .b-input--full{flex:1}.b-eval-result{border:1px solid var(--s-color-border-default);border-radius:var(--g-radius-2);overflow:hidden}.b-eval-result-header{align-items:center;background-color:var(--s-color-bg-muted);border-bottom:1px solid var(--s-color-border-default);display:flex;font-size:var(--g-font-size-50);font-weight:var(--g-font-semibold);justify-content:space-between;letter-spacing:.05em;padding:var(--g-space-1) var(--g-space-2);text-transform:uppercase}.b-eval-result-clear{background:none;border:none;color:var(--s-color-text-muted);cursor:pointer;font-size:var(--g-font-size-50)}.b-eval-result-clear:hover{color:var(--s-color-text-base)}.b-eval-result-content{font-family:var(--g-font-mono);font-size:var(--g-font-size-75);line-height:1.6;margin:0;max-height:400px;min-height:60px;overflow-y:auto;padding:var(--g-space-2);white-space:pre-wrap;word-break:break-word}.b-eval-func{align-items:center;display:flex;gap:var(--g-space-2)}.b-eval-history-entry{border:1px solid var(--s-color-border-default);border-radius:var(--g-radius-1);padding:var(--g-space-1-5)}.b-eval-history-entry+.b-eval-history-entry{margin-top:var(--g-space-1)}.b-eval-history-expr{align-items:center;display:flex;justify-content:space-between;margin-bottom:var(--g-space-1)}.b-eval-history-rerun{background:none;border:none;color:var(--s-color-text-muted);cursor:pointer;font-size:var(--g-font-size-100)}.b-eval-history-rerun:hover{color:var(--s-color-text-base)}.b-eval-history-result{background-color:var(--s-color-bg-muted);border-radius:var(--g-radius-1);font-family:var(--g-font-mono);font-size:var(--g-font-size-50);margin:0;padding:var(--g-space-1);white-space:pre-wrap;word-break:break-word}.u-hidden{display:none}.u-inline{display:inline}.u-sr-only{height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;clip:rect(0,0,0,0);border-width:0;white-space:nowrap}.u-color-valid{color:var(--s-color-bg-brand-default)}.u-color-danger{color:var(--s-color-text-caution)}.u-text-stroke{-webkit-text-stroke:currentColor;-webkit-text-stroke-width:.6px}.u-font-mono{font-family:var(--g-font-family-mono)}.u-capitalize{text-transform:capitalize}.u-text-center{text-align:center}.u-no-scrollbar::-webkit-scrollbar{display:none}.u-no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.u-is-loading{opacity:0}.u-icon-static{height:var(--g-space-4);width:var(--g-space-4)}.u-gap-0{gap:0}.u-mt-4{margin-top:var(--g-space-4)}.u-mb-0{margin-bottom:0}.u-mb-2{margin-bottom:var(--g-space-2)}@media (min-width:calc(820 / 16 * 1rem)){.u-lg-mb-4{margin-bottom:var(--g-space-4)}}.u-grid-full{grid-column:1/-1}
\ No newline at end of file
From a7abadf26c421857e16c2a301c98bbde557507d6 Mon Sep 17 00:00:00 2001
From: moul <94029+moul@users.noreply.github.com>
Date: Fri, 3 Apr 2026 19:35:06 +0000
Subject: [PATCH 03/40] fix(gnoweb): add cache busting to dynamic JS controller
imports
---
gno.land/pkg/gnoweb/frontend/js/index.ts | 13 ++++++++++++-
gno.land/pkg/gnoweb/public/js/index.js | 2 +-
2 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/gno.land/pkg/gnoweb/frontend/js/index.ts b/gno.land/pkg/gnoweb/frontend/js/index.ts
index f58505ff099..701c1096c39 100644
--- a/gno.land/pkg/gnoweb/frontend/js/index.ts
+++ b/gno.land/pkg/gnoweb/frontend/js/index.ts
@@ -7,6 +7,16 @@ declare const process: { env: { NODE_ENV: string } };
const CONTROLLER_PATH = "/public/js/controller-";
const modulePromises = new Map>>();
+ // Extract build version from this script's own URL for cache busting
+ const buildVersion = (() => {
+ const scripts = document.querySelectorAll('script[src*="index.js"]');
+ for (const s of scripts) {
+ const match = s.getAttribute("src")?.match(/[?&]v=([^&]+)/);
+ if (match) return match[1];
+ }
+ return "";
+ })();
+
// load one controller for a provided set of elements (no re-query)
const loadController = async (
controllerName: string,
@@ -26,7 +36,8 @@ declare const process: { env: { NODE_ENV: string } };
const pascal = camel.charAt(0).toUpperCase() + camel.slice(1);
// Only kebab-case file naming, prefixed with "controller-"
- const path = `${CONTROLLER_PATH}${kebab}.js`;
+ const vSuffix = buildVersion ? `?v=${buildVersion}` : "";
+ const path = `${CONTROLLER_PATH}${kebab}.js${vSuffix}`;
// import the controller module with promise cache (dedupe concurrent imports)
let modulePromise = modulePromises.get(path);
diff --git a/gno.land/pkg/gnoweb/public/js/index.js b/gno.land/pkg/gnoweb/public/js/index.js
index 79a0e82c0c0..cf820979f05 100644
--- a/gno.land/pkg/gnoweb/public/js/index.js
+++ b/gno.land/pkg/gnoweb/public/js/index.js
@@ -1 +1 @@
-function b(i){return i.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()}function M(i){return i=i.replace(/-([a-z])/g,(m,p)=>p.toUpperCase()),i.charAt(0).toLowerCase()+i.slice(1)}(()=>{let i="/public/js/controller-",m=new Map,p=async(e,o)=>{if(o.length===0)return;let l=b(e);if(!/^[a-z0-9-]+$/.test(l)){console.error(`\u274C Invalid controller name: ${e}`);return}let d=M(e),c=d.charAt(0).toUpperCase()+d.slice(1),a=`${i}${l}.js`,s=m.get(a);s||(s=import(a),m.set(a,s));let t;try{t=await s}catch(n){m.delete(a),console.error(`\u274C Failed to load ${a}:`,n);return}let r,f=t.default;if(typeof f=="function")r=/^\s*class\b/.test(Function.prototype.toString.call(f))?u=>new f(u):f;else{let n=t[`${c}Controller`];n&&(r=u=>new n(u))}if(typeof r!="function"){console.error(`\u274C Invalid controller export for ${e}. Expected default or named class "${c}Controller"`);return}let h=`data-controller-initialized-${l}`,g=o.filter(n=>!n.hasAttribute(h));g.length!==0&&g.forEach(n=>{try{r(n),n.setAttribute(h,"1")}catch(u){console.error(`\u274C Controller runtime error for ${e}:`,u,n)}})},L=e=>{let o=new Map;return e.querySelectorAll("[data-controller]").forEach(l=>{let d=l.getAttribute("data-controller");d&&d.split(/\s+/).filter(Boolean).forEach(c=>{let a=o.get(c)||[];a.push(l),o.set(c,a)})}),o},T=async()=>{let e=L(document);e.size!==0&&(await Promise.all(Array.from(e.entries()).map(([o,l])=>p(o,l))),document.dispatchEvent(new CustomEvent("controllers:ready",{detail:{names:Array.from(e.keys())}})))},v=()=>{let e=new Map,o=!1,l=()=>{if(o=!1,e.size===0)return;let s=[];for(let[t,r]of e)s.push(p(t,[...r]));e.clear(),Promise.all(s).catch(t=>console.error("Observer batch error:",t))},d=()=>{o||(o=!0,queueMicrotask(l))},c=s=>{if(!s.querySelector?.("[data-controller]"))return;let t=L(s);for(let[r,f]of t){let g=`data-controller-initialized-${b(r)}`,n=f.filter(E=>!E.hasAttribute(g));if(n.length===0)continue;let u=e.get(r)??new Set;n.forEach(E=>u.add(E)),e.set(r,u)}t.size&&d()};new MutationObserver(s=>{for(let t of s)t.type==="childList"?t.addedNodes.forEach(r=>{r.nodeType===1&&c(r)}):t.type==="attributes"&&t.target instanceof HTMLElement&&c(t.target)}).observe(document.documentElement,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["data-controller"]})};document.addEventListener("DOMContentLoaded",async()=>{await T(),v()})})();
+function b(i){return i.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()}function M(i){return i=i.replace(/-([a-z])/g,(g,h)=>h.toUpperCase()),i.charAt(0).toLowerCase()+i.slice(1)}(()=>{let i="/public/js/controller-",g=new Map,h=(()=>{let e=document.querySelectorAll('script[src*="index.js"]');for(let n of e){let o=n.getAttribute("src")?.match(/[?&]v=([^&]+)/);if(o)return o[1]}return""})(),v=async(e,n)=>{if(n.length===0)return;let o=b(e);if(!/^[a-z0-9-]+$/.test(o)){console.error(`\u274C Invalid controller name: ${e}`);return}let u=M(e),c=u.charAt(0).toUpperCase()+u.slice(1),f=h?`?v=${h}`:"",s=`${i}${o}.js${f}`,t=g.get(s);t||(t=import(s),g.set(s,t));let l;try{l=await t}catch(r){g.delete(s),console.error(`\u274C Failed to load ${s}:`,r);return}let d,m=l.default;if(typeof m=="function")d=/^\s*class\b/.test(Function.prototype.toString.call(m))?a=>new m(a):m;else{let r=l[`${c}Controller`];r&&(d=a=>new r(a))}if(typeof d!="function"){console.error(`\u274C Invalid controller export for ${e}. Expected default or named class "${c}Controller"`);return}let E=`data-controller-initialized-${o}`,p=n.filter(r=>!r.hasAttribute(E));p.length!==0&&p.forEach(r=>{try{d(r),r.setAttribute(E,"1")}catch(a){console.error(`\u274C Controller runtime error for ${e}:`,a,r)}})},L=e=>{let n=new Map;return e.querySelectorAll("[data-controller]").forEach(o=>{let u=o.getAttribute("data-controller");u&&u.split(/\s+/).filter(Boolean).forEach(c=>{let f=n.get(c)||[];f.push(o),n.set(c,f)})}),n},T=async()=>{let e=L(document);e.size!==0&&(await Promise.all(Array.from(e.entries()).map(([n,o])=>v(n,o))),document.dispatchEvent(new CustomEvent("controllers:ready",{detail:{names:Array.from(e.keys())}})))},w=()=>{let e=new Map,n=!1,o=()=>{if(n=!1,e.size===0)return;let s=[];for(let[t,l]of e)s.push(v(t,[...l]));e.clear(),Promise.all(s).catch(t=>console.error("Observer batch error:",t))},u=()=>{n||(n=!0,queueMicrotask(o))},c=s=>{if(!s.querySelector?.("[data-controller]"))return;let t=L(s);for(let[l,d]of t){let E=`data-controller-initialized-${b(l)}`,p=d.filter(a=>!a.hasAttribute(E));if(p.length===0)continue;let r=e.get(l)??new Set;p.forEach(a=>r.add(a)),e.set(l,r)}t.size&&u()};new MutationObserver(s=>{for(let t of s)t.type==="childList"?t.addedNodes.forEach(l=>{l.nodeType===1&&c(l)}):t.type==="attributes"&&t.target instanceof HTMLElement&&c(t.target)}).observe(document.documentElement,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["data-controller"]})};document.addEventListener("DOMContentLoaded",async()=>{await T(),w()})})();
From 1cbe351c9bdf53a04ae52419163ad9527a60e500 Mon Sep 17 00:00:00 2001
From: moul <94029+moul@users.noreply.github.com>
Date: Fri, 3 Apr 2026 20:00:42 +0000
Subject: [PATCH 04/40] fix(gnoweb): eval form submit via direct listener and
Enter key
---
.../pkg/gnoweb/frontend/js/controller-eval.ts | 17 +++++++++++++++++
.../pkg/gnoweb/public/js/controller-eval.js | 2 +-
2 files changed, 18 insertions(+), 1 deletion(-)
diff --git a/gno.land/pkg/gnoweb/frontend/js/controller-eval.ts b/gno.land/pkg/gnoweb/frontend/js/controller-eval.ts
index b5ec39139c0..e54b1d28e54 100644
--- a/gno.land/pkg/gnoweb/frontend/js/controller-eval.ts
+++ b/gno.land/pkg/gnoweb/frontend/js/controller-eval.ts
@@ -20,12 +20,29 @@ export class EvalController extends BaseController {
this._inputEl?.focus();
this._inputEl?.addEventListener("keydown", (e: KeyboardEvent) => {
+ // Up arrow = previous history
if (e.key === "ArrowUp" && this._history.length > 0) {
e.preventDefault();
this._inputEl.value =
this._history[this._history.length - 1].expression;
}
+ // Enter = submit
+ if (e.key === "Enter") {
+ e.preventDefault();
+ const expr = this._inputEl.value.trim();
+ if (expr) this._doEval(expr);
+ }
});
+
+ // Also intercept form submit directly (belt and suspenders)
+ const form = this.element.querySelector("form");
+ if (form) {
+ form.addEventListener("submit", (e: Event) => {
+ e.preventDefault();
+ const expr = this._inputEl.value.trim();
+ if (expr) this._doEval(expr);
+ });
+ }
}
public evalExpression(event: Event): void {
diff --git a/gno.land/pkg/gnoweb/public/js/controller-eval.js b/gno.land/pkg/gnoweb/public/js/controller-eval.js
index 2c7e3e78dce..991f33d2e20 100644
--- a/gno.land/pkg/gnoweb/public/js/controller-eval.js
+++ b/gno.land/pkg/gnoweb/public/js/controller-eval.js
@@ -1 +1 @@
-import{BaseController as h}from"./controller.js";var a=class extends h{_inputEl;_resultEl;_historyListEl;_history=[];connect(){this._inputEl=this.getTarget("input"),this._resultEl=this.getTarget("result"),this._historyListEl=this.getTarget("history-list"),this._inputEl?.focus(),this._inputEl?.addEventListener("keydown",e=>{e.key==="ArrowUp"&&this._history.length>0&&(e.preventDefault(),this._inputEl.value=this._history[this._history.length-1].expression)})}evalExpression(e){e.preventDefault();let t=this._inputEl.value.trim();t&&this._doEval(t)}quickCall(e){let t=e.params?.funcName,o=e.params?.funcSig;if(t){let i=o?.match(/\(([^)]*)\)/),s=i?i[1]:"";s?(this._inputEl.value=`${t}(${s.split(",").map(r=>{let n=r.trim().split(/\s+/);return n[n.length-1]==="string"?'""':"0"}).join(", ")})`,this._inputEl.focus(),this._inputEl.setSelectionRange(t.length+1,this._inputEl.value.length-1)):(this._inputEl.value=`${t}()`,this._doEval(`${t}()`))}}clearResult(){this._resultEl.textContent="// Enter an expression above",this._resultEl.classList.remove("u-color-danger")}async _doEval(e){let t=this.getValue("pkg-path"),o=this.getValue("domain");this._resultEl.textContent="Evaluating...",this._resultEl.classList.remove("u-color-danger");try{let i=t.startsWith(o+"/")?t.slice(o.length+1):t,s=await fetch("/_/api/eval",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pkg_path:i,expression:e})});if(!s.ok)throw new Error(`HTTP ${s.status}`);let r=await s.json(),n,l;r.error?(n=`Error: ${r.error}`,l=!0):(n=r.result||"(empty result)",l=!1),this._resultEl.textContent=n,this._resultEl.classList.toggle("u-color-danger",l),this._addToHistory(e,n,l)}catch(i){let s=`Error: ${i instanceof Error?i.message:String(i)}`;this._resultEl.textContent=s,this._resultEl.classList.add("u-color-danger"),this._addToHistory(e,s,!0)}}_addToHistory(e,t,o){if(this._history.push({expression:e,result:t,isError:o}),this._historyListEl){let i=document.createElement("div");i.className="b-eval-history-entry";let s=document.createElement("div");s.className="b-eval-history-expr";let r=document.createElement("code");r.className="u-font-mono",r.textContent=e,s.appendChild(r);let n=document.createElement("button");n.className="b-eval-history-rerun",n.textContent="\u21A9",n.title="Re-run",n.addEventListener("click",()=>{this._inputEl.value=e,this._doEval(e)}),s.appendChild(n);let l=document.createElement("pre");l.className=`b-eval-history-result${o?" u-color-danger":""}`,l.textContent=t.length>200?t.substring(0,200)+"...":t,i.appendChild(s),i.appendChild(l),this._historyListEl.prepend(i)}}};export{a as EvalController};
+import{BaseController as u}from"./controller.js";var a=class extends u{_inputEl;_resultEl;_historyListEl;_history=[];connect(){this._inputEl=this.getTarget("input"),this._resultEl=this.getTarget("result"),this._historyListEl=this.getTarget("history-list"),this._inputEl?.focus(),this._inputEl?.addEventListener("keydown",t=>{if(t.key==="ArrowUp"&&this._history.length>0&&(t.preventDefault(),this._inputEl.value=this._history[this._history.length-1].expression),t.key==="Enter"){t.preventDefault();let i=this._inputEl.value.trim();i&&this._doEval(i)}});let e=this.element.querySelector("form");e&&e.addEventListener("submit",t=>{t.preventDefault();let i=this._inputEl.value.trim();i&&this._doEval(i)})}evalExpression(e){e.preventDefault();let t=this._inputEl.value.trim();t&&this._doEval(t)}quickCall(e){let t=e.params?.funcName,i=e.params?.funcSig;if(t){let n=i?.match(/\(([^)]*)\)/),s=n?n[1]:"";s?(this._inputEl.value=`${t}(${s.split(",").map(l=>{let r=l.trim().split(/\s+/);return r[r.length-1]==="string"?'""':"0"}).join(", ")})`,this._inputEl.focus(),this._inputEl.setSelectionRange(t.length+1,this._inputEl.value.length-1)):(this._inputEl.value=`${t}()`,this._doEval(`${t}()`))}}clearResult(){this._resultEl.textContent="// Enter an expression above",this._resultEl.classList.remove("u-color-danger")}async _doEval(e){let t=this.getValue("pkg-path"),i=this.getValue("domain");this._resultEl.textContent="Evaluating...",this._resultEl.classList.remove("u-color-danger");try{let n=t.startsWith(i+"/")?t.slice(i.length+1):t,s=await fetch("/_/api/eval",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pkg_path:n,expression:e})});if(!s.ok)throw new Error(`HTTP ${s.status}`);let l=await s.json(),r,o;l.error?(r=`Error: ${l.error}`,o=!0):(r=l.result||"(empty result)",o=!1),this._resultEl.textContent=r,this._resultEl.classList.toggle("u-color-danger",o),this._addToHistory(e,r,o)}catch(n){let s=`Error: ${n instanceof Error?n.message:String(n)}`;this._resultEl.textContent=s,this._resultEl.classList.add("u-color-danger"),this._addToHistory(e,s,!0)}}_addToHistory(e,t,i){if(this._history.push({expression:e,result:t,isError:i}),this._historyListEl){let n=document.createElement("div");n.className="b-eval-history-entry";let s=document.createElement("div");s.className="b-eval-history-expr";let l=document.createElement("code");l.className="u-font-mono",l.textContent=e,s.appendChild(l);let r=document.createElement("button");r.className="b-eval-history-rerun",r.textContent="\u21A9",r.title="Re-run",r.addEventListener("click",()=>{this._inputEl.value=e,this._doEval(e)}),s.appendChild(r);let o=document.createElement("pre");o.className=`b-eval-history-result${i?" u-color-danger":""}`,o.textContent=t.length>200?t.substring(0,200)+"...":t,n.appendChild(s),n.appendChild(o),this._historyListEl.prepend(n)}}};export{a as EvalController};
From b5643ccbb9e58cfee617aec4d66120f3c549c056 Mon Sep 17 00:00:00 2001
From: moul <94029+moul@users.noreply.github.com>
Date: Fri, 3 Apr 2026 20:02:17 +0000
Subject: [PATCH 05/40] fix(gnoweb): bind playground buttons directly in
connect()
---
.../gnoweb/frontend/js/controller-playground.ts | 16 ++++++++++++++++
.../gnoweb/public/js/controller-playground.js | 6 +++---
2 files changed, 19 insertions(+), 3 deletions(-)
diff --git a/gno.land/pkg/gnoweb/frontend/js/controller-playground.ts b/gno.land/pkg/gnoweb/frontend/js/controller-playground.ts
index b186620ba14..8761ea39004 100644
--- a/gno.land/pkg/gnoweb/frontend/js/controller-playground.ts
+++ b/gno.land/pkg/gnoweb/frontend/js/controller-playground.ts
@@ -27,6 +27,22 @@ export class PlaygroundController extends BaseController {
this._renderTabs();
this._setupKeyboard();
+ this._bindButtons();
+ }
+
+ // Bind toolbar buttons directly (BaseController.setupActions can be unreliable)
+ private _bindButtons(): void {
+ this.element.querySelectorAll("[data-action]").forEach((el) => {
+ const attr = el.getAttribute("data-action");
+ if (!attr) return;
+ const match = attr.match(/^(\w+)->playground#(\w+)$/);
+ if (!match) return;
+ const [, event, method] = match;
+ const fn = (this as Record)[method];
+ if (typeof fn === "function") {
+ el.addEventListener(event, fn.bind(this));
+ }
+ });
}
private _parseForkedFiles(code: string): void {
diff --git a/gno.land/pkg/gnoweb/public/js/controller-playground.js b/gno.land/pkg/gnoweb/public/js/controller-playground.js
index 00a3c2005e0..1fe1afcf985 100644
--- a/gno.land/pkg/gnoweb/public/js/controller-playground.js
+++ b/gno.land/pkg/gnoweb/public/js/controller-playground.js
@@ -1,5 +1,5 @@
-import{BaseController as r}from"./controller.js";var a=class extends r{files=[];activeFile=0;_codeEl;_outputEl;_tabsEl;connect(){this._codeEl=this.getTarget("code"),this._outputEl=this.getTarget("output"),this._tabsEl=this.getTarget("tabs");let t=this._codeEl.value;t.includes("// --- ")&&t.includes(" ---")?this._parseForkedFiles(t):this.files=[{name:"main.gno",content:t}],this._renderTabs(),this._setupKeyboard()}_parseForkedFiles(t){let e=t.split(/^\/\/ --- (.+?) ---$/m);this.files=[];for(let i=1;i{if(t.ctrlKey&&t.key==="Enter"){t.preventDefault(),this.runCode();return}if(t.key==="Tab"&&!t.shiftKey){t.preventDefault();let e=this._codeEl.selectionStart,i=this._codeEl.selectionEnd;this._codeEl.value=this._codeEl.value.substring(0,e)+" "+this._codeEl.value.substring(i),this._codeEl.selectionStart=this._codeEl.selectionEnd=e+1}})}_renderTabs(){for(;this._tabsEl.firstChild;)this._tabsEl.removeChild(this._tabsEl.firstChild);this.files.forEach((e,i)=>{let n=document.createElement("button");n.className=`b-playground-tab${i===this.activeFile?" b-playground-tab--active":""}`,n.textContent=e.name,n.addEventListener("click",()=>this._switchToFile(e.name)),this._tabsEl.appendChild(n)});let t=document.createElement("button");t.className="b-playground-tab-add",t.textContent="+",t.title="Add file",t.addEventListener("click",()=>this.addFile()),this._tabsEl.appendChild(t)}_switchToFile(t){this.files[this.activeFile].content=this._codeEl.value;let e=this.files.findIndex(i=>i.name===t);e>=0&&(this.activeFile=e,this._codeEl.value=this.files[e].content,this._renderTabs())}switchTab(t){let i=t.currentTarget.dataset.playgroundFileParam||"";this._switchToFile(i)}addFile(){let t=prompt("File name (e.g. helper.gno):");if(t){if(!t.endsWith(".gno")){alert("File name must end with .gno");return}if(this.files.some(e=>e.name===t)){alert("File already exists");return}this.files[this.activeFile].content=this._codeEl.value,this.files.push({name:t,content:`package main
-`}),this.activeFile=this.files.length-1,this._codeEl.value=this.files[this.activeFile].content,this._renderTabs()}}async runCode(){this.files[this.activeFile].content=this._codeEl.value,this._outputEl.textContent="Running...";let t=this.getValue("remote"),e=this.getValue("domain"),i=this._codeEl.value,n=i.match(/^package\s+(\w+)/m),o=n?n[1]:"main";if(i.includes("func Render("))try{let l=await(await fetch("/_/api/eval",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pkg_path:`${e}/r/playground_preview`,expression:'Render("")'})})).json();l.error?(this._outputEl.textContent=`Error: ${l.error}`,this._outputEl.classList.add("u-color-danger")):(this._outputEl.textContent=l.result,this._outputEl.classList.remove("u-color-danger"))}catch{this._outputEl.textContent=`Note: Server-side execution requires a running gno node.
+import{BaseController as r}from"./controller.js";var l=class extends r{files=[];activeFile=0;_codeEl;_outputEl;_tabsEl;connect(){this._codeEl=this.getTarget("code"),this._outputEl=this.getTarget("output"),this._tabsEl=this.getTarget("tabs");let t=this._codeEl.value;t.includes("// --- ")&&t.includes(" ---")?this._parseForkedFiles(t):this.files=[{name:"main.gno",content:t}],this._renderTabs(),this._setupKeyboard(),this._bindButtons()}_bindButtons(){this.element.querySelectorAll("[data-action]").forEach(t=>{let e=t.getAttribute("data-action");if(!e)return;let i=e.match(/^(\w+)->playground#(\w+)$/);if(!i)return;let[,n,o]=i,s=this[o];typeof s=="function"&&t.addEventListener(n,s.bind(this))})}_parseForkedFiles(t){let e=t.split(/^\/\/ --- (.+?) ---$/m);this.files=[];for(let i=1;i{if(t.ctrlKey&&t.key==="Enter"){t.preventDefault(),this.runCode();return}if(t.key==="Tab"&&!t.shiftKey){t.preventDefault();let e=this._codeEl.selectionStart,i=this._codeEl.selectionEnd;this._codeEl.value=this._codeEl.value.substring(0,e)+" "+this._codeEl.value.substring(i),this._codeEl.selectionStart=this._codeEl.selectionEnd=e+1}})}_renderTabs(){for(;this._tabsEl.firstChild;)this._tabsEl.removeChild(this._tabsEl.firstChild);this.files.forEach((e,i)=>{let n=document.createElement("button");n.className=`b-playground-tab${i===this.activeFile?" b-playground-tab--active":""}`,n.textContent=e.name,n.addEventListener("click",()=>this._switchToFile(e.name)),this._tabsEl.appendChild(n)});let t=document.createElement("button");t.className="b-playground-tab-add",t.textContent="+",t.title="Add file",t.addEventListener("click",()=>this.addFile()),this._tabsEl.appendChild(t)}_switchToFile(t){this.files[this.activeFile].content=this._codeEl.value;let e=this.files.findIndex(i=>i.name===t);e>=0&&(this.activeFile=e,this._codeEl.value=this.files[e].content,this._renderTabs())}switchTab(t){let i=t.currentTarget.dataset.playgroundFileParam||"";this._switchToFile(i)}addFile(){let t=prompt("File name (e.g. helper.gno):");if(t){if(!t.endsWith(".gno")){alert("File name must end with .gno");return}if(this.files.some(e=>e.name===t)){alert("File already exists");return}this.files[this.activeFile].content=this._codeEl.value,this.files.push({name:t,content:`package main
+`}),this.activeFile=this.files.length-1,this._codeEl.value=this.files[this.activeFile].content,this._renderTabs()}}async runCode(){this.files[this.activeFile].content=this._codeEl.value,this._outputEl.textContent="Running...";let t=this.getValue("remote"),e=this.getValue("domain"),i=this._codeEl.value,n=i.match(/^package\s+(\w+)/m),o=n?n[1]:"main";if(i.includes("func Render("))try{let a=await(await fetch("/_/api/eval",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pkg_path:`${e}/r/playground_preview`,expression:'Render("")'})})).json();a.error?(this._outputEl.textContent=`Error: ${a.error}`,this._outputEl.classList.add("u-color-danger")):(this._outputEl.textContent=a.result,this._outputEl.classList.remove("u-color-danger"))}catch{this._outputEl.textContent=`Note: Server-side execution requires a running gno node.
Package: ${o}
Files: ${this.files.map(s=>s.name).join(", ")}
@@ -22,4 +22,4 @@ To format locally:
${n.content}`).join(`
`),e=encodeURIComponent(t),i=`${window.location.origin}/_/play?code=${e}`;navigator.clipboard.writeText(i).then(()=>{this._outputEl.textContent="Share URL copied to clipboard!"}).catch(()=>{this._outputEl.textContent=`Share URL:
-${i}`})}clearOutput(){this._outputEl.textContent="// Run code to see output here",this._outputEl.classList.remove("u-color-danger")}};export{a as PlaygroundController};
+${i}`})}clearOutput(){this._outputEl.textContent="// Run code to see output here",this._outputEl.classList.remove("u-color-danger")}};export{l as PlaygroundController};
From 8e789cf5315f7f42452380d9ce70d23beb873ba6 Mon Sep 17 00:00:00 2001
From: moul <94029+moul@users.noreply.github.com>
Date: Fri, 3 Apr 2026 20:23:12 +0000
Subject: [PATCH 06/40] fix(gnoweb): rewrite controllers as standalone
functions, add self to CSP connect-src
---
gno.land/cmd/gnoweb/main.go | 2 +-
.../pkg/gnoweb/frontend/js/controller-eval.ts | 249 ++++++++----------
.../frontend/js/controller-playground.ts | 248 +++++++----------
.../pkg/gnoweb/public/js/controller-eval.js | 2 +-
.../gnoweb/public/js/controller-playground.js | 28 +-
5 files changed, 225 insertions(+), 304 deletions(-)
diff --git a/gno.land/cmd/gnoweb/main.go b/gno.land/cmd/gnoweb/main.go
index 93167de9794..c5e69a9dbdc 100644
--- a/gno.land/cmd/gnoweb/main.go
+++ b/gno.land/cmd/gnoweb/main.go
@@ -343,7 +343,7 @@ func SecureHeadersMiddleware(next http.Handler, strict bool, remote string) http
// scripts, styles, images, and other resources. This helps prevent
// cross-site scripting (XSS) and other code injection attacks.
csp := fmt.Sprintf(
- "default-src 'self'; script-src 'self' https://sa.gno.services; style-src 'self'; img-src %s; font-src 'self'; connect-src %s/abci_query; form-action 'self'",
+ "default-src 'self'; script-src 'self' https://sa.gno.services; style-src 'self'; img-src %s; font-src 'self'; connect-src 'self' %s/abci_query; form-action 'self'",
imgSrc,
remote,
)
diff --git a/gno.land/pkg/gnoweb/frontend/js/controller-eval.ts b/gno.land/pkg/gnoweb/frontend/js/controller-eval.ts
index e54b1d28e54..1d7d2fa0f6e 100644
--- a/gno.land/pkg/gnoweb/frontend/js/controller-eval.ts
+++ b/gno.land/pkg/gnoweb/frontend/js/controller-eval.ts
@@ -1,100 +1,28 @@
-import { BaseController } from "./controller.js";
-
+// Expression evaluator controller — standalone, no BaseController dependency
interface HistoryEntry {
expression: string;
result: string;
isError: boolean;
}
-export class EvalController extends BaseController {
- private _inputEl!: HTMLInputElement;
- private _resultEl!: HTMLElement;
- private _historyListEl!: HTMLElement;
- private _history: HistoryEntry[] = [];
-
- protected connect(): void {
- this._inputEl = this.getTarget("input") as HTMLInputElement;
- this._resultEl = this.getTarget("result") as HTMLElement;
- this._historyListEl = this.getTarget("history-list") as HTMLElement;
-
- this._inputEl?.focus();
-
- this._inputEl?.addEventListener("keydown", (e: KeyboardEvent) => {
- // Up arrow = previous history
- if (e.key === "ArrowUp" && this._history.length > 0) {
- e.preventDefault();
- this._inputEl.value =
- this._history[this._history.length - 1].expression;
- }
- // Enter = submit
- if (e.key === "Enter") {
- e.preventDefault();
- const expr = this._inputEl.value.trim();
- if (expr) this._doEval(expr);
- }
- });
-
- // Also intercept form submit directly (belt and suspenders)
- const form = this.element.querySelector("form");
- if (form) {
- form.addEventListener("submit", (e: Event) => {
- e.preventDefault();
- const expr = this._inputEl.value.trim();
- if (expr) this._doEval(expr);
- });
- }
- }
-
- public evalExpression(event: Event): void {
- event.preventDefault();
- const expr = this._inputEl.value.trim();
- if (!expr) return;
- this._doEval(expr);
- }
-
- public quickCall(event: Event & { params?: Record }): void {
- const funcName = event.params?.funcName as string;
- const funcSig = event.params?.funcSig as string;
+export default function EvalController(element: HTMLElement): void {
+ const inputEl = element.querySelector('[data-eval-target="input"]') as HTMLInputElement;
+ const resultEl = element.querySelector('[data-eval-target="result"]') as HTMLElement;
+ const historyListEl = element.querySelector('[data-eval-target="history-list"]') as HTMLElement;
- if (funcName) {
- const paramMatch = funcSig?.match(/\(([^)]*)\)/);
- const params = paramMatch ? paramMatch[1] : "";
-
- if (params) {
- this._inputEl.value = `${funcName}(${params
- .split(",")
- .map((p: string) => {
- const parts = p.trim().split(/\s+/);
- const type = parts[parts.length - 1];
- return type === "string" ? '""' : "0";
- })
- .join(", ")})`;
- this._inputEl.focus();
- this._inputEl.setSelectionRange(
- funcName.length + 1,
- this._inputEl.value.length - 1,
- );
- } else {
- this._inputEl.value = `${funcName}()`;
- this._doEval(`${funcName}()`);
- }
- }
- }
+ if (!inputEl || !resultEl) return;
- public clearResult(): void {
- this._resultEl.textContent = "// Enter an expression above";
- this._resultEl.classList.remove("u-color-danger");
- }
+ const pkgPath = element.getAttribute("data-eval-pkg-path-value") || "";
+ const domain = element.getAttribute("data-eval-domain-value") || "gno.land";
+ const history: HistoryEntry[] = [];
- private async _doEval(expression: string): Promise {
- const pkgPath = this.getValue("pkg-path");
- const domain = this.getValue("domain");
+ inputEl.focus();
- this._resultEl.textContent = "Evaluating...";
- this._resultEl.classList.remove("u-color-danger");
+ async function doEval(expression: string): Promise {
+ resultEl.textContent = "Evaluating...";
+ resultEl.classList.remove("u-color-danger");
try {
- // Strip domain prefix from pkgPath for the API (it expects relative path)
const relPath = pkgPath.startsWith(domain + "/")
? pkgPath.slice(domain.length + 1)
: pkgPath;
@@ -102,21 +30,14 @@ export class EvalController extends BaseController {
const response = await fetch("/_/api/eval", {
method: "POST",
headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- pkg_path: relPath,
- expression: expression,
- }),
+ body: JSON.stringify({ pkg_path: relPath, expression }),
});
- if (!response.ok) {
- throw new Error(`HTTP ${response.status}`);
- }
-
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
const json = await response.json();
let result: string;
let isError: boolean;
-
if (json.error) {
result = `Error: ${json.error}`;
isError = true;
@@ -125,55 +46,111 @@ export class EvalController extends BaseController {
isError = false;
}
- this._resultEl.textContent = result;
- this._resultEl.classList.toggle("u-color-danger", isError);
-
- this._addToHistory(expression, result, isError);
+ resultEl.textContent = result;
+ resultEl.classList.toggle("u-color-danger", isError);
+ addToHistory(expression, result, isError);
} catch (err) {
const errMsg = `Error: ${err instanceof Error ? err.message : String(err)}`;
- this._resultEl.textContent = errMsg;
- this._resultEl.classList.add("u-color-danger");
- this._addToHistory(expression, errMsg, true);
+ resultEl.textContent = errMsg;
+ resultEl.classList.add("u-color-danger");
+ addToHistory(expression, errMsg, true);
}
}
- private _addToHistory(
- expression: string,
- result: string,
- isError: boolean,
- ): void {
- this._history.push({ expression, result, isError });
-
- if (this._historyListEl) {
- const entry = document.createElement("div");
- entry.className = "b-eval-history-entry";
-
- const exprDiv = document.createElement("div");
- exprDiv.className = "b-eval-history-expr";
-
- const codeEl = document.createElement("code");
- codeEl.className = "u-font-mono";
- codeEl.textContent = expression;
- exprDiv.appendChild(codeEl);
-
- const rerunBtn = document.createElement("button");
- rerunBtn.className = "b-eval-history-rerun";
- rerunBtn.textContent = "\u21A9"; // ↩
- rerunBtn.title = "Re-run";
- rerunBtn.addEventListener("click", () => {
- this._inputEl.value = expression;
- this._doEval(expression);
- });
- exprDiv.appendChild(rerunBtn);
+ function addToHistory(expression: string, result: string, isError: boolean): void {
+ history.push({ expression, result, isError });
+ if (!historyListEl) return;
+
+ const entry = document.createElement("div");
+ entry.className = "b-eval-history-entry";
+
+ const exprDiv = document.createElement("div");
+ exprDiv.className = "b-eval-history-expr";
+
+ const codeEl = document.createElement("code");
+ codeEl.className = "u-font-mono";
+ codeEl.textContent = expression;
+ exprDiv.appendChild(codeEl);
+
+ const rerunBtn = document.createElement("button");
+ rerunBtn.className = "b-eval-history-rerun";
+ rerunBtn.textContent = "\u21A9";
+ rerunBtn.title = "Re-run";
+ rerunBtn.addEventListener("click", () => {
+ inputEl.value = expression;
+ doEval(expression);
+ });
+ exprDiv.appendChild(rerunBtn);
+
+ const resultPre = document.createElement("pre");
+ resultPre.className = `b-eval-history-result${isError ? " u-color-danger" : ""}`;
+ resultPre.textContent = result.length > 200 ? result.substring(0, 200) + "..." : result;
- const resultPre = document.createElement("pre");
- resultPre.className = `b-eval-history-result${isError ? " u-color-danger" : ""}`;
- resultPre.textContent =
- result.length > 200 ? result.substring(0, 200) + "..." : result;
+ entry.appendChild(exprDiv);
+ entry.appendChild(resultPre);
+ historyListEl.prepend(entry);
+ }
- entry.appendChild(exprDiv);
- entry.appendChild(resultPre);
- this._historyListEl.prepend(entry);
+ // Enter key submits
+ inputEl.addEventListener("keydown", (e: KeyboardEvent) => {
+ if (e.key === "ArrowUp" && history.length > 0) {
+ e.preventDefault();
+ inputEl.value = history[history.length - 1].expression;
+ }
+ if (e.key === "Enter") {
+ e.preventDefault();
+ const expr = inputEl.value.trim();
+ if (expr) doEval(expr);
}
+ });
+
+ // Form submit
+ const form = element.querySelector("form");
+ if (form) {
+ form.addEventListener("submit", (e: Event) => {
+ e.preventDefault();
+ const expr = inputEl.value.trim();
+ if (expr) doEval(expr);
+ });
}
+
+ // Clear button
+ element.querySelectorAll("[data-action]").forEach((el) => {
+ const attr = el.getAttribute("data-action");
+ if (attr === "click->eval#clearResult") {
+ el.addEventListener("click", () => {
+ resultEl.textContent = "// Enter an expression above";
+ resultEl.classList.remove("u-color-danger");
+ });
+ }
+ });
+
+ // Quick call buttons
+ element.querySelectorAll("[data-action]").forEach((el) => {
+ const attr = el.getAttribute("data-action");
+ if (attr !== "click->eval#quickCall") return;
+ el.addEventListener("click", () => {
+ const funcName = (el as HTMLElement).dataset.evalFuncNameParam || "";
+ const funcSig = (el as HTMLElement).dataset.evalFuncSigParam || "";
+ if (!funcName) return;
+
+ const paramMatch = funcSig.match(/\(([^)]*)\)/);
+ const params = paramMatch ? paramMatch[1] : "";
+ if (params) {
+ inputEl.value = `${funcName}(${params
+ .split(",")
+ .map((p: string) => {
+ const parts = p.trim().split(/\s+/);
+ const type = parts[parts.length - 1];
+ return type === "string" ? '""' : "0";
+ })
+ .join(", ")})`;
+ inputEl.focus();
+ inputEl.setSelectionRange(funcName.length + 1, inputEl.value.length - 1);
+ } else {
+ inputEl.value = `${funcName}()`;
+ doEval(`${funcName}()`);
+ }
+ });
+ });
}
diff --git a/gno.land/pkg/gnoweb/frontend/js/controller-playground.ts b/gno.land/pkg/gnoweb/frontend/js/controller-playground.ts
index 8761ea39004..e201dadcf40 100644
--- a/gno.land/pkg/gnoweb/frontend/js/controller-playground.ts
+++ b/gno.land/pkg/gnoweb/frontend/js/controller-playground.ts
@@ -1,150 +1,80 @@
-import { BaseController } from "./controller.js";
-
+// Playground controller — standalone, does not rely on BaseController action wiring
interface PlaygroundFile {
name: string;
content: string;
}
-export class PlaygroundController extends BaseController {
- private files: PlaygroundFile[] = [];
- private activeFile = 0;
- private _codeEl!: HTMLTextAreaElement;
- private _outputEl!: HTMLElement;
- private _tabsEl!: HTMLElement;
-
- protected connect(): void {
- this._codeEl = this.getTarget("code") as HTMLTextAreaElement;
- this._outputEl = this.getTarget("output") as HTMLElement;
- this._tabsEl = this.getTarget("tabs") as HTMLElement;
-
- // Initialize files from the editor content
- const initialCode = this._codeEl.value;
- if (initialCode.includes("// --- ") && initialCode.includes(" ---")) {
- this._parseForkedFiles(initialCode);
- } else {
- this.files = [{ name: "main.gno", content: initialCode }];
- }
+export default function PlaygroundController(element: HTMLElement): void {
+ let files: PlaygroundFile[] = [];
+ let activeFile = 0;
- this._renderTabs();
- this._setupKeyboard();
- this._bindButtons();
- }
+ const codeEl = element.querySelector('[data-playground-target="code"]') as HTMLTextAreaElement;
+ const outputEl = element.querySelector('[data-playground-target="output"]') as HTMLElement;
+ const tabsEl = element.querySelector('[data-playground-target="tabs"]') as HTMLElement;
- // Bind toolbar buttons directly (BaseController.setupActions can be unreliable)
- private _bindButtons(): void {
- this.element.querySelectorAll("[data-action]").forEach((el) => {
- const attr = el.getAttribute("data-action");
- if (!attr) return;
- const match = attr.match(/^(\w+)->playground#(\w+)$/);
- if (!match) return;
- const [, event, method] = match;
- const fn = (this as Record)[method];
- if (typeof fn === "function") {
- el.addEventListener(event, fn.bind(this));
- }
- });
- }
+ if (!codeEl || !outputEl || !tabsEl) return;
- private _parseForkedFiles(code: string): void {
- const parts = code.split(/^\/\/ --- (.+?) ---$/m);
- this.files = [];
+ const domain = element.getAttribute("data-playground-domain-value") || "gno.land";
+
+ // Parse initial code
+ const initialCode = codeEl.value;
+ if (initialCode.includes("// --- ") && initialCode.includes(" ---")) {
+ const parts = initialCode.split(/^\/\/ --- (.+?) ---$/m);
for (let i = 1; i < parts.length; i += 2) {
const name = parts[i].trim();
const content = (parts[i + 1] || "").trim();
- if (name) {
- this.files.push({ name, content });
- }
+ if (name) files.push({ name, content });
}
- if (this.files.length === 0) {
- this.files = [{ name: "main.gno", content: code }];
- }
- this._codeEl.value = this.files[0].content;
- }
-
- private _setupKeyboard(): void {
- this._codeEl.addEventListener("keydown", (e: KeyboardEvent) => {
- if (e.ctrlKey && e.key === "Enter") {
- e.preventDefault();
- this.runCode();
- return;
- }
- if (e.key === "Tab" && !e.shiftKey) {
- e.preventDefault();
- const start = this._codeEl.selectionStart;
- const end = this._codeEl.selectionEnd;
- this._codeEl.value =
- this._codeEl.value.substring(0, start) +
- "\t" +
- this._codeEl.value.substring(end);
- this._codeEl.selectionStart = this._codeEl.selectionEnd = start + 1;
- }
- });
+ if (files.length === 0) files = [{ name: "main.gno", content: initialCode }];
+ codeEl.value = files[0].content;
+ } else {
+ files = [{ name: "main.gno", content: initialCode }];
}
- private _renderTabs(): void {
- // Clear existing tabs using safe DOM methods
- while (this._tabsEl.firstChild) {
- this._tabsEl.removeChild(this._tabsEl.firstChild);
- }
-
- this.files.forEach((f, i) => {
+ function renderTabs(): void {
+ while (tabsEl.firstChild) tabsEl.removeChild(tabsEl.firstChild);
+ files.forEach((f, i) => {
const btn = document.createElement("button");
- btn.className = `b-playground-tab${i === this.activeFile ? " b-playground-tab--active" : ""}`;
+ btn.className = `b-playground-tab${i === activeFile ? " b-playground-tab--active" : ""}`;
btn.textContent = f.name;
- btn.addEventListener("click", () => this._switchToFile(f.name));
- this._tabsEl.appendChild(btn);
+ btn.addEventListener("click", () => switchToFile(f.name));
+ tabsEl.appendChild(btn);
});
-
const addBtn = document.createElement("button");
addBtn.className = "b-playground-tab-add";
addBtn.textContent = "+";
addBtn.title = "Add file";
- addBtn.addEventListener("click", () => this.addFile());
- this._tabsEl.appendChild(addBtn);
+ addBtn.addEventListener("click", addFile);
+ tabsEl.appendChild(addBtn);
}
- private _switchToFile(fileName: string): void {
- this.files[this.activeFile].content = this._codeEl.value;
- const idx = this.files.findIndex((f) => f.name === fileName);
+ function switchToFile(fileName: string): void {
+ files[activeFile].content = codeEl.value;
+ const idx = files.findIndex((f) => f.name === fileName);
if (idx >= 0) {
- this.activeFile = idx;
- this._codeEl.value = this.files[idx].content;
- this._renderTabs();
+ activeFile = idx;
+ codeEl.value = files[idx].content;
+ renderTabs();
}
}
- public switchTab(event: Event): void {
- const target = event.currentTarget as HTMLElement;
- const fileName = target.dataset.playgroundFileParam || "";
- this._switchToFile(fileName);
- }
-
- public addFile(): void {
+ function addFile(): void {
const name = prompt("File name (e.g. helper.gno):");
- if (!name) return;
- if (!name.endsWith(".gno")) {
- alert("File name must end with .gno");
- return;
- }
- if (this.files.some((f) => f.name === name)) {
- alert("File already exists");
- return;
- }
-
- this.files[this.activeFile].content = this._codeEl.value;
- this.files.push({ name, content: `package main\n` });
- this.activeFile = this.files.length - 1;
- this._codeEl.value = this.files[this.activeFile].content;
- this._renderTabs();
+ if (!name || !name.endsWith(".gno")) return;
+ if (files.some((f) => f.name === name)) return;
+ files[activeFile].content = codeEl.value;
+ files.push({ name, content: "package main\n" });
+ activeFile = files.length - 1;
+ codeEl.value = files[activeFile].content;
+ renderTabs();
}
- public async runCode(): Promise {
- this.files[this.activeFile].content = this._codeEl.value;
- this._outputEl.textContent = "Running...";
+ async function runCode(): Promise {
+ files[activeFile].content = codeEl.value;
+ outputEl.textContent = "Running...";
+ outputEl.classList.remove("u-color-danger");
- const remote = this.getValue("remote");
- const domain = this.getValue("domain");
- const code = this._codeEl.value;
+ const code = codeEl.value;
const pkgMatch = code.match(/^package\s+(\w+)/m);
const pkgName = pkgMatch ? pkgMatch[1] : "main";
@@ -155,63 +85,77 @@ export class PlaygroundController extends BaseController {
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
pkg_path: `${domain}/r/playground_preview`,
- expression: `Render("")`,
+ expression: 'Render("")',
}),
});
const result = await resp.json();
if (result.error) {
- this._outputEl.textContent = `Error: ${result.error}`;
- this._outputEl.classList.add("u-color-danger");
+ outputEl.textContent = `Error: ${result.error}`;
+ outputEl.classList.add("u-color-danger");
} else {
- this._outputEl.textContent = result.result;
- this._outputEl.classList.remove("u-color-danger");
+ outputEl.textContent = result.result;
}
} catch {
- this._outputEl.textContent = `Note: Server-side execution requires a running gno node.\n\nPackage: ${pkgName}\nFiles: ${this.files.map((f) => f.name).join(", ")}\n\nTo deploy and test, use:\n gnokey maketx addpkg -pkgpath "${domain}/r/yourname/pkg" ...`;
- this._outputEl.classList.remove("u-color-danger");
+ outputEl.textContent = `Note: Server-side execution not available for scratch pad code.\n\nPackage: ${pkgName}\nFiles: ${files.map((f) => f.name).join(", ")}\n\nTo deploy and test:\n gnokey maketx addpkg -pkgpath "${domain}/r/yourname/pkg" ...`;
}
} else {
- this._outputEl.textContent = `Package: ${pkgName}\nFiles: ${this.files.map((f) => f.name).join(", ")}\n\nTo run locally:\n gno run ${this.files.map((f) => f.name).join(" ")}\n\nTo test:\n gno test .`;
- this._outputEl.classList.remove("u-color-danger");
+ outputEl.textContent = `Package: ${pkgName}\nFiles: ${files.map((f) => f.name).join(", ")}\n\nTo run locally:\n gno run ${files.map((f) => f.name).join(" ")}\n\nTo test:\n gno test .`;
}
}
- public runTests(): void {
- this._outputEl.textContent =
- "Testing requires a running gno node.\n\nTo test locally:\n gno test .";
+ function runTests(): void {
+ outputEl.textContent = "Testing requires a running gno node.\n\nTo test locally:\n gno test .";
}
- public formatCode(): void {
- this._outputEl.textContent =
+ function formatCode(): void {
+ outputEl.textContent =
"Formatting requires server-side gno fmt (coming soon).\n\nTo format locally:\n gno fmt -w " +
- this.files[this.activeFile].name;
+ files[activeFile].name;
}
- public shareCode(): void {
- this.files[this.activeFile].content = this._codeEl.value;
-
+ function shareCode(): void {
+ files[activeFile].content = codeEl.value;
const code =
- this.files.length === 1
- ? this.files[0].content
- : this.files
- .map((f) => `// --- ${f.name} ---\n${f.content}`)
- .join("\n\n");
-
- const encoded = encodeURIComponent(code);
- const url = `${window.location.origin}/_/play?code=${encoded}`;
-
+ files.length === 1
+ ? files[0].content
+ : files.map((f) => `// --- ${f.name} ---\n${f.content}`).join("\n\n");
+ const url = `${window.location.origin}/_/play?code=${encodeURIComponent(code)}`;
navigator.clipboard
.writeText(url)
- .then(() => {
- this._outputEl.textContent = "Share URL copied to clipboard!";
- })
- .catch(() => {
- this._outputEl.textContent = `Share URL:\n${url}`;
- });
+ .then(() => { outputEl.textContent = "Share URL copied to clipboard!"; })
+ .catch(() => { outputEl.textContent = `Share URL:\n${url}`; });
}
- public clearOutput(): void {
- this._outputEl.textContent = "// Run code to see output here";
- this._outputEl.classList.remove("u-color-danger");
+ function clearOutput(): void {
+ outputEl.textContent = "// Run code to see output here";
+ outputEl.classList.remove("u-color-danger");
}
+
+ // Keyboard shortcuts
+ codeEl.addEventListener("keydown", (e: KeyboardEvent) => {
+ if (e.ctrlKey && e.key === "Enter") { e.preventDefault(); runCode(); return; }
+ if (e.key === "Tab" && !e.shiftKey) {
+ e.preventDefault();
+ const start = codeEl.selectionStart;
+ const end = codeEl.selectionEnd;
+ codeEl.value = codeEl.value.substring(0, start) + "\t" + codeEl.value.substring(end);
+ codeEl.selectionStart = codeEl.selectionEnd = start + 1;
+ }
+ });
+
+ // Wire buttons by data-action attribute
+ const actions: Record void> = {
+ runCode, runTests, formatCode, shareCode, clearOutput,
+ };
+ element.querySelectorAll("[data-action]").forEach((el) => {
+ const attr = el.getAttribute("data-action");
+ if (!attr) return;
+ const match = attr.match(/^(\w+)->playground#(\w+)$/);
+ if (!match) return;
+ const [, evt, method] = match;
+ const fn = actions[method];
+ if (fn) el.addEventListener(evt, fn);
+ });
+
+ renderTabs();
}
diff --git a/gno.land/pkg/gnoweb/public/js/controller-eval.js b/gno.land/pkg/gnoweb/public/js/controller-eval.js
index 991f33d2e20..9c3474d5e88 100644
--- a/gno.land/pkg/gnoweb/public/js/controller-eval.js
+++ b/gno.land/pkg/gnoweb/public/js/controller-eval.js
@@ -1 +1 @@
-import{BaseController as u}from"./controller.js";var a=class extends u{_inputEl;_resultEl;_historyListEl;_history=[];connect(){this._inputEl=this.getTarget("input"),this._resultEl=this.getTarget("result"),this._historyListEl=this.getTarget("history-list"),this._inputEl?.focus(),this._inputEl?.addEventListener("keydown",t=>{if(t.key==="ArrowUp"&&this._history.length>0&&(t.preventDefault(),this._inputEl.value=this._history[this._history.length-1].expression),t.key==="Enter"){t.preventDefault();let i=this._inputEl.value.trim();i&&this._doEval(i)}});let e=this.element.querySelector("form");e&&e.addEventListener("submit",t=>{t.preventDefault();let i=this._inputEl.value.trim();i&&this._doEval(i)})}evalExpression(e){e.preventDefault();let t=this._inputEl.value.trim();t&&this._doEval(t)}quickCall(e){let t=e.params?.funcName,i=e.params?.funcSig;if(t){let n=i?.match(/\(([^)]*)\)/),s=n?n[1]:"";s?(this._inputEl.value=`${t}(${s.split(",").map(l=>{let r=l.trim().split(/\s+/);return r[r.length-1]==="string"?'""':"0"}).join(", ")})`,this._inputEl.focus(),this._inputEl.setSelectionRange(t.length+1,this._inputEl.value.length-1)):(this._inputEl.value=`${t}()`,this._doEval(`${t}()`))}}clearResult(){this._resultEl.textContent="// Enter an expression above",this._resultEl.classList.remove("u-color-danger")}async _doEval(e){let t=this.getValue("pkg-path"),i=this.getValue("domain");this._resultEl.textContent="Evaluating...",this._resultEl.classList.remove("u-color-danger");try{let n=t.startsWith(i+"/")?t.slice(i.length+1):t,s=await fetch("/_/api/eval",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pkg_path:n,expression:e})});if(!s.ok)throw new Error(`HTTP ${s.status}`);let l=await s.json(),r,o;l.error?(r=`Error: ${l.error}`,o=!0):(r=l.result||"(empty result)",o=!1),this._resultEl.textContent=r,this._resultEl.classList.toggle("u-color-danger",o),this._addToHistory(e,r,o)}catch(n){let s=`Error: ${n instanceof Error?n.message:String(n)}`;this._resultEl.textContent=s,this._resultEl.classList.add("u-color-danger"),this._addToHistory(e,s,!0)}}_addToHistory(e,t,i){if(this._history.push({expression:e,result:t,isError:i}),this._historyListEl){let n=document.createElement("div");n.className="b-eval-history-entry";let s=document.createElement("div");s.className="b-eval-history-expr";let l=document.createElement("code");l.className="u-font-mono",l.textContent=e,s.appendChild(l);let r=document.createElement("button");r.className="b-eval-history-rerun",r.textContent="\u21A9",r.title="Re-run",r.addEventListener("click",()=>{this._inputEl.value=e,this._doEval(e)}),s.appendChild(r);let o=document.createElement("pre");o.className=`b-eval-history-result${i?" u-color-danger":""}`,o.textContent=t.length>200?t.substring(0,200)+"...":t,n.appendChild(s),n.appendChild(o),this._historyListEl.prepend(n)}}};export{a as EvalController};
+function h(i){let a=i.querySelector('[data-eval-target="input"]'),l=i.querySelector('[data-eval-target="result"]'),p=i.querySelector('[data-eval-target="history-list"]');if(!a||!l)return;let g=i.getAttribute("data-eval-pkg-path-value")||"",m=i.getAttribute("data-eval-domain-value")||"gno.land",d=[];a.focus();async function v(t){l.textContent="Evaluating...",l.classList.remove("u-color-danger");try{let e=g.startsWith(m+"/")?g.slice(m.length+1):g,r=await fetch("/_/api/eval",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pkg_path:e,expression:t})});if(!r.ok)throw new Error(`HTTP ${r.status}`);let s=await r.json(),n,o;s.error?(n=`Error: ${s.error}`,o=!0):(n=s.result||"(empty result)",o=!1),l.textContent=n,l.classList.toggle("u-color-danger",o),E(t,n,o)}catch(e){let r=`Error: ${e instanceof Error?e.message:String(e)}`;l.textContent=r,l.classList.add("u-color-danger"),E(t,r,!0)}}function E(t,e,r){if(d.push({expression:t,result:e,isError:r}),!p)return;let s=document.createElement("div");s.className="b-eval-history-entry";let n=document.createElement("div");n.className="b-eval-history-expr";let o=document.createElement("code");o.className="u-font-mono",o.textContent=t,n.appendChild(o);let c=document.createElement("button");c.className="b-eval-history-rerun",c.textContent="\u21A9",c.title="Re-run",c.addEventListener("click",()=>{a.value=t,v(t)}),n.appendChild(c);let u=document.createElement("pre");u.className=`b-eval-history-result${r?" u-color-danger":""}`,u.textContent=e.length>200?e.substring(0,200)+"...":e,s.appendChild(n),s.appendChild(u),p.prepend(s)}a.addEventListener("keydown",t=>{if(t.key==="ArrowUp"&&d.length>0&&(t.preventDefault(),a.value=d[d.length-1].expression),t.key==="Enter"){t.preventDefault();let e=a.value.trim();e&&v(e)}});let f=i.querySelector("form");f&&f.addEventListener("submit",t=>{t.preventDefault();let e=a.value.trim();e&&v(e)}),i.querySelectorAll("[data-action]").forEach(t=>{t.getAttribute("data-action")==="click->eval#clearResult"&&t.addEventListener("click",()=>{l.textContent="// Enter an expression above",l.classList.remove("u-color-danger")})}),i.querySelectorAll("[data-action]").forEach(t=>{t.getAttribute("data-action")==="click->eval#quickCall"&&t.addEventListener("click",()=>{let r=t.dataset.evalFuncNameParam||"",s=t.dataset.evalFuncSigParam||"";if(!r)return;let n=s.match(/\(([^)]*)\)/),o=n?n[1]:"";o?(a.value=`${r}(${o.split(",").map(c=>{let u=c.trim().split(/\s+/);return u[u.length-1]==="string"?'""':"0"}).join(", ")})`,a.focus(),a.setSelectionRange(r.length+1,a.value.length-1)):(a.value=`${r}()`,v(`${r}()`))})})}export{h as default};
diff --git a/gno.land/pkg/gnoweb/public/js/controller-playground.js b/gno.land/pkg/gnoweb/public/js/controller-playground.js
index 1fe1afcf985..a79a7776c95 100644
--- a/gno.land/pkg/gnoweb/public/js/controller-playground.js
+++ b/gno.land/pkg/gnoweb/public/js/controller-playground.js
@@ -1,25 +1,25 @@
-import{BaseController as r}from"./controller.js";var l=class extends r{files=[];activeFile=0;_codeEl;_outputEl;_tabsEl;connect(){this._codeEl=this.getTarget("code"),this._outputEl=this.getTarget("output"),this._tabsEl=this.getTarget("tabs");let t=this._codeEl.value;t.includes("// --- ")&&t.includes(" ---")?this._parseForkedFiles(t):this.files=[{name:"main.gno",content:t}],this._renderTabs(),this._setupKeyboard(),this._bindButtons()}_bindButtons(){this.element.querySelectorAll("[data-action]").forEach(t=>{let e=t.getAttribute("data-action");if(!e)return;let i=e.match(/^(\w+)->playground#(\w+)$/);if(!i)return;let[,n,o]=i,s=this[o];typeof s=="function"&&t.addEventListener(n,s.bind(this))})}_parseForkedFiles(t){let e=t.split(/^\/\/ --- (.+?) ---$/m);this.files=[];for(let i=1;i{if(t.ctrlKey&&t.key==="Enter"){t.preventDefault(),this.runCode();return}if(t.key==="Tab"&&!t.shiftKey){t.preventDefault();let e=this._codeEl.selectionStart,i=this._codeEl.selectionEnd;this._codeEl.value=this._codeEl.value.substring(0,e)+" "+this._codeEl.value.substring(i),this._codeEl.selectionStart=this._codeEl.selectionEnd=e+1}})}_renderTabs(){for(;this._tabsEl.firstChild;)this._tabsEl.removeChild(this._tabsEl.firstChild);this.files.forEach((e,i)=>{let n=document.createElement("button");n.className=`b-playground-tab${i===this.activeFile?" b-playground-tab--active":""}`,n.textContent=e.name,n.addEventListener("click",()=>this._switchToFile(e.name)),this._tabsEl.appendChild(n)});let t=document.createElement("button");t.className="b-playground-tab-add",t.textContent="+",t.title="Add file",t.addEventListener("click",()=>this.addFile()),this._tabsEl.appendChild(t)}_switchToFile(t){this.files[this.activeFile].content=this._codeEl.value;let e=this.files.findIndex(i=>i.name===t);e>=0&&(this.activeFile=e,this._codeEl.value=this.files[e].content,this._renderTabs())}switchTab(t){let i=t.currentTarget.dataset.playgroundFileParam||"";this._switchToFile(i)}addFile(){let t=prompt("File name (e.g. helper.gno):");if(t){if(!t.endsWith(".gno")){alert("File name must end with .gno");return}if(this.files.some(e=>e.name===t)){alert("File already exists");return}this.files[this.activeFile].content=this._codeEl.value,this.files.push({name:t,content:`package main
-`}),this.activeFile=this.files.length-1,this._codeEl.value=this.files[this.activeFile].content,this._renderTabs()}}async runCode(){this.files[this.activeFile].content=this._codeEl.value,this._outputEl.textContent="Running...";let t=this.getValue("remote"),e=this.getValue("domain"),i=this._codeEl.value,n=i.match(/^package\s+(\w+)/m),o=n?n[1]:"main";if(i.includes("func Render("))try{let a=await(await fetch("/_/api/eval",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pkg_path:`${e}/r/playground_preview`,expression:'Render("")'})})).json();a.error?(this._outputEl.textContent=`Error: ${a.error}`,this._outputEl.classList.add("u-color-danger")):(this._outputEl.textContent=a.result,this._outputEl.classList.remove("u-color-danger"))}catch{this._outputEl.textContent=`Note: Server-side execution requires a running gno node.
+function T(l){let e=[],c=0,o=l.querySelector('[data-playground-target="code"]'),i=l.querySelector('[data-playground-target="output"]'),s=l.querySelector('[data-playground-target="tabs"]');if(!o||!i||!s)return;let m=l.getAttribute("data-playground-domain-value")||"gno.land",d=o.value;if(d.includes("// --- ")&&d.includes(" ---")){let t=d.split(/^\/\/ --- (.+?) ---$/m);for(let n=1;n{let r=document.createElement("button");r.className=`b-playground-tab${a===c?" b-playground-tab--active":""}`,r.textContent=n.name,r.addEventListener("click",()=>v(n.name)),s.appendChild(r)});let t=document.createElement("button");t.className="b-playground-tab-add",t.textContent="+",t.title="Add file",t.addEventListener("click",h),s.appendChild(t)}function v(t){e[c].content=o.value;let n=e.findIndex(a=>a.name===t);n>=0&&(c=n,o.value=e[n].content,g())}function h(){let t=prompt("File name (e.g. helper.gno):");!t||!t.endsWith(".gno")||e.some(n=>n.name===t)||(e[c].content=o.value,e.push({name:t,content:`package main
+`}),c=e.length-1,o.value=e[c].content,g())}async function p(){e[c].content=o.value,i.textContent="Running...",i.classList.remove("u-color-danger");let t=o.value,n=t.match(/^package\s+(\w+)/m),a=n?n[1]:"main";if(t.includes("func Render("))try{let u=await(await fetch("/_/api/eval",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pkg_path:`${m}/r/playground_preview`,expression:'Render("")'})})).json();u.error?(i.textContent=`Error: ${u.error}`,i.classList.add("u-color-danger")):i.textContent=u.result}catch{i.textContent=`Note: Server-side execution not available for scratch pad code.
-Package: ${o}
-Files: ${this.files.map(s=>s.name).join(", ")}
+Package: ${a}
+Files: ${e.map(r=>r.name).join(", ")}
-To deploy and test, use:
- gnokey maketx addpkg -pkgpath "${e}/r/yourname/pkg" ...`,this._outputEl.classList.remove("u-color-danger")}else this._outputEl.textContent=`Package: ${o}
-Files: ${this.files.map(s=>s.name).join(", ")}
+To deploy and test:
+ gnokey maketx addpkg -pkgpath "${m}/r/yourname/pkg" ...`}else i.textContent=`Package: ${a}
+Files: ${e.map(r=>r.name).join(", ")}
To run locally:
- gno run ${this.files.map(s=>s.name).join(" ")}
+ gno run ${e.map(r=>r.name).join(" ")}
To test:
- gno test .`,this._outputEl.classList.remove("u-color-danger")}runTests(){this._outputEl.textContent=`Testing requires a running gno node.
+ gno test .`}function y(){i.textContent=`Testing requires a running gno node.
To test locally:
- gno test .`}formatCode(){this._outputEl.textContent=`Formatting requires server-side gno fmt (coming soon).
+ gno test .`}function C(){i.textContent=`Formatting requires server-side gno fmt (coming soon).
To format locally:
- gno fmt -w `+this.files[this.activeFile].name}shareCode(){this.files[this.activeFile].content=this._codeEl.value;let t=this.files.length===1?this.files[0].content:this.files.map(n=>`// --- ${n.name} ---
-${n.content}`).join(`
+ gno fmt -w `+e[c].name}function b(){e[c].content=o.value;let t=e.length===1?e[0].content:e.map(a=>`// --- ${a.name} ---
+${a.content}`).join(`
-`),e=encodeURIComponent(t),i=`${window.location.origin}/_/play?code=${e}`;navigator.clipboard.writeText(i).then(()=>{this._outputEl.textContent="Share URL copied to clipboard!"}).catch(()=>{this._outputEl.textContent=`Share URL:
-${i}`})}clearOutput(){this._outputEl.textContent="// Run code to see output here",this._outputEl.classList.remove("u-color-danger")}};export{l as PlaygroundController};
+`),n=`${window.location.origin}/_/play?code=${encodeURIComponent(t)}`;navigator.clipboard.writeText(n).then(()=>{i.textContent="Share URL copied to clipboard!"}).catch(()=>{i.textContent=`Share URL:
+${n}`})}function x(){i.textContent="// Run code to see output here",i.classList.remove("u-color-danger")}o.addEventListener("keydown",t=>{if(t.ctrlKey&&t.key==="Enter"){t.preventDefault(),p();return}if(t.key==="Tab"&&!t.shiftKey){t.preventDefault();let n=o.selectionStart,a=o.selectionEnd;o.value=o.value.substring(0,n)+" "+o.value.substring(a),o.selectionStart=o.selectionEnd=n+1}});let E={runCode:p,runTests:y,formatCode:C,shareCode:b,clearOutput:x};l.querySelectorAll("[data-action]").forEach(t=>{let n=t.getAttribute("data-action");if(!n)return;let a=n.match(/^(\w+)->playground#(\w+)$/);if(!a)return;let[,r,u]=a,f=E[u];f&&t.addEventListener(r,f)}),g()}export{T as default};
From 5f1febde45664f180019558910cc5f65803e9923 Mon Sep 17 00:00:00 2001
From: moul <94029+moul@users.noreply.github.com>
Date: Thu, 9 Apr 2026 16:21:15 +0200
Subject: [PATCH 07/40] fix(gnoweb): fix lint errors in playground and eval
controllers
- pre-allocate `funcs` slice in BuildEvalFuncs (prealloc lint)
- fix biome formatting in controller-eval.ts and controller-playground.ts
---
gno.land/pkg/gnoweb/components/view_eval.go | 2 +-
.../pkg/gnoweb/frontend/js/controller-eval.ts | 26 ++++++++---
.../frontend/js/controller-playground.ts | 44 ++++++++++++++-----
3 files changed, 54 insertions(+), 18 deletions(-)
diff --git a/gno.land/pkg/gnoweb/components/view_eval.go b/gno.land/pkg/gnoweb/components/view_eval.go
index 34acf93c1a2..f1f15b1f505 100644
--- a/gno.land/pkg/gnoweb/components/view_eval.go
+++ b/gno.land/pkg/gnoweb/components/view_eval.go
@@ -63,7 +63,7 @@ func EvalView(data EvalData) *View {
// BuildEvalFuncs extracts public non-method functions suitable for eval.
func BuildEvalFuncs(jdoc *doc.JSONDocumentation) []EvalFuncInfo {
- var funcs []EvalFuncInfo
+ funcs := make([]EvalFuncInfo, 0, len(jdoc.Funcs))
for _, fn := range jdoc.Funcs {
if fn.Type != "" || !token.IsExported(fn.Name) {
continue
diff --git a/gno.land/pkg/gnoweb/frontend/js/controller-eval.ts b/gno.land/pkg/gnoweb/frontend/js/controller-eval.ts
index 1d7d2fa0f6e..210d92c5698 100644
--- a/gno.land/pkg/gnoweb/frontend/js/controller-eval.ts
+++ b/gno.land/pkg/gnoweb/frontend/js/controller-eval.ts
@@ -6,9 +6,15 @@ interface HistoryEntry {
}
export default function EvalController(element: HTMLElement): void {
- const inputEl = element.querySelector('[data-eval-target="input"]') as HTMLInputElement;
- const resultEl = element.querySelector('[data-eval-target="result"]') as HTMLElement;
- const historyListEl = element.querySelector('[data-eval-target="history-list"]') as HTMLElement;
+ const inputEl = element.querySelector(
+ '[data-eval-target="input"]',
+ ) as HTMLInputElement;
+ const resultEl = element.querySelector(
+ '[data-eval-target="result"]',
+ ) as HTMLElement;
+ const historyListEl = element.querySelector(
+ '[data-eval-target="history-list"]',
+ ) as HTMLElement;
if (!inputEl || !resultEl) return;
@@ -57,7 +63,11 @@ export default function EvalController(element: HTMLElement): void {
}
}
- function addToHistory(expression: string, result: string, isError: boolean): void {
+ function addToHistory(
+ expression: string,
+ result: string,
+ isError: boolean,
+ ): void {
history.push({ expression, result, isError });
if (!historyListEl) return;
@@ -84,7 +94,8 @@ export default function EvalController(element: HTMLElement): void {
const resultPre = document.createElement("pre");
resultPre.className = `b-eval-history-result${isError ? " u-color-danger" : ""}`;
- resultPre.textContent = result.length > 200 ? result.substring(0, 200) + "..." : result;
+ resultPre.textContent =
+ result.length > 200 ? result.substring(0, 200) + "..." : result;
entry.appendChild(exprDiv);
entry.appendChild(resultPre);
@@ -146,7 +157,10 @@ export default function EvalController(element: HTMLElement): void {
})
.join(", ")})`;
inputEl.focus();
- inputEl.setSelectionRange(funcName.length + 1, inputEl.value.length - 1);
+ inputEl.setSelectionRange(
+ funcName.length + 1,
+ inputEl.value.length - 1,
+ );
} else {
inputEl.value = `${funcName}()`;
doEval(`${funcName}()`);
diff --git a/gno.land/pkg/gnoweb/frontend/js/controller-playground.ts b/gno.land/pkg/gnoweb/frontend/js/controller-playground.ts
index e201dadcf40..53581cdbbd8 100644
--- a/gno.land/pkg/gnoweb/frontend/js/controller-playground.ts
+++ b/gno.land/pkg/gnoweb/frontend/js/controller-playground.ts
@@ -8,13 +8,20 @@ export default function PlaygroundController(element: HTMLElement): void {
let files: PlaygroundFile[] = [];
let activeFile = 0;
- const codeEl = element.querySelector('[data-playground-target="code"]') as HTMLTextAreaElement;
- const outputEl = element.querySelector('[data-playground-target="output"]') as HTMLElement;
- const tabsEl = element.querySelector('[data-playground-target="tabs"]') as HTMLElement;
+ const codeEl = element.querySelector(
+ '[data-playground-target="code"]',
+ ) as HTMLTextAreaElement;
+ const outputEl = element.querySelector(
+ '[data-playground-target="output"]',
+ ) as HTMLElement;
+ const tabsEl = element.querySelector(
+ '[data-playground-target="tabs"]',
+ ) as HTMLElement;
if (!codeEl || !outputEl || !tabsEl) return;
- const domain = element.getAttribute("data-playground-domain-value") || "gno.land";
+ const domain =
+ element.getAttribute("data-playground-domain-value") || "gno.land";
// Parse initial code
const initialCode = codeEl.value;
@@ -25,7 +32,8 @@ export default function PlaygroundController(element: HTMLElement): void {
const content = (parts[i + 1] || "").trim();
if (name) files.push({ name, content });
}
- if (files.length === 0) files = [{ name: "main.gno", content: initialCode }];
+ if (files.length === 0)
+ files = [{ name: "main.gno", content: initialCode }];
codeEl.value = files[0].content;
} else {
files = [{ name: "main.gno", content: initialCode }];
@@ -104,7 +112,8 @@ export default function PlaygroundController(element: HTMLElement): void {
}
function runTests(): void {
- outputEl.textContent = "Testing requires a running gno node.\n\nTo test locally:\n gno test .";
+ outputEl.textContent =
+ "Testing requires a running gno node.\n\nTo test locally:\n gno test .";
}
function formatCode(): void {
@@ -122,8 +131,12 @@ export default function PlaygroundController(element: HTMLElement): void {
const url = `${window.location.origin}/_/play?code=${encodeURIComponent(code)}`;
navigator.clipboard
.writeText(url)
- .then(() => { outputEl.textContent = "Share URL copied to clipboard!"; })
- .catch(() => { outputEl.textContent = `Share URL:\n${url}`; });
+ .then(() => {
+ outputEl.textContent = "Share URL copied to clipboard!";
+ })
+ .catch(() => {
+ outputEl.textContent = `Share URL:\n${url}`;
+ });
}
function clearOutput(): void {
@@ -133,19 +146,28 @@ export default function PlaygroundController(element: HTMLElement): void {
// Keyboard shortcuts
codeEl.addEventListener("keydown", (e: KeyboardEvent) => {
- if (e.ctrlKey && e.key === "Enter") { e.preventDefault(); runCode(); return; }
+ if (e.ctrlKey && e.key === "Enter") {
+ e.preventDefault();
+ runCode();
+ return;
+ }
if (e.key === "Tab" && !e.shiftKey) {
e.preventDefault();
const start = codeEl.selectionStart;
const end = codeEl.selectionEnd;
- codeEl.value = codeEl.value.substring(0, start) + "\t" + codeEl.value.substring(end);
+ codeEl.value =
+ codeEl.value.substring(0, start) + "\t" + codeEl.value.substring(end);
codeEl.selectionStart = codeEl.selectionEnd = start + 1;
}
});
// Wire buttons by data-action attribute
const actions: Record void> = {
- runCode, runTests, formatCode, shareCode, clearOutput,
+ runCode,
+ runTests,
+ formatCode,
+ shareCode,
+ clearOutput,
};
element.querySelectorAll("[data-action]").forEach((el) => {
const attr = el.getAttribute("data-action");
From 6aba36f8b07bfca63893c11c6c6cbaca98849437 Mon Sep 17 00:00:00 2001
From: moul <94029+moul@users.noreply.github.com>
Date: Thu, 9 Apr 2026 16:31:05 +0200
Subject: [PATCH 08/40] docs(gnoweb): add ADR for integrated playground (PR
#5421)
---
gno.land/adr/pr5421_integrated_playground.md | 125 +++++++++++++++++++
1 file changed, 125 insertions(+)
create mode 100644 gno.land/adr/pr5421_integrated_playground.md
diff --git a/gno.land/adr/pr5421_integrated_playground.md b/gno.land/adr/pr5421_integrated_playground.md
new file mode 100644
index 00000000000..a5cf4aca09e
--- /dev/null
+++ b/gno.land/adr/pr5421_integrated_playground.md
@@ -0,0 +1,125 @@
+# ADR: Integrated Playground, Eval, and Fork Views in gnoweb
+
+## Context
+
+gno.land previously had no interactive code evaluation in gnoweb. Users who
+wanted to call realm functions or experiment with Gno code had to use external
+tools (gnokey CLI, a separate gnostudio/studio app, or copy-paste workflows).
+
+The key problems:
+
+1. **Friction for exploration.** Reading a realm's `Render` output or calling
+ a view function required setting up a local environment or using CLI tools.
+2. **No in-browser eval.** Users could not quickly test expressions against a
+ deployed package from the web UI.
+3. **No code scratch pad.** There was nowhere in gnoweb to write and share
+ short Gno snippets.
+4. **Separate studio dependency.** The only interactive option required running
+ a separate application, breaking the single-binary model.
+
+## Decision
+
+Add three interactive features to gnoweb as Go-native, single-binary
+extensions with no additional runtime dependencies:
+
+### 1. `/_/play` — Playground scratch pad
+
+A multi-file code editor backed by a plain `