diff --git a/examples/gno.land/r/docs/markdown/markdown.gno b/examples/gno.land/r/docs/markdown/markdown.gno index b1ce084cc24..98c3785723a 100644 --- a/examples/gno.land/r/docs/markdown/markdown.gno +++ b/examples/gno.land/r/docs/markdown/markdown.gno @@ -411,16 +411,19 @@ Create a form using the ±± tag and add inputs with ±±: - Must start with ±± and end with ±± - Optional attributes: - ±path±: Render path after form submission (e.g. ±path="user"± will redirect to ±:user?[params]±) + - ±exec±: Auto-generate fields from a realm function signature (e.g. ±exec="Render"±). + When set, manual fields are not allowed; inputs are derived from the function parameters. - Form data is always sent as query parameters - Cannot be nested (forms inside forms are not allowed) - Automatically includes a header showing the realm name 2. **Input Fields** - Created with ±± tag - - Required attributes: + - Attributes: - ±name±: Unique identifier for the input (required) - ±type±: Type of input (optional, defaults to "text") - ±placeholder±: Hint text shown in the input (optional, defaults to "Enter value") + - ±value±: Default value for non-selectable inputs (optional) - ±description±: Description of the input as title (optional - can be used as title for group of one or multiple inputs) - Supported input types: - ±type="text"± (default): For text input @@ -444,6 +447,7 @@ Create a form using the ±± tag and add inputs with ±±: - ±name±: Unique identifier for the textarea (required) - ±placeholder±: Hint text shown in the textarea (optional, defaults to "Enter value") - Optional attributes: + - ±value±: Default content for the textarea (optional) - ±rows±: Number of visible rows (2-10, defaults to 4) - Perfect for longer text input like messages, descriptions, or comments - Each textarea must have a unique name @@ -608,6 +612,32 @@ Select options work like radio buttons but with a more semantic meaning. All ±< This example shows how to combine all form element types in a single form. +8. Form with Default Values +±±±markdown + + + + + +±±± + + + + + + + +9. Exec-Generated Form +When using the ±exec± attribute, the form inputs are automatically generated from the realm function signature, and a command preview appears. Manual fields are not allowed in this mode. + +±±±markdown + + +±±± + + + + #### Important Rules 1. **Validation Rules**: diff --git a/gno.land/pkg/gnoweb/app.go b/gno.land/pkg/gnoweb/app.go index 6a4108230f5..5f74b2c29aa 100644 --- a/gno.land/pkg/gnoweb/app.go +++ b/gno.land/pkg/gnoweb/app.go @@ -121,7 +121,7 @@ func NewRouter(logger *slog.Logger, cfg *AppConfig) (http.Handler, error) { mdhtml.WithXHTML(), mdhtml.WithUnsafe(), )) } - renderer := NewHTMLRenderer(logger, rcfg) + renderer := NewHTMLRenderer(logger, rcfg, adpcli) // Configure HTTPHandler if cfg.Aliases == nil { diff --git a/gno.land/pkg/gnoweb/client.go b/gno.land/pkg/gnoweb/client.go index 97a3dfe53fc..46bff7f97e6 100644 --- a/gno.land/pkg/gnoweb/client.go +++ b/gno.land/pkg/gnoweb/client.go @@ -38,10 +38,13 @@ type ClientAdapter interface { // package path and filename. File(ctx context.Context, path, filename string) ([]byte, FileMeta, error) - // Sources lists all source files available in a specified + // ListFiles lists all source files available in a specified // package path. ListFiles(ctx context.Context, path string) ([]string, error) + // ListFuncs lists all exposed funcs in a specified package path. + ListFuncs(ctx context.Context, path string) (vm.FunctionSignatures, error) + // QueryPath list any path given the specified prefix ListPaths(ctx context.Context, prefix string, limit int) ([]string, error) @@ -159,18 +162,36 @@ func (c *rpcClient) Doc(ctx context.Context, pkgPath string) (*doc.JSONDocumenta args := fmt.Sprintf("%s/%s", c.domain, strings.Trim(pkgPath, "/")) res, err := c.query(ctx, qpath, []byte(args)) if err != nil { - return nil, fmt.Errorf("unable to query qdoc: %w", err) + return nil, fmt.Errorf("unable to query qdoc for %s: %w", pkgPath, err) } jdoc := &doc.JSONDocumentation{} if err := amino.UnmarshalJSON(res, jdoc); err != nil { c.logger.Warn("unable to unmarshal qdoc, client is probably outdated") - return nil, fmt.Errorf("unable to unmarshal qdoc: %w", err) + return nil, fmt.Errorf("unable to unmarshal qdoc for %s: %w", pkgPath, err) } return jdoc, nil } +func (c *rpcClient) ListFuncs(ctx context.Context, pkgPath string) (vm.FunctionSignatures, error) { + const qpath = "vm/qfuncs" + + args := fmt.Sprintf("%s/%s", c.domain, strings.Trim(pkgPath, "/")) + res, err := c.query(ctx, qpath, []byte(args)) + if err != nil { + return nil, fmt.Errorf("unable to query qfuncs for %s: %w", pkgPath, err) + } + + fsigs := vm.FunctionSignatures{} + if err := amino.UnmarshalJSON(res, &fsigs); err != nil { + c.logger.Warn("unable to unmarshal qfuncs, client is probably outdated") + return nil, fmt.Errorf("unable to unmarshal qfuncs for %s: %w", pkgPath, err) + } + + return fsigs, nil +} + // 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..9732ce5685a 100644 --- a/gno.land/pkg/gnoweb/client_mock.go +++ b/gno.land/pkg/gnoweb/client_mock.go @@ -7,6 +7,7 @@ import ( "sort" "strings" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/gnolang/gno/gnovm/pkg/doc" ) @@ -138,6 +139,46 @@ func (m *MockClient) Doc(ctx context.Context, path string) (*doc.JSONDocumentati return &doc.JSONDocumentation{Funcs: pkg.Functions}, nil } +// ListFuncs retrieves the function signatures for a specified package path. +func (m *MockClient) ListFuncs(ctx context.Context, path string) (vm.FunctionSignatures, error) { + if err := ctx.Err(); err != nil { + return nil, fmt.Errorf("context error: %w", err) + } + + pkg, exists := m.Packages[path] + if !exists { + return nil, ErrClientPackageNotFound + } + + // Convert JSONFunc to FunctionSignature + var sigs vm.FunctionSignatures + for _, fn := range pkg.Functions { + sig := vm.FunctionSignature{ + FuncName: fn.Name, + } + + // Convert parameters + for _, param := range fn.Params { + sig.Params = append(sig.Params, vm.NamedType{ + Name: param.Name, + Type: param.Type, + }) + } + + // Convert results + for _, result := range fn.Results { + sig.Results = append(sig.Results, vm.NamedType{ + Name: result.Name, + Type: result.Type, + }) + } + + sigs = append(sigs, sig) + } + + return sigs, 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/layouts/header.html b/gno.land/pkg/gnoweb/components/layouts/header.html index 0548aabc67d..54b865273b5 100644 --- a/gno.land/pkg/gnoweb/components/layouts/header.html +++ b/gno.land/pkg/gnoweb/components/layouts/header.html @@ -9,7 +9,7 @@ Gno username profile pic -
+
-{{ end }} \ No newline at end of file +{{ end }} diff --git a/gno.land/pkg/gnoweb/frontend/css/05-composition.css b/gno.land/pkg/gnoweb/frontend/css/05-composition.css index 1d8bc3348a9..0cfc528d02f 100644 --- a/gno.land/pkg/gnoweb/frontend/css/05-composition.css +++ b/gno.land/pkg/gnoweb/frontend/css/05-composition.css @@ -15,6 +15,18 @@ margin-block-start: var(--g-space-4); } +.c-inline { + display: inline-flex; + align-items: center; + gap: var(--g-space-3); +} + +.c-between { + display: flex; + justify-content: space-between; + align-items: center; +} + /* Center layout */ .c-center { box-sizing: border-box; diff --git a/gno.land/pkg/gnoweb/frontend/css/06-blocks.css b/gno.land/pkg/gnoweb/frontend/css/06-blocks.css index 68f1644af2b..6f12f1329d8 100644 --- a/gno.land/pkg/gnoweb/frontend/css/06-blocks.css +++ b/gno.land/pkg/gnoweb/frontend/css/06-blocks.css @@ -50,7 +50,7 @@ } /* ===== MAIN NAVIGATION ===== */ -.b-main-navivation { +.b-main-navigation { position: relative; height: auto; width: 100%; @@ -726,6 +726,7 @@ & > label { display: flex; + white-space: nowrap; align-items: center; gap: var(--g-space-3); padding: var(--g-space-1-5) var(--g-space-3); @@ -1270,10 +1271,13 @@ pre.chroma-chroma { } .btn-copy { + background-color: var(--g-color-transparent); + padding: 0; position: absolute; top: var(--g-space-2); right: var(--g-space-2); color: var(--s-color-text-tertiary); + cursor: pointer; &:hover { color: var(--s-color-text-primary); @@ -1973,7 +1977,6 @@ pre.chroma-chroma { display: block; border-radius: var(--s-rounded); border: var(--s-border-secondary); - padding: var(--g-space-2) var(--g-space-4); margin-block: var(--g-space-12); input[type="submit"] { @@ -1989,6 +1992,46 @@ pre.chroma-chroma { opacity: 0.9; } } + + .command { + margin-block-start: var(--g-space-6); + background-color: var(--s-color-bg-base-dev); + padding: var(--g-space-4); + + .title { + white-space: nowrap; + font-size: var(--g-font-size-200); + font-weight: var(--g-font-semibold); + } + + & > .b-code { + background-color: var(--s-color-bg-base-dev); + + & > pre { + background-color: var(--s-color-bg-base); + margin-block-end: 0; + } + } + + .c-inline, + .c-between { + flex-direction: column; + align-items: flex-start; + gap: var(--g-space-2); + @media (--md) { + align-items: center; + flex-direction: row; + } + + & > * { + width: 100%; + + @media (--md) { + width: auto; + } + } + } + } } .gno-form_header { @@ -1996,18 +2039,20 @@ pre.chroma-chroma { justify-content: space-between; color: var(--s-color-text-tertiary); font-size: var(--g-font-size-50); + padding: var(--g-space-2) var(--g-space-4) 0; margin-block-end: var(--g-space-6); } .gno-form_input, .gno-form_select { position: relative; + padding-inline: var(--g-space-4); label { position: absolute; display: none; top: 0; - left: var(--g-space-2); + left: var(--g-space-5); transform: translateY(-50%); color: var(--s-color-text-tertiary); background-color: var(--s-color-bg-base); @@ -2017,7 +2062,7 @@ pre.chroma-chroma { svg { position: absolute; - right: var(--g-space-2); + right: var(--g-space-6); top: 50%; transform: translateY(-50%); pointer-events: none; @@ -2076,6 +2121,7 @@ pre.chroma-chroma { color: var(--s-color-text-tertiary); margin-block-start: var(--g-space-5); margin-block-end: var(--g-space-2); + padding-inline-start: var(--g-space-4); font-weight: var(--g-font-semibold); } @@ -2083,6 +2129,7 @@ pre.chroma-chroma { display: flex; column-gap: var(--g-space-2); margin-block-end: var(--g-space-1); + padding-inline: var(--g-space-4); input { width: auto; diff --git a/gno.land/pkg/gnoweb/frontend/js/controller-action-function.ts b/gno.land/pkg/gnoweb/frontend/js/controller-action-function.ts index a2e0d9a82ff..dede0d2ac30 100644 --- a/gno.land/pkg/gnoweb/frontend/js/controller-action-function.ts +++ b/gno.land/pkg/gnoweb/frontend/js/controller-action-function.ts @@ -26,7 +26,6 @@ export class ActionFunctionController extends BaseController { this.on("mode:changed", (event: Event) => { const customEvent = event as CustomEvent; const mode: ActionMode = customEvent.detail.mode; - console.log("mode:changed", mode); this._updateAllFunctionsMode(mode); }); @@ -44,7 +43,6 @@ export class ActionFunctionController extends BaseController { const modeElements = this.getTargets("mode"); modeElements.forEach((modeElement) => { - console.log("modeElement in function", modeElement); const isVisible = this.getValue("mode", modeElement) === mode; modeElement.classList.toggle("u-inline", isVisible); modeElement.classList.toggle("u-hidden", !isVisible); diff --git a/gno.land/pkg/gnoweb/frontend/js/controller-form-exec.ts b/gno.land/pkg/gnoweb/frontend/js/controller-form-exec.ts new file mode 100644 index 00000000000..0382101a4b0 --- /dev/null +++ b/gno.land/pkg/gnoweb/frontend/js/controller-form-exec.ts @@ -0,0 +1,31 @@ +import { BaseController } from "./controller.js"; + +export class FormExecController extends BaseController { + protected connect(): void { + this.initializeDOM({}); + + // Find the form element within this controller's scope + // The form should be either the element itself or a descendant + const form = + this.element instanceof HTMLFormElement + ? this.element + : this.element.querySelector("form"); + + if (form) { + // Listen for submit events + form.addEventListener("submit", this._handleSubmit.bind(this)); + } + } + + // Handle form submission + private _handleSubmit(event: Event): void { + // Prevent the form from submitting - Extensions should handle the submission + event.preventDefault(); + event.stopPropagation(); + + const actionFunction = this.getTarget("command"); + if (actionFunction) { + actionFunction.classList.remove("u-hidden"); + } + } +} diff --git a/gno.land/pkg/gnoweb/handler_http.go b/gno.land/pkg/gnoweb/handler_http.go index b6bc75900f5..caee997f872 100644 --- a/gno.land/pkg/gnoweb/handler_http.go +++ b/gno.land/pkg/gnoweb/handler_http.go @@ -246,7 +246,10 @@ func (h *HTTPHandler) GetMarkdownView(gnourl *weburl.GnoURL, mdContent string) ( var content bytes.Buffer // Use Goldmark for Markdown parsing - toc, err := h.Renderer.RenderRealm(&content, gnourl, []byte(mdContent)) + toc, err := h.Renderer.RenderRealm(&content, gnourl, []byte(mdContent), RealmRenderContext{ + ChainId: h.Static.ChainId, + Remote: h.Static.RemoteHelp, + }) if err != nil { h.Logger.Error("unable to render markdown file", "error", err, "path", gnourl.EncodeURL()) return GetClientErrorStatusPage(gnourl, err) @@ -302,7 +305,10 @@ func (h *HTTPHandler) GetRealmView(ctx context.Context, gnourl *weburl.GnoURL, i } var content bytes.Buffer - meta, err := h.Renderer.RenderRealm(&content, gnourl, raw) + meta, err := h.Renderer.RenderRealm(&content, gnourl, raw, RealmRenderContext{ + ChainId: h.Static.ChainId, + Remote: h.Static.RemoteHelp, + }) if err != nil { h.Logger.Error("unable to render realm", "error", err, "path", gnourl.EncodeURL()) return GetClientErrorStatusPage(gnourl, err) @@ -377,7 +383,10 @@ func (h *HTTPHandler) GetUserView(ctx context.Context, gnourl *weburl.GnoURL) (i // Render user profile realm raw, err := h.Client.Realm(ctx, "/r/"+username+"/home", "") if err == nil { - _, err = h.Renderer.RenderRealm(&content, gnourl, raw) + _, err = h.Renderer.RenderRealm(&content, gnourl, raw, RealmRenderContext{ + ChainId: h.Static.ChainId, + Remote: h.Static.RemoteHelp, + }) } if content.Len() == 0 { @@ -399,7 +408,7 @@ func (h *HTTPHandler) GetUserView(ctx context.Context, gnourl *weburl.GnoURL) (i // Try to decode the bech32 address username = CreateUsernameFromBech32(username) - //TODO: get from user r/profile and use placeholder if not set + // TODO: get from user r/profile and use placeholder if not set handlename := "Gnome " + username data := components.UserData{ @@ -481,7 +490,10 @@ func (h *HTTPHandler) renderReadme(ctx context.Context, gnourl *weburl.GnoURL, p } var buf bytes.Buffer - if _, err := h.Renderer.RenderRealm(&buf, gnourl, file); err != nil { + if _, err := h.Renderer.RenderRealm(&buf, gnourl, file, RealmRenderContext{ + ChainId: h.Static.ChainId, + Remote: h.Static.RemoteHelp, + }); err != nil { h.Logger.Error("render README.md", "error", err) return nil, nil } diff --git a/gno.land/pkg/gnoweb/handler_http_test.go b/gno.land/pkg/gnoweb/handler_http_test.go index 29538812b80..480faa1cdd0 100644 --- a/gno.land/pkg/gnoweb/handler_http_test.go +++ b/gno.land/pkg/gnoweb/handler_http_test.go @@ -17,6 +17,7 @@ import ( "github.com/gnolang/gno/gno.land/pkg/gnoweb" md "github.com/gnolang/gno/gno.land/pkg/gnoweb/markdown" "github.com/gnolang/gno/gno.land/pkg/gnoweb/weburl" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/gnolang/gno/gnovm/pkg/doc" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -39,6 +40,7 @@ type stubClient struct { docFunc func(ctx context.Context, path string) (*doc.JSONDocumentation, error) listFilesFunc func(ctx context.Context, path string) ([]string, error) listPathsFunc func(ctx context.Context, prefix string, limit int) ([]string, error) + listFuncsFunc func(ctx context.Context, path string) (vm.FunctionSignatures, error) } func (s *stubClient) Realm(ctx context.Context, path, args string) ([]byte, error) { @@ -76,9 +78,16 @@ func (s *stubClient) ListPaths(ctx context.Context, prefix string, limit int) ([ return nil, errors.New("stubClient: ListPaths not implemented") } +func (s *stubClient) ListFuncs(ctx context.Context, path string) (vm.FunctionSignatures, error) { + if s.listFuncsFunc != nil { + return s.listFuncsFunc(ctx, path) + } + return nil, errors.New("stubClient: ListFuncs not implemented") +} + type rawRenderer struct{} -func (rawRenderer) RenderRealm(w io.Writer, u *weburl.GnoURL, src []byte) (md.Toc, error) { +func (rawRenderer) RenderRealm(w io.Writer, u *weburl.GnoURL, src []byte, ctx gnoweb.RealmRenderContext) (md.Toc, error) { _, err := w.Write(src) return md.Toc{}, err } diff --git a/gno.land/pkg/gnoweb/markdown/context.go b/gno.land/pkg/gnoweb/markdown/context.go index 767c5c6a9d0..5465455a61f 100644 --- a/gno.land/pkg/gnoweb/markdown/context.go +++ b/gno.land/pkg/gnoweb/markdown/context.go @@ -2,20 +2,66 @@ package markdown import ( "github.com/gnolang/gno/gno.land/pkg/gnoweb/weburl" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/yuin/goldmark/parser" ) -var gUrlContextKey = parser.NewContextKey() +var ( + gUrlContextKey = parser.NewContextKey() + gRealmFuncsContextKey = parser.NewContextKey() + gChainIdContextKey = parser.NewContextKey() + gRemoteContextKey = parser.NewContextKey() +) + +type RealmFuncSigGetter func(fn string) (*vm.FunctionSignature, error) + +type GnoContext struct { + GnoURL *weburl.GnoURL + RealmFuncSigGetter RealmFuncSigGetter + ChainId string + Remote string +} // NewGnoParserContext creates a new parser context with GnoURL -func NewGnoParserContext(url *weburl.GnoURL) parser.Context { +func NewGnoParserContext(mdctx GnoContext) parser.Context { ctx := parser.NewContext() - ctx.Set(gUrlContextKey, *url) + ctx.Set(gUrlContextKey, mdctx.GnoURL) + ctx.Set(gRealmFuncsContextKey, mdctx.RealmFuncSigGetter) + ctx.Set(gChainIdContextKey, mdctx.ChainId) + ctx.Set(gRemoteContextKey, mdctx.Remote) return ctx } // getUrlFromContext retrieves the GnoURL from the parser context -func getUrlFromContext(ctx parser.Context) (url weburl.GnoURL, ok bool) { - url, ok = ctx.Get(gUrlContextKey).(weburl.GnoURL) +func getUrlFromContext(ctx parser.Context) (url *weburl.GnoURL, ok bool) { + if url, ok = ctx.Get(gUrlContextKey).(*weburl.GnoURL); url == nil { + return nil, false + } + + return +} + +// getRealmFuncsGetter retrieves the Sigs of the given function +func getRealmFuncsGetterFromContext(ctx parser.Context) (gfuncs RealmFuncSigGetter, ok bool) { + if gfuncs, ok = ctx.Get(gRealmFuncsContextKey).(RealmFuncSigGetter); gfuncs == nil { + return nil, false + } + + return +} + +// getChainIdFromContext retrieves the ChainId from the parser context +func getChainIdFromContext(ctx parser.Context) (chainId string, ok bool) { + if chainId, ok = ctx.Get(gChainIdContextKey).(string); !ok { + return "", false + } + return +} + +// getRemoteFromContext retrieves the Remote from the parser context +func getRemoteFromContext(ctx parser.Context) (remote string, ok bool) { + if remote, ok = ctx.Get(gRemoteContextKey).(string); !ok { + return "", false + } return } diff --git a/gno.land/pkg/gnoweb/markdown/ext_forms.go b/gno.land/pkg/gnoweb/markdown/ext_forms.go index 641950f27e7..b0590297b00 100644 --- a/gno.land/pkg/gnoweb/markdown/ext_forms.go +++ b/gno.land/pkg/gnoweb/markdown/ext_forms.go @@ -6,6 +6,8 @@ import ( "fmt" "strings" + "github.com/gnolang/gno/gno.land/pkg/gnoweb/components" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/yuin/goldmark" "github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/parser" @@ -15,458 +17,402 @@ import ( "golang.org/x/net/html" ) -var KindForm = ast.NewNodeKind("Form") - +// Form-specific constants const ( - defaultInputType = "text" - defaultPlaceholder = "Enter value" - defaultTextareaRows = 4 - defaultTextareaMinRows = 2 - defaultTextareaMaxRows = 10 - - // HTML tag names - tagGnoForm = "gno-form" - tagGnoInput = "gno-input" - tagGnoTextarea = "gno-textarea" - tagGnoSelect = "gno-select" + formTagName = "gno-form" + formInputTag = "gno-input" + formTextareaTag = "gno-textarea" + formSelectTag = "gno-select" + + formDefaultInputType = "text" + formDefaultPlaceholder = "Enter value" + formDefaultTextareaRows = 4 + formMinTextareaRows = 2 + formMaxTextareaRows = 10 ) +// Form-specific errors var ( - ErrFormUnexpectedOrInvalidTag = errors.New("unexpected or invalid tag") - ErrFormInputMissingName = errors.New(tagGnoInput + " must have a 'name' attribute") - ErrFormInvalidInputType = errors.New("invalid input type") - ErrFormInputNameAlreadyUsed = errors.New("input name already used") + ErrFormInvalidTag = errors.New("unexpected or invalid tag") + ErrFormMissingName = errors.New("missing 'name' attribute") + ErrFormInvalidInputType = errors.New("invalid input type") + ErrFormDuplicateName = errors.New("name already used") + ErrFormInvalidAttribute = errors.New("invalid attribute for input type") + ErrFormMissingValue = errors.New("missing 'value' attribute") + ErrFormParameterNotFound = errors.New("parameter not found in function") + ErrFormUnsupportedType = errors.New("unsupported parameter type") ) -// Whitelist of allowed input types -var allowedInputTypes = map[string]bool{ - "text": true, - "number": true, - "email": true, - "tel": true, - "password": true, - "radio": true, - "checkbox": true, -} - -// validateInputType checks if the input type is allowed -func validateInputType(inputType string) bool { - if inputType == "" { - return true // Empty type will use default +var ( + FormKind = ast.NewNodeKind("Form") + + formAllowedInputTypes = map[string]bool{ + "text": true, + "number": true, + "email": true, + "tel": true, + "password": true, + "radio": true, + "checkbox": true, } +) - return allowedInputTypes[inputType] +// FormElement represents any form element +type FormElement interface { + GetName() string + GetError() error + String() string } +// FormInput represents an input element type FormInput struct { - Error error Name string Type string Placeholder string - Value string // For radio/checkbox values - Checked bool // For radio/checkbox checked state - Description string // New attribute for description span + Value string + Checked bool + Description string + Error error } +func (e FormInput) GetName() string { return e.Name } +func (e FormInput) GetError() error { return e.Error } +func (e FormInput) String() string { + if e.Error != nil { + return fmt.Sprintf("(err=%s)", e.Error) + } + s := fmt.Sprintf("(name=%s) (type=%s)", e.Name, e.Type) + if e.Type != "radio" && e.Type != "checkbox" { + s += fmt.Sprintf(" (placeholder=%s)", e.Placeholder) + } else { + s += fmt.Sprintf(" (value=%s) (checked=%t)", e.Value, e.Checked) + } + if e.Description != "" { + s += fmt.Sprintf(" (description=%s)", e.Description) + } + return s +} + +// FormTextarea represents a textarea element type FormTextarea struct { - Error error Name string Placeholder string Rows int - Description string // New attribute for description span -} - -type FormSelect struct { + Value string + Description string Error error - Name string - Value string // Value and display text for this option - Selected bool // Whether this option is selected - Description string // New attribute for description span } -// FormElement interface for form elements -type FormElement interface { - GetError() error -} - -func (in *FormInput) GetError() error { return in.Error } -func (ta *FormTextarea) GetError() error { return ta.Error } -func (sel *FormSelect) GetError() error { return sel.Error } - -func (in FormInput) String() string { - if in.Error != nil { - return fmt.Sprintf("(err=%s)", in.Error) - } - - base := fmt.Sprintf("(name=%s) (type=%s) (placeholder=%s)", - in.Name, in.Type, in.Placeholder) - - if in.Type == "radio" || in.Type == "checkbox" { - base += fmt.Sprintf(" (value=%s) (checked=%t)", in.Value, in.Checked) +func (e FormTextarea) GetName() string { return e.Name } +func (e FormTextarea) GetError() error { return e.Error } +func (e FormTextarea) String() string { + if e.Error != nil { + return fmt.Sprintf("(err=%s)", e.Error) } - - if in.Description != "" { - base += fmt.Sprintf(" (description=%s)", in.Description) + s := fmt.Sprintf("(name=%s) (placeholder=%s) (rows=%d)", e.Name, e.Placeholder, e.Rows) + if e.Description != "" { + s += fmt.Sprintf(" (description=%s)", e.Description) } - - return base + return s } -func (ta FormTextarea) String() string { - if ta.Error != nil { - return fmt.Sprintf("(err=%s)", ta.Error) - } - - base := fmt.Sprintf("(name=%s) (placeholder=%s) (rows=%d)", ta.Name, ta.Placeholder, ta.Rows) - - if ta.Description != "" { - base += fmt.Sprintf(" (description=%s)", ta.Description) - } - - return base +// FormSelect represents a select option +type FormSelect struct { + Name string + Value string + Selected bool + Description string + Error error } -func (sel FormSelect) String() string { - if sel.Error != nil { - return fmt.Sprintf("(err=%s)", sel.Error) +func (e FormSelect) GetName() string { return e.Name } +func (e FormSelect) GetError() error { return e.Error } +func (e FormSelect) String() string { + if e.Error != nil { + return fmt.Sprintf("(err=%s)", e.Error) } - - base := fmt.Sprintf("(name=%s) (value=%s) (selected=%t)", - sel.Name, sel.Value, sel.Selected) - - if sel.Description != "" { - base += fmt.Sprintf(" (description=%s)", sel.Description) + s := fmt.Sprintf("(name=%s) (value=%s) (selected=%t)", e.Name, e.Value, e.Selected) + if e.Description != "" { + s += fmt.Sprintf(" (description=%s)", e.Description) } - - return base + return s } +// FormNode represents a form in the AST type FormNode struct { ast.BaseBlock - Error error - Elements []FormElement - ElementsName map[string]bool - RenderPath string // Path to render after form submission - RealmName string -} - -func NewFormNode() *FormNode { - return &FormNode{ - ElementsName: map[string]bool{}, - } + Elements []FormElement + Exec *vm.FunctionSignature + RenderPath string + RealmName string + ChainId string + Remote string + Error error + usedNames map[string]bool } -func (n *FormNode) Kind() ast.NodeKind { return KindForm } +func (n *FormNode) Kind() ast.NodeKind { return FormKind } -// Dump displays the information level for the Form node func (n *FormNode) Dump(source []byte, level int) { kv := map[string]string{ "path": n.RenderPath, "name": n.RealmName, } - for i, element := range n.Elements { - switch e := element.(type) { - case *FormInput: - kv[fmt.Sprintf("input_%d", i)] = e.String() - case *FormTextarea: - kv[fmt.Sprintf("textarea_%d", i)] = e.String() - case *FormSelect: - kv[fmt.Sprintf("select_%d", i)] = e.String() - } + kv[fmt.Sprintf("element_%d", i)] = element.String() } - ast.DumpHelper(n, source, level, kv, nil) } -func (n *FormNode) NewInput() (input *FormInput) { - input = &FormInput{} - n.Elements = append(n.Elements, input) - return input -} - -func (n *FormNode) NewErrorInput(err error) (input *FormInput) { - input = n.NewInput() - input.Error = err - return input -} - -func (n *FormNode) NewTextarea() (textarea *FormTextarea) { - textarea = &FormTextarea{} - n.Elements = append(n.Elements, textarea) - return textarea +func (n *FormNode) addElement(elem FormElement) { + n.Elements = append(n.Elements, elem) } -func (n *FormNode) NewErrorTextarea(err error) (textarea *FormTextarea) { - textarea = n.NewTextarea() - textarea.Error = err - return textarea -} - -func (n *FormNode) NewSelect() (sel *FormSelect) { - sel = &FormSelect{} - n.Elements = append(n.Elements, sel) - return sel -} - -func (n *FormNode) NewErrorSelect(err error) (sel *FormSelect) { - sel = n.NewSelect() - sel.Error = err - return sel -} - -// parseFormTag parses a form tag and returns the tag information -func parseFormTag(line []byte) (tok html.Token, ok bool) { - line = bytes.TrimSpace(line) - if len(line) > 0 { - toks, err := ParseHTMLTokens(bytes.NewReader(line)) - if err == nil && len(toks) == 1 { - return toks[0], true +func (n *FormNode) validateName(name string, elemType string) error { + if name == "" { + return ErrFormMissingName + } + // Allow duplicate names for radio and checkbox inputs + if elemType != "radio" && elemType != "checkbox" { + if n.usedNames[name] { + return fmt.Errorf("%q: %w", name, ErrFormDuplicateName) } + n.usedNames[name] = true } - - return + return nil } -// formParser starts a block as soon as we encounter "" -// and closes it as soon as we encounter "". -// In between, only lines are processed. -type formParser struct{} - -var _ parser.BlockParser = (*formParser)(nil) +// FormParser handles parsing of form blocks +type FormParser struct{} -// NewFormParser creates a new instance of formParser -func NewFormParser() *formParser { - return &formParser{} -} +func NewFormParser() *FormParser { return &FormParser{} } -// Trigger detects the start of the block -func (p *formParser) Trigger() []byte { - return []byte{'<'} -} +func (p *FormParser) Trigger() []byte { return []byte{'<'} } -// Open starts a block only when the line is exactly "" -func (p *formParser) Open(parent ast.Node, reader text.Reader, pc parser.Context) (ast.Node, parser.State) { +func (p *FormParser) Open(parent ast.Node, reader text.Reader, pc parser.Context) (ast.Node, parser.State) { line, _ := reader.PeekLine() - tok, valid := parseFormTag(line) - if !valid || tok.Data != tagGnoForm { + tok, ok := parseFormTag(line) + if !ok || tok.Data != formTagName || tok.Type != html.StartTagToken { return nil, parser.NoChildren } - fn := NewFormNode() + node := &FormNode{usedNames: make(map[string]bool)} - if tok.Type != html.StartTagToken { - fn.Error = ErrFormUnexpectedOrInvalidTag - return fn, parser.NoChildren // skip, not our tag + // Extract attributes + node.RenderPath, _ = ExtractAttr(tok.Attr, "path") + if gnourl, ok := getUrlFromContext(pc); ok { + node.RealmName = gnourl.Path } - fn.RenderPath, _ = ExtractAttr(tok.Attr, "path") - if gnourl, ok := getUrlFromContext(pc); ok { - fn.RealmName = gnourl.Path // Use full path instead of just namespace + // Get ChainId and Remote from context + if chainId, ok := getChainIdFromContext(pc); ok { + node.ChainId = chainId + } + if remote, ok := getRemoteFromContext(pc); ok { + node.Remote = remote + } + + // Handle exec attribute + if exec, ok := ExtractAttr(tok.Attr, "exec"); ok { + if sigGetter, ok := getRealmFuncsGetterFromContext(pc); ok { + sig, err := sigGetter(exec) + if err != nil || sig == nil { + node.Error = ErrFormInvalidTag + } else { + node.Exec = sig + } + } } - return fn, parser.Continue + return node, parser.Continue } -// Continue processes lines until "" is found. -// When a line contains , it adds a child node. -func (p *formParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State { +func (p *FormParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State { line, _ := reader.PeekLine() - if line = bytes.TrimSpace(line); len(line) == 0 { - return parser.Continue // skip empty line + line = bytes.TrimSpace(line) + if len(line) == 0 { + return parser.Continue } formNode := node.(*FormNode) - - tok, valid := parseFormTag(line) - if !valid { - formNode.NewErrorInput(ErrFormUnexpectedOrInvalidTag) + tok, ok := parseFormTag(line) + if !ok { + formNode.addElement(FormInput{Error: ErrFormInvalidTag}) return parser.Continue } - if tok.Data == tagGnoForm { + // Check for closing tag + if tok.Data == formTagName { if tok.Type == html.EndTagToken { reader.AdvanceLine() - return parser.Close // done + return parser.Close } - - formNode.NewErrorInput(ErrFormUnexpectedOrInvalidTag) - return parser.Continue - } - - if tok.Data != tagGnoInput && tok.Data != tagGnoTextarea && tok.Data != tagGnoSelect { - formNode.NewErrorInput(ErrFormUnexpectedOrInvalidTag) + formNode.addElement(FormInput{Error: ErrFormInvalidTag}) return parser.Continue } + // Process form elements switch tok.Data { - case tagGnoInput: - formInput := formNode.NewInput() - if tok.Type != html.SelfClosingTagToken { - formNode.NewErrorInput(ErrFormUnexpectedOrInvalidTag) // XXX: use better error - return parser.Continue - } + case formInputTag: + p.parseInput(formNode, tok) + case formTextareaTag: + p.parseTextarea(formNode, tok) + case formSelectTag: + p.parseSelect(formNode, tok) + default: + formNode.addElement(FormInput{Error: ErrFormInvalidTag}) + } - for _, attr := range tok.Attr { - switch attr.Key { - case "name": - formInput.Name = strings.TrimSpace(attr.Val) - case "placeholder": - formInput.Placeholder = strings.TrimSpace(attr.Val) - case "description": - formInput.Description = strings.TrimSpace(attr.Val) - case "type": - formInput.Type = strings.TrimSpace(attr.Val) - case "value": - // Value is required for radio and checkbox, optional for other types - formInput.Value = strings.TrimSpace(attr.Val) - case "checked": - // Checked is only valid for radio and checkbox - if formInput.Type != "" && formInput.Type != "radio" && formInput.Type != "checkbox" { - formInput.Error = fmt.Errorf("'checked' attribute is only valid for radio and checkbox inputs, not for type '%s'", formInput.Type) - return parser.Continue - } - formInput.Checked = strings.TrimSpace(attr.Val) == "true" - } - } + return parser.Continue +} - if formInput.Name == "" { - formInput.Error = ErrFormInputMissingName - return parser.Continue - } +func (p *FormParser) parseInput(node *FormNode, tok html.Token) { + if tok.Type != html.SelfClosingTagToken { + node.addElement(FormInput{Error: ErrFormInvalidTag}) + return + } - // For radio and checkbox, allow same name (needed for groups) - // For other types, ensure unique names - if formInput.Type != "radio" && formInput.Type != "checkbox" { - if formNode.ElementsName[formInput.Name] { - formInput.Error = fmt.Errorf("%q: %w", formInput.Name, ErrFormInputNameAlreadyUsed) - return parser.Continue - } - formNode.ElementsName[formInput.Name] = true - } + input := FormInput{Type: formDefaultInputType} + attrs := make(map[string]string) - // Set default placeholder only for non-radio/checkbox inputs - if formInput.Placeholder == "" && formInput.Type != "radio" && formInput.Type != "checkbox" { - formInput.Placeholder = defaultPlaceholder - } + // Collect attributes + for _, attr := range tok.Attr { + attrs[attr.Key] = strings.TrimSpace(attr.Val) + } - if formInput.Type == "" { - formInput.Type = defaultInputType - } else if !validateInputType(formInput.Type) { - formInput.Error = ErrFormInvalidInputType - return parser.Continue - } + // Process attributes + input.Name = attrs["name"] + if t := attrs["type"]; t != "" { + input.Type = t + } + input.Placeholder = attrs["placeholder"] + input.Description = attrs["description"] + input.Value = attrs["value"] + input.Checked = attrs["checked"] == "true" + + // Validate + if err := node.validateName(input.Name, input.Type); err != nil { + input.Error = err + node.addElement(input) + return + } - // Check if value is required for radio and checkbox - if (formInput.Type == "radio" || formInput.Type == "checkbox") && formInput.Value == "" { - formInput.Error = fmt.Errorf("'value' attribute is required for %s inputs", formInput.Type) - return parser.Continue - } + if !formAllowedInputTypes[input.Type] { + input.Error = ErrFormInvalidInputType + node.addElement(input) + return + } - case tagGnoTextarea: - formTextarea := formNode.NewTextarea() - if tok.Type != html.SelfClosingTagToken { - formNode.NewErrorTextarea(ErrFormUnexpectedOrInvalidTag) - return parser.Continue - } + // Type-specific validation + isSelectable := input.Type == "radio" || input.Type == "checkbox" - for _, attr := range tok.Attr { - switch attr.Key { - case "name": - formTextarea.Name = strings.TrimSpace(attr.Val) - case "placeholder": - formTextarea.Placeholder = strings.TrimSpace(attr.Val) - case "rows": - if _, err := fmt.Sscanf(attr.Val, "%d", &formTextarea.Rows); err != nil { - formTextarea.Rows = defaultTextareaRows // default rows for textarea - } else if formTextarea.Rows < defaultTextareaMinRows { - formTextarea.Rows = defaultTextareaMinRows // min rows for textarea - } else if formTextarea.Rows > defaultTextareaMaxRows { - formTextarea.Rows = defaultTextareaMaxRows // max rows for textarea - } - case "description": - formTextarea.Description = strings.TrimSpace(attr.Val) - } - } + if attrs["checked"] != "" && !isSelectable { + input.Error = fmt.Errorf("'checked' attribute: %w for type '%s'", ErrFormInvalidAttribute, input.Type) + node.addElement(input) + return + } - if formTextarea.Name == "" { - formTextarea.Error = ErrFormInputMissingName - return parser.Continue - } + if isSelectable && input.Value == "" { + input.Error = fmt.Errorf("%w for %s input", ErrFormMissingValue, input.Type) + node.addElement(input) + return + } - if formNode.ElementsName[formTextarea.Name] { - formTextarea.Error = fmt.Errorf("%q: %w", formTextarea.Name, ErrFormInputNameAlreadyUsed) - return parser.Continue - } - formNode.ElementsName[formTextarea.Name] = true + // Set defaults + if input.Placeholder == "" && !isSelectable { + input.Placeholder = formDefaultPlaceholder + } - if formTextarea.Placeholder == "" { - formTextarea.Placeholder = defaultPlaceholder - } + node.addElement(input) +} - // Set default rows if not specified - if formTextarea.Rows == 0 { - formTextarea.Rows = defaultTextareaRows - } +func (p *FormParser) parseTextarea(node *FormNode, tok html.Token) { + if tok.Type != html.SelfClosingTagToken { + node.addElement(FormTextarea{Error: ErrFormInvalidTag}) + return + } - case tagGnoSelect: - formSelect := formNode.NewSelect() - if tok.Type != html.SelfClosingTagToken { - formNode.NewErrorSelect(ErrFormUnexpectedOrInvalidTag) - return parser.Continue - } + textarea := FormTextarea{ + Placeholder: formDefaultPlaceholder, + Rows: formDefaultTextareaRows, + } - for _, attr := range tok.Attr { - switch attr.Key { - case "name": - formSelect.Name = strings.TrimSpace(attr.Val) - case "value": - formSelect.Value = strings.TrimSpace(attr.Val) - case "description": - formSelect.Description = strings.TrimSpace(attr.Val) - case "selected": - formSelect.Selected = strings.TrimSpace(attr.Val) == "true" + // Process attributes + for _, attr := range tok.Attr { + switch attr.Key { + case "name": + textarea.Name = strings.TrimSpace(attr.Val) + case "placeholder": + if p := strings.TrimSpace(attr.Val); p != "" { + textarea.Placeholder = p } + case "value": + textarea.Value = strings.TrimSpace(attr.Val) + case "rows": + if _, err := fmt.Sscanf(attr.Val, "%d", &textarea.Rows); err != nil { + textarea.Rows = formDefaultTextareaRows + } + // Clamp rows value + if textarea.Rows < formMinTextareaRows { + textarea.Rows = formMinTextareaRows + } else if textarea.Rows > formMaxTextareaRows { + textarea.Rows = formMaxTextareaRows + } + case "description": + textarea.Description = strings.TrimSpace(attr.Val) } + } - if formSelect.Name == "" { - formSelect.Error = ErrFormInputMissingName - return parser.Continue - } - - default: - formNode.NewErrorInput(ErrFormUnexpectedOrInvalidTag) - return parser.Continue + // Validate + if err := node.validateName(textarea.Name, "textarea"); err != nil { + textarea.Error = err } - // Continue with the next line - return parser.Continue + node.addElement(textarea) } -// Close closes the block -func (p *formParser) Close(node ast.Node, reader text.Reader, pc parser.Context) {} +func (p *FormParser) parseSelect(node *FormNode, tok html.Token) { + if tok.Type != html.SelfClosingTagToken { + node.addElement(FormSelect{Error: ErrFormInvalidTag}) + return + } -func (p *formParser) CanInterruptParagraph() bool { return true } -func (p *formParser) CanAcceptIndentedLine() bool { return true } + sel := FormSelect{} + + for _, attr := range tok.Attr { + switch attr.Key { + case "name": + sel.Name = strings.TrimSpace(attr.Val) + case "value": + sel.Value = strings.TrimSpace(attr.Val) + case "selected": + sel.Selected = strings.TrimSpace(attr.Val) == "true" + case "description": + sel.Description = strings.TrimSpace(attr.Val) + } + } -// formRenderer renders the Form node. -// When entering the Form node, it displays the opening
tag -// and when exiting (after rendering the child inputs), -// it displays the submit button and
. -type formRenderer struct{} + if sel.Name == "" { + sel.Error = ErrFormMissingName + } -// NewFormRenderer creates a new instance of formRenderer -func NewFormRenderer() *formRenderer { - return &formRenderer{} + node.addElement(sel) } -// RegisterFuncs registers the render function for the Form node -func (r *formRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { - reg.Register(KindForm, r.render) +func (p *FormParser) Close(node ast.Node, reader text.Reader, pc parser.Context) {} +func (p *FormParser) CanInterruptParagraph() bool { return true } +func (p *FormParser) CanAcceptIndentedLine() bool { return true } + +// FormRenderer handles rendering of form nodes +type FormRenderer struct{} + +func NewFormRenderer() *FormRenderer { return &FormRenderer{} } + +func (r *FormRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { + reg.Register(FormKind, r.render) } -// render renders the Form node -func (r *formRenderer) render(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { +func (r *FormRenderer) render(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { if !entering { return ast.WalkContinue, nil } @@ -481,182 +427,342 @@ func (r *formRenderer) render(w util.BufWriter, source []byte, node ast.Node, en return ast.WalkContinue, nil } - // Form action must include the full path - formAction := n.RealmName // start with /r/docs/markdown + // Build form action + action := n.RealmName if n.RenderPath != "" { - formAction += ":" + strings.TrimPrefix(n.RenderPath, "/") + action += ":" + strings.TrimPrefix(n.RenderPath, "/") } - // Render form opening and header - fmt.Fprintf(w, `
`+"\n", HTMLEscapeString(formAction)) - fmt.Fprintln(w, `
`) - fmt.Fprintf(w, `%s Form`+"\n", HTMLEscapeString(n.RealmName)) - fmt.Fprintf(w, ``+"\n", HTMLEscapeString(n.RealmName)) - fmt.Fprintln(w, `
`) + // Render form opening + fmt.Fprintf(w, ``+"\n") + headerLabel := "Form" + if n.Exec != nil { + headerLabel = fmt.Sprintf("Exec: %s", HTMLEscapeString(titleCase(n.Exec.FuncName))) + } + fmt.Fprintf(w, `
+%s %s + +
+`, HTMLEscapeString(n.RealmName), headerLabel, HTMLEscapeString(n.RealmName)) + + if n.Exec != nil { + fmt.Fprintf(w, `
`+"\n", HTMLEscapeString(n.Exec.FuncName)) + } - // Render all form elements in order of appearance - lastDescID := "" // Track the last description ID for aria-labelledby fallback + // Track select elements that have been rendered + renderedSelects := make(map[string]bool) + lastDescID := "" - for i, element := range n.Elements { - if element.GetError() != nil { - fmt.Fprintf(w, "\n", HTMLEscapeString(element.GetError().Error())) + // Render elements + for i, elem := range n.Elements { + if elem.GetError() != nil { + fmt.Fprintf(w, "\n", HTMLEscapeString(elem.GetError().Error())) continue } - switch e := element.(type) { - case *FormInput: - // Show description span if available - if e.Description != "" { - descID := fmt.Sprintf("desc_%s_%d", e.Name, i) - fmt.Fprintf(w, `
%s
`+"\n", - HTMLEscapeString(descID), HTMLEscapeString(e.Description)) - lastDescID = descID // Update last description ID + switch e := elem.(type) { + case FormInput: + r.renderInput(w, e, i, &lastDescID, n.Exec != nil) + case FormTextarea: + r.renderTextarea(w, e, i, &lastDescID) + case FormSelect: + if !renderedSelects[e.Name] { + r.renderSelect(w, n.Elements, e, i, &lastDescID) + renderedSelects[e.Name] = true } + } + } - // Render different input types - switch e.Type { - case "radio", "checkbox": - // Generate unique ID for radio/checkbox using index - uniqueID := fmt.Sprintf("%s_%d", e.Name, i) - - fmt.Fprintf(w, `
`+"\n") - fmt.Fprintf(w, ``+"\n") - - // Build label text: value + placeholder (if available) - labelText := e.Value - if e.Placeholder != "" { - labelText += " - " + e.Placeholder - } - - fmt.Fprintf(w, ``+"\n", - HTMLEscapeString(uniqueID), - HTMLEscapeString(labelText)) - fmt.Fprintln(w, "
") + // Submit button + if len(n.Elements) > 0 { + if n.Exec != nil { + fmt.Fprintf(w, `
`+"\n", + HTMLEscapeString(titleCase(n.Exec.FuncName))) + } else { + fmt.Fprintf(w, `
`+"\n", + HTMLEscapeString(n.RealmName)) + } + } - default: - // Render standard input - fmt.Fprintf(w, `
`+"\n", - HTMLEscapeString(e.Name), - HTMLEscapeString(e.Placeholder)) - fmt.Fprintf(w, ``+"\n", - HTMLEscapeString(e.Type), - HTMLEscapeString(e.Name), - HTMLEscapeString(e.Name), - HTMLEscapeString(e.Placeholder)) - fmt.Fprintln(w, "
") - } + // Add command block if we have an exec function + if n.Exec != nil { + fmt.Fprintf(w, `
`) + // Add mode and address controls if we have an exec function + fmt.Fprintf(w, `
+ Command +
+
+ + +
+
+ + +
+
+
`, HTMLEscapeString(n.Exec.FuncName), HTMLEscapeString(n.Exec.FuncName)) + r.renderCommandBlock(w, n) + fmt.Fprintln(w, `
`) + fmt.Fprintln(w, `
`) + } + + fmt.Fprintln(w, "
") + + return ast.WalkContinue, nil +} + +func (r *FormRenderer) renderCommandBlock(w util.BufWriter, n *FormNode) { + // Use default values if not set + chainId := n.ChainId + if chainId == "" { + chainId = "dev" + } + remote := n.Remote + if remote == "" { + remote = "127.0.0.1:26657" + } + + // Extract parameter names + paramNames := make([]string, len(n.Exec.Params)) + + for i, param := range n.Exec.Params { + paramNames[i] = param.Name + } + + // Prepare data for the command template + data := components.CommandData{ + FuncName: n.Exec.FuncName, + PkgPath: n.RealmName, + ParamNames: paramNames, + ChainId: chainId, + Remote: remote, + } + + // Create and render the template component + comp := components.NewTemplateComponent("ui/command", data) + if err := comp.Render(w); err != nil { + fmt.Fprintf(w, "\n", HTMLEscapeString(err.Error())) + } +} + +func (r *FormRenderer) renderInput(w util.BufWriter, e FormInput, idx int, lastDescID *string, isExec bool) { + // Description + if e.Description != "" { + descID := fmt.Sprintf("desc_%s_%d", e.Name, idx) + fmt.Fprintf(w, `
%s
`+"\n", + HTMLEscapeString(descID), HTMLEscapeString(e.Description)) + *lastDescID = descID + } - case *FormTextarea: - // Show description span if available - if e.Description != "" { - descID := fmt.Sprintf("desc_%s_%d", e.Name, i) - fmt.Fprintf(w, `
%s
`+"\n", - HTMLEscapeString(descID), HTMLEscapeString(e.Description)) - lastDescID = descID // Update last description ID + isSelectable := e.Type == "radio" || e.Type == "checkbox" + + if isSelectable { + uniqueID := fmt.Sprintf("%s_%d", e.Name, idx) + fmt.Fprintf(w, `
+`) + + label := e.Value + if e.Placeholder != "" { + label += " - " + e.Placeholder + } + fmt.Fprintf(w, ` +
+`, HTMLEscapeString(uniqueID), HTMLEscapeString(label)) + } else { + fmt.Fprintf(w, `
+ +
`) + } +} + +func (r *FormRenderer) renderTextarea(w util.BufWriter, e FormTextarea, idx int, lastDescID *string) { + if e.Description != "" { + descID := fmt.Sprintf("desc_%s_%d", e.Name, idx) + fmt.Fprintf(w, `
%s
`+"\n", + HTMLEscapeString(descID), HTMLEscapeString(e.Description)) + *lastDescID = descID + } + + fmt.Fprintf(w, `
+ +
+`, HTMLEscapeString(e.Name), HTMLEscapeString(e.Placeholder), + HTMLEscapeString(e.Name), HTMLEscapeString(e.Name), + HTMLEscapeString(e.Placeholder), e.Rows, HTMLEscapeString(e.Value)) +} + +func (r *FormRenderer) renderSelect(w util.BufWriter, elements []FormElement, e FormSelect, idx int, lastDescID *string) { + if e.Description != "" { + descID := fmt.Sprintf("desc_%s_%d", e.Name, idx) + fmt.Fprintf(w, `
%s
`+"\n", + HTMLEscapeString(descID), HTMLEscapeString(e.Description)) + *lastDescID = descID + } + + label := titleCase(strings.ReplaceAll(e.Name, "_", " ")) + fmt.Fprintf(w, `
+ + +
`) +} + +// FormTransformer reorders form elements based on function signatures +type FormTransformer struct{} - fmt.Fprintf(w, `
`+"\n", - HTMLEscapeString(e.Name), - HTMLEscapeString(e.Placeholder)) - fmt.Fprintf(w, ``+"\n", - HTMLEscapeString(e.Name), - HTMLEscapeString(e.Name), - HTMLEscapeString(e.Placeholder), - e.Rows) - fmt.Fprintln(w, "
") - - case *FormSelect: - // Check if we already rendered a select for this name - selectRendered := false - for j := 0; j < i; j++ { - if prevElement, ok := n.Elements[j].(*FormSelect); ok && prevElement.Name == e.Name { - selectRendered = true - break - } +func (t *FormTransformer) Transform(doc *ast.Document, reader text.Reader, pc parser.Context) { + ast.Walk(doc, func(node ast.Node, entering bool) (ast.WalkStatus, error) { + if !entering { + if formNode, ok := node.(*FormNode); ok && formNode.Exec != nil { + t.reorderElements(formNode) } + } + return ast.WalkContinue, nil + }) +} - // If this is the first select element with this name, render the select container - if !selectRendered { - // Show description span if available (only for the first element) - if e.Description != "" { - descID := fmt.Sprintf("desc_%s_%d", e.Name, i) - fmt.Fprintf(w, `
%s
`+"\n", - HTMLEscapeString(descID), HTMLEscapeString(e.Description)) - lastDescID = descID // Update last description ID - } - - // Start the select container - // Format the name to be more readable (capitalize first letter, replace underscores with spaces) - labelText := titleCase(strings.ReplaceAll(e.Name, "_", " ")) - fmt.Fprintf(w, `
`+"\n", - HTMLEscapeString(e.Name), - HTMLEscapeString(labelText)) - fmt.Fprintf(w, ``+"\n") - - // SVG icon for select - fmt.Fprintf(w, ``+"\n") - - fmt.Fprintln(w, "
") +func (t *FormTransformer) reorderElements(node *FormNode) { + newElements := make([]FormElement, 0, len(node.Elements)) + + // If user provided any fields, error them all out + if len(node.Elements) > 0 { + for _, elem := range node.Elements { + switch e := elem.(type) { + case FormInput: + e.Error = fmt.Errorf("manual fields not allowed when 'exec' is specified") + newElements = append(newElements, e) + case FormTextarea: + e.Error = fmt.Errorf("manual fields not allowed when 'exec' is specified") + newElements = append(newElements, e) + case FormSelect: + e.Error = fmt.Errorf("manual fields not allowed when 'exec' is specified") + newElements = append(newElements, e) + default: + newElements = append(newElements, elem) } } } - // Display submit button only if there is at least one input or textarea - if len(n.Elements) > 0 { - fmt.Fprintf(w, ``+"\n", HTMLEscapeString(n.RealmName)) + // Generate fields for all function parameters + for _, param := range node.Exec.Params { + newElements = append(newElements, t.createDefaultElement(param)) } - fmt.Fprintln(w, "") - return ast.WalkContinue, nil + node.Elements = newElements } -type formExtension struct{} +func (t *FormTransformer) createDefaultElement(param vm.NamedType) FormElement { + placeholder := fmt.Sprintf("Enter %s", param.Name) -// Extend adds parsing and rendering options for the Form node -func (e *formExtension) Extend(m goldmark.Markdown) { + switch param.Type { + case "string": + return FormInput{ + Name: param.Name, + Type: "text", + Placeholder: placeholder, + } + case "int", "int8", "int16", "int32", "int64", + "uint", "uint8", "uint16", "uint32", "uint64", + "float32", "float64": + return FormInput{ + Name: param.Name, + Type: "number", + Placeholder: placeholder, + } + case "bool": + return FormInput{ + Name: param.Name, + Type: "checkbox", + Value: "true", + } + default: + return FormInput{ + Name: param.Name, + Type: "text", + Placeholder: fmt.Sprintf("Unsupported type: %s", param.Type), + Error: fmt.Errorf("%w '%s' for parameter '%s'", ErrFormUnsupportedType, param.Type, param.Name), + } + } +} + +// FormExtension integrates forms into goldmark +type FormExtension struct{} + +func (e *FormExtension) Extend(m goldmark.Markdown) { m.Parser().AddOptions( parser.WithBlockParsers(util.Prioritized(NewFormParser(), 500)), + parser.WithASTTransformers(util.Prioritized(&FormTransformer{}, 500)), ) m.Renderer().AddOptions( renderer.WithNodeRenderers(util.Prioritized(NewFormRenderer(), 500)), ) } -var ExtForms = &formExtension{} +// ExtForms is the public form extension instance +var ExtForms = &FormExtension{} + +// Helper function for parsing form tags +func parseFormTag(line []byte) (html.Token, bool) { + line = bytes.TrimSpace(line) + if len(line) == 0 { + return html.Token{}, false + } + toks, err := ParseHTMLTokens(bytes.NewReader(line)) + if err != nil || len(toks) != 1 { + return html.Token{}, false + } + return toks[0], true +} diff --git a/gno.land/pkg/gnoweb/markdown/ext_links.go b/gno.land/pkg/gnoweb/markdown/ext_links.go index 1767ffa8466..54fa2a3d2ca 100644 --- a/gno.land/pkg/gnoweb/markdown/ext_links.go +++ b/gno.land/pkg/gnoweb/markdown/ext_links.go @@ -121,7 +121,7 @@ func (t *linkTransformer) Transform(doc *ast.Document, reader text.Reader, pc pa } // Detect and set the GnoLink type. - gnoLink.GnoURL, gnoLink.LinkType = detectLinkType(dest, &orig) + gnoLink.GnoURL, gnoLink.LinkType = detectLinkType(dest, orig) return ast.WalkContinue, nil }) diff --git a/gno.land/pkg/gnoweb/markdown/ext_test.go b/gno.land/pkg/gnoweb/markdown/ext_test.go index 3a2f9717dbf..5eea1e7724a 100644 --- a/gno.land/pkg/gnoweb/markdown/ext_test.go +++ b/gno.land/pkg/gnoweb/markdown/ext_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/gnolang/gno/gno.land/pkg/gnoweb/weburl" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/stretchr/testify/require" "github.com/yuin/goldmark" "github.com/yuin/goldmark/parser" @@ -20,6 +21,68 @@ var ( dump = flag.Bool("dump", false, "dump ast tree after parsing") ) +func mockSignatureFunc(fn string) (*vm.FunctionSignature, error) { + switch fn { + case "CreatePost": + return &vm.FunctionSignature{ + FuncName: "CreatePost", + Params: []vm.NamedType{ + {Name: "title", Type: "string"}, + {Name: "content", Type: "string"}, + {Name: "published", Type: "bool"}, + }, + Results: []vm.NamedType{ + {Name: "", Type: "string"}, // return post ID + }, + }, nil + + case "UpdateUser": + return &vm.FunctionSignature{ + FuncName: "UpdateUser", + Params: []vm.NamedType{ + {Name: "username", Type: "string"}, + {Name: "email", Type: "string"}, + {Name: "age", Type: "int"}, + {Name: "active", Type: "bool"}, + }, + Results: []vm.NamedType{}, + }, nil + + case "SendMessage": + return &vm.FunctionSignature{ + FuncName: "SendMessage", + Params: []vm.NamedType{ + {Name: "recipient", Type: "string"}, + {Name: "message", Type: "string"}, + {Name: "priority", Type: "int"}, + }, + Results: []vm.NamedType{}, + }, nil + + case "Vote": + return &vm.FunctionSignature{ + FuncName: "Vote", + Params: []vm.NamedType{ + {Name: "proposal_id", Type: "int"}, + {Name: "vote", Type: "bool"}, + }, + Results: []vm.NamedType{}, + }, nil + + case "Transfer": + return &vm.FunctionSignature{ + FuncName: "Transfer", + Params: []vm.NamedType{ + {Name: "to", Type: "string"}, + {Name: "amount", Type: "uint64"}, + }, + Results: []vm.NamedType{}, + }, nil + default: + return nil, nil + } +} + func testGoldmarkOutput(t *testing.T, nameIn string, input []byte) (string, []byte) { t.Helper() @@ -32,8 +95,11 @@ func testGoldmarkOutput(t *testing.T, nameIn string, input []byte) (string, []by gnourl, err := weburl.Parse("https://gno.land/r/test") require.NoError(t, err) - // Create parser context with the test URL - ctxOpts := parser.WithContext(NewGnoParserContext(gnourl)) + // Create parser context with the test URL and mock function signatures + ctxOpts := parser.WithContext(NewGnoParserContext(GnoContext{ + GnoURL: gnourl, + RealmFuncSigGetter: mockSignatureFunc, + })) ext := NewGnoExtension(WithImageValidator(func(uri string) bool { return !strings.HasPrefix(uri, "https://") // disallow https diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/exec_create_post.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/exec_create_post.md.txtar new file mode 100644 index 00000000000..105c22f27ed --- /dev/null +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/exec_create_post.md.txtar @@ -0,0 +1,62 @@ +-- input.md -- + + + + +-- output.html -- +
+
+/r/test Form + +
+
+
+ +
+
+ +
+
+ + +
+
+
+ Command +
+
+ + +
+
+ + +
+
+
+
+ +
# WARNING: This command is running in an INSECURE mode.
+# It is strongly recommended to use a hardware device for signing
+# and avoid trusting any computer connected to the internet,
+# as your private keys could be exposed.
+
+gnokey maketx call -pkgpath "/r/test" -func "CreatePost" -args "" -args "" -args "" -gas-fee 1000000ugnot -gas-wanted 5000000 -send "" -broadcast -chainid "dev" -remote "127.0.0.1:26657" ADDRESSgnokey query -remote "127.0.0.1:26657" auth/accounts/ADDRESS
+gnokey maketx call -pkgpath "/r/test" -func "CreatePost" -args "" -args "" -args "" -gas-fee 1000000ugnot -gas-wanted 5000000 -send "" ADDRESS > call.tx
+gnokey sign -tx-path call.tx -chainid "dev" -account-number ACCOUNTNUMBER -account-sequence SEQUENCENUMBER ADDRESS
+gnokey broadcast -remote "127.0.0.1:26657" call.tx
+  
+
+
+
+
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/exec_invalid_function.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/exec_invalid_function.md.txtar new file mode 100644 index 00000000000..a69c9d371b4 --- /dev/null +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/exec_invalid_function.md.txtar @@ -0,0 +1,7 @@ +-- input.md -- + + + + +-- output.html -- + diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/exec_send_message.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/exec_send_message.md.txtar new file mode 100644 index 00000000000..678cd1dd037 --- /dev/null +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/exec_send_message.md.txtar @@ -0,0 +1,61 @@ +-- input.md -- + + + + +-- output.html -- +
+
+/r/test Form + +
+
+
+ +
+
+ +
+
+ +
+
+
+ Command +
+
+ + +
+
+ + +
+
+
+
+ +
# WARNING: This command is running in an INSECURE mode.
+# It is strongly recommended to use a hardware device for signing
+# and avoid trusting any computer connected to the internet,
+# as your private keys could be exposed.
+
+gnokey maketx call -pkgpath "/r/test" -func "SendMessage" -args "" -args "" -args "" -gas-fee 1000000ugnot -gas-wanted 5000000 -send "" -broadcast -chainid "dev" -remote "127.0.0.1:26657" ADDRESSgnokey query -remote "127.0.0.1:26657" auth/accounts/ADDRESS
+gnokey maketx call -pkgpath "/r/test" -func "SendMessage" -args "" -args "" -args "" -gas-fee 1000000ugnot -gas-wanted 5000000 -send "" ADDRESS > call.tx
+gnokey sign -tx-path call.tx -chainid "dev" -account-number ACCOUNTNUMBER -account-sequence SEQUENCENUMBER ADDRESS
+gnokey broadcast -remote "127.0.0.1:26657" call.tx
+  
+
+
+
+
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/exec_transfer.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/exec_transfer.md.txtar new file mode 100644 index 00000000000..f4332ec32a3 --- /dev/null +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/exec_transfer.md.txtar @@ -0,0 +1,58 @@ +-- input.md -- + + + + +-- output.html -- +
+
+/r/test Form + +
+
+
+ +
+
+ +
+
+
+ Command +
+
+ + +
+
+ + +
+
+
+
+ +
# WARNING: This command is running in an INSECURE mode.
+# It is strongly recommended to use a hardware device for signing
+# and avoid trusting any computer connected to the internet,
+# as your private keys could be exposed.
+
+gnokey maketx call -pkgpath "/r/test" -func "Transfer" -args "" -args "" -gas-fee 1000000ugnot -gas-wanted 5000000 -send "" -broadcast -chainid "dev" -remote "127.0.0.1:26657" ADDRESSgnokey query -remote "127.0.0.1:26657" auth/accounts/ADDRESS
+gnokey maketx call -pkgpath "/r/test" -func "Transfer" -args "" -args "" -gas-fee 1000000ugnot -gas-wanted 5000000 -send "" ADDRESS > call.tx
+gnokey sign -tx-path call.tx -chainid "dev" -account-number ACCOUNTNUMBER -account-sequence SEQUENCENUMBER ADDRESS
+gnokey broadcast -remote "127.0.0.1:26657" call.tx
+  
+
+
+
+
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/exec_update_user.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/exec_update_user.md.txtar new file mode 100644 index 00000000000..248aa8c1274 --- /dev/null +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/exec_update_user.md.txtar @@ -0,0 +1,65 @@ +-- input.md -- + + + + +-- output.html -- +
+
+/r/test Form + +
+
+
+ +
+
+ +
+
+ +
+
+ + +
+
+
+ Command +
+
+ + +
+
+ + +
+
+
+
+ +
# WARNING: This command is running in an INSECURE mode.
+# It is strongly recommended to use a hardware device for signing
+# and avoid trusting any computer connected to the internet,
+# as your private keys could be exposed.
+
+gnokey maketx call -pkgpath "/r/test" -func "UpdateUser" -args "" -args "" -args "" -args "" -gas-fee 1000000ugnot -gas-wanted 5000000 -send "" -broadcast -chainid "dev" -remote "127.0.0.1:26657" ADDRESSgnokey query -remote "127.0.0.1:26657" auth/accounts/ADDRESS
+gnokey maketx call -pkgpath "/r/test" -func "UpdateUser" -args "" -args "" -args "" -args "" -gas-fee 1000000ugnot -gas-wanted 5000000 -send "" ADDRESS > call.tx
+gnokey sign -tx-path call.tx -chainid "dev" -account-number ACCOUNTNUMBER -account-sequence SEQUENCENUMBER ADDRESS
+gnokey broadcast -remote "127.0.0.1:26657" call.tx
+  
+
+
+
+
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/exec_vote.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/exec_vote.md.txtar new file mode 100644 index 00000000000..f00cb5292b5 --- /dev/null +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/exec_vote.md.txtar @@ -0,0 +1,59 @@ +-- input.md -- + + + + +-- output.html -- +
+
+/r/test Form + +
+
+
+ +
+
+ + +
+
+
+ Command +
+
+ + +
+
+ + +
+
+
+
+ +
# WARNING: This command is running in an INSECURE mode.
+# It is strongly recommended to use a hardware device for signing
+# and avoid trusting any computer connected to the internet,
+# as your private keys could be exposed.
+
+gnokey maketx call -pkgpath "/r/test" -func "Vote" -args "" -args "" -gas-fee 1000000ugnot -gas-wanted 5000000 -send "" -broadcast -chainid "dev" -remote "127.0.0.1:26657" ADDRESSgnokey query -remote "127.0.0.1:26657" auth/accounts/ADDRESS
+gnokey maketx call -pkgpath "/r/test" -func "Vote" -args "" -args "" -gas-fee 1000000ugnot -gas-wanted 5000000 -send "" ADDRESS > call.tx
+gnokey sign -tx-path call.tx -chainid "dev" -account-number ACCOUNTNUMBER -account-sequence SEQUENCENUMBER ADDRESS
+gnokey broadcast -remote "127.0.0.1:26657" call.tx
+  
+
+
+
+
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/exec_with_manual_fields_error.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/exec_with_manual_fields_error.md.txtar new file mode 100644 index 00000000000..2afbbdcc634 --- /dev/null +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/exec_with_manual_fields_error.md.txtar @@ -0,0 +1,61 @@ +-- input.md -- + + + + + +-- output.html -- +
+
+/r/test Form + +
+
+ +
+ +
+
+ + +
+
+
+ Command +
+
+ + +
+
+ + +
+
+
+
+ +
# WARNING: This command is running in an INSECURE mode.
+# It is strongly recommended to use a hardware device for signing
+# and avoid trusting any computer connected to the internet,
+# as your private keys could be exposed.
+
+gnokey maketx call -pkgpath "/r/test" -func "Vote" -args "" -args "" -gas-fee 1000000ugnot -gas-wanted 5000000 -send "" -broadcast -chainid "dev" -remote "127.0.0.1:26657" ADDRESSgnokey query -remote "127.0.0.1:26657" auth/accounts/ADDRESS
+gnokey maketx call -pkgpath "/r/test" -func "Vote" -args "" -args "" -gas-fee 1000000ugnot -gas-wanted 5000000 -send "" ADDRESS > call.tx
+gnokey sign -tx-path call.tx -chainid "dev" -account-number ACCOUNTNUMBER -account-sequence SEQUENCENUMBER ADDRESS
+gnokey broadcast -remote "127.0.0.1:26657" call.tx
+  
+
+
+
+
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/form_empty_no_submit.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/form_empty_no_submit.md.txtar index c9b3d9e9060..11da4cfb554 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/form_empty_no_submit.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/form_empty_no_submit.md.txtar @@ -5,7 +5,7 @@ -- output.html --
-/r/test Form - +/r/test Form +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/form_html_escape.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/form_html_escape.md.txtar index 6de4cb633d8..b55a4444a76 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/form_html_escape.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/form_html_escape.md.txtar @@ -7,8 +7,8 @@ -- output.html --
-/r/test Form - +/r/test Form +
@@ -16,5 +16,5 @@
- +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/form_input_types.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/form_input_types.md.txtar index 654257b11c2..68c7c4e007b 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/form_input_types.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/form_input_types.md.txtar @@ -11,8 +11,8 @@ -- output.html --
-/r/test Form - +/r/test Form +
@@ -30,5 +30,5 @@
- +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/form_with_descriptions.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/form_with_descriptions.md.txtar index 004b92edc2d..956b0f35810 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/form_with_descriptions.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/form_with_descriptions.md.txtar @@ -9,8 +9,8 @@ -- output.html --
-/r/test Form - +/r/test Form +
Your unique username for the platform
@@ -18,16 +18,16 @@
Choose your preferred theme
- +
- +
Please provide detailed feedback about your experience
- +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/form_with_path.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/form_with_path.md.txtar index 2e5255facf3..53dbcc5a103 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/form_with_path.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/form_with_path.md.txtar @@ -7,8 +7,8 @@ -- output.html --
-/r/test Form - +/r/test Form +
@@ -16,5 +16,5 @@
- +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/form_without_path.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/form_without_path.md.txtar index c586b021ad8..e1e8fc23220 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/form_without_path.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/form_without_path.md.txtar @@ -6,11 +6,11 @@ -- output.html --
-/r/test Form - +/r/test Form +
- +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_duplicate_attributes.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_duplicate_attributes.md.txtar index c3ab08d9804..3ccf65911a2 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_duplicate_attributes.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_duplicate_attributes.md.txtar @@ -6,11 +6,11 @@ -- output.html --
-/r/test Form - +/r/test Form +
- +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_duplicate_input_name.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_duplicate_input_name.md.txtar index 12d3ba4c14f..fddbf7924cb 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_duplicate_input_name.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_duplicate_input_name.md.txtar @@ -12,14 +12,14 @@

only the first one should be take into account

-/r/test Form - +/r/test Form +
- - - - + + + +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_duplicate_names.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_duplicate_names.md.txtar index 54fea0911f6..610715b4ed1 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_duplicate_names.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_duplicate_names.md.txtar @@ -10,21 +10,21 @@ -- output.html --
-/r/test Form - +/r/test Form +
- +
- +
- +
- - + +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_input_not_selfclosing.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_input_not_selfclosing.md.txtar index 807f4fff93d..a25a847bd9b 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_input_not_selfclosing.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_input_not_selfclosing.md.txtar @@ -6,12 +6,9 @@ -- output.html --
-/r/test Form - -
-
- +/r/test Form +
- +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_malformed_input.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_malformed_input.md.txtar index 3dd5e49619f..3ff4e31db06 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_malformed_input.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_malformed_input.md.txtar @@ -6,9 +6,9 @@ -- output.html --
-/r/test Form - +/r/test Form +
- +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_markdown_content.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_markdown_content.md.txtar index 16dafa37d0c..0ffbbfa67e5 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_markdown_content.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_markdown_content.md.txtar @@ -7,12 +7,12 @@ This is some **markdown** content -- output.html --
-/r/test Form - +/r/test Form +
- +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_missing_ending.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_missing_ending.md.txtar index c14a176d26b..e5d3fba6135 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_missing_ending.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_missing_ending.md.txtar @@ -7,12 +7,12 @@ my content -- output.html --
-/r/test Form - +/r/test Form +
- +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_missing_name.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_missing_name.md.txtar index 044e86a71c5..4272d2b57b6 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_missing_name.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_missing_name.md.txtar @@ -6,9 +6,9 @@ -- output.html --
-/r/test Form - +/r/test Form +
- - + +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_missing_open.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_missing_open.md.txtar index 5a571aef55a..da7f0d62b2d 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_missing_open.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_missing_open.md.txtar @@ -7,4 +7,4 @@ my content -- output.html -- - + diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_missing_quote.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_missing_quote.md.txtar index 1569f346561..f2aea11afac 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_missing_quote.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_missing_quote.md.txtar @@ -6,9 +6,9 @@ -- output.html --
-/r/test Form - +/r/test Form +
- +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_nested_forms.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_nested_forms.md.txtar index df933cb078b..d011a4f306a 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_nested_forms.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_nested_forms.md.txtar @@ -9,8 +9,8 @@ -- output.html --
-/r/test Form - +/r/test Form +
@@ -19,6 +19,6 @@
- +
- + diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_radio_checkbox.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_radio_checkbox.md.txtar index 0e1034d7dce..d7d2e787719 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_radio_checkbox.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_radio_checkbox.md.txtar @@ -11,20 +11,20 @@ -- output.html --
-/r/test Form - +/r/test Form +
- - - - + + + +
- +
- +
- +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_select_missing_name.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_select_missing_name.md.txtar index 2940c13f36e..06caade4e76 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_select_missing_name.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_select_missing_name.md.txtar @@ -8,10 +8,10 @@ -- output.html --
-/r/test Form - +/r/test Form +
- - - + + +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_textarea_missing_name.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_textarea_missing_name.md.txtar index 28a8df5271d..40de75a1593 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_textarea_missing_name.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_textarea_missing_name.md.txtar @@ -6,9 +6,9 @@ -- output.html --
-/r/test Form - +/r/test Form +
- - + +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_textarea_not_selfclosing.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_textarea_not_selfclosing.md.txtar index b3d606d85b2..89d0b400570 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_textarea_not_selfclosing.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_textarea_not_selfclosing.md.txtar @@ -6,12 +6,9 @@ -- output.html --
-/r/test Form - -
-
- +/r/test Form +
- +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_textarea_rows.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_textarea_rows.md.txtar index dbcf04065bb..d77c9cde069 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_textarea_rows.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_textarea_rows.md.txtar @@ -9,8 +9,8 @@ -- output.html --
-/r/test Form - +/r/test Form +
@@ -24,5 +24,5 @@
- +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_unknown_tag.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_unknown_tag.md.txtar index 092760e82fe..5f8784fc1c2 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_unknown_tag.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/invalid_unknown_tag.md.txtar @@ -6,9 +6,9 @@ -- output.html --
-/r/test Form - +/r/test Form +
- +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_basic.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_basic.md.txtar index 8f4f90a443e..9b1d3423c71 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_basic.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_basic.md.txtar @@ -7,11 +7,11 @@ -- output.html --
-/r/test Form - +/r/test Form +
- +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_default_placeholder.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_default_placeholder.md.txtar index 745f4347921..e72d2c0e3f7 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_default_placeholder.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_default_placeholder.md.txtar @@ -6,11 +6,11 @@ -- output.html --
-/r/test Form - +/r/test Form +
- +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_default_values.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_default_values.md.txtar new file mode 100644 index 00000000000..bbb3d337014 --- /dev/null +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_default_values.md.txtar @@ -0,0 +1,20 @@ +-- input.md -- + + + + + +-- output.html -- +
+
+/r/test Form + +
+
+ +
+
+ +
+
+
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_descriptions.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_descriptions.md.txtar index b37ff9bc3bb..597de78ecdc 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_descriptions.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_descriptions.md.txtar @@ -12,8 +12,8 @@ -- output.html --
-/r/test Form - +/r/test Form +
Your username will be displayed publicly
@@ -29,22 +29,22 @@
Receive updates about new features
- +
Select your gender
- +
Select your gender
- +
A brief description of yourself (optional)
- +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_empty_form.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_empty_form.md.txtar index c9b3d9e9060..11da4cfb554 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_empty_form.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_empty_form.md.txtar @@ -5,7 +5,7 @@ -- output.html --
-/r/test Form - +/r/test Form +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_empty_lines.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_empty_lines.md.txtar index 295a084d46d..bd77f114055 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_empty_lines.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_empty_lines.md.txtar @@ -15,8 +15,8 @@ -- output.html --
-/r/test Form - +/r/test Form +
@@ -34,5 +34,5 @@
- +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_indent.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_indent.md.txtar index 96f9e33d5c2..75cea423601 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_indent.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_indent.md.txtar @@ -7,11 +7,11 @@ -- output.html --
-/r/test Form - +/r/test Form +
- +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_multiple_forms.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_multiple_forms.md.txtar index 6bcbae4337d..4788f8aaa39 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_multiple_forms.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_multiple_forms.md.txtar @@ -17,32 +17,32 @@ -- output.html --
-/r/test Form - +/r/test Form +
- +

hello 1

-/r/test Form - +/r/test Form +
- +
-/r/test Form - +/r/test Form +
- +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_multiple_inputs.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_multiple_inputs.md.txtar index 1b1c2edc06d..9a06873a021 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_multiple_inputs.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_multiple_inputs.md.txtar @@ -8,8 +8,8 @@ -- output.html --
-/r/test Form - +/r/test Form +
@@ -17,5 +17,5 @@
- +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_radio_checkbox.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_radio_checkbox.md.txtar index 4f7d84a0a51..573455db1cb 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_radio_checkbox.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_radio_checkbox.md.txtar @@ -11,32 +11,32 @@ -- output.html --
-/r/test Form - +/r/test Form +
- +
- +
- +
- +
- +
- +
- +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_select_alternating_names.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_select_alternating_names.md.txtar index b390b6bf7af..85e9f59adba 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_select_alternating_names.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_select_alternating_names.md.txtar @@ -12,26 +12,26 @@ -- output.html --
-/r/test Form - +/r/test Form +
- +
- +
- +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_select_basic.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_select_basic.md.txtar index fa1e8b044b6..bb6c7d3e6b6 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_select_basic.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_select_basic.md.txtar @@ -9,17 +9,17 @@ -- output.html --
-/r/test Form - +/r/test Form +
- +
- +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_select_with_description.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_select_with_description.md.txtar index 11efb20eeaa..e146be5290a 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_select_with_description.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_select_with_description.md.txtar @@ -10,19 +10,19 @@ -- output.html --
-/r/test Form - +/r/test Form +
Select your preferred programming language
- +
- +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_select_with_selected.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_select_with_selected.md.txtar index a4db16453b5..0477c54c561 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_select_with_selected.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_select_with_selected.md.txtar @@ -9,17 +9,17 @@ -- output.html --
-/r/test Form - +/r/test Form +
- +
- +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_select_with_underscores.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_select_with_underscores.md.txtar index 930e5340089..62b2e0384df 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_select_with_underscores.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_select_with_underscores.md.txtar @@ -9,17 +9,17 @@ -- output.html --
-/r/test Form - +/r/test Form +
- +
- +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_textarea.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_textarea.md.txtar index 798f0af9830..345ca330889 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_textarea.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_textarea.md.txtar @@ -9,8 +9,8 @@ -- output.html --
-/r/test Form - +/r/test Form +
Your feedback helps us improve
@@ -27,5 +27,5 @@
- +
diff --git a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_textarea_default_placeholder.md.txtar b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_textarea_default_placeholder.md.txtar index f14a02fbad3..dd1a384d38e 100644 --- a/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_textarea_default_placeholder.md.txtar +++ b/gno.land/pkg/gnoweb/markdown/golden/ext_forms/valid_textarea_default_placeholder.md.txtar @@ -6,11 +6,11 @@ -- output.html --
-/r/test Form - +/r/test Form +
- +
diff --git a/gno.land/pkg/gnoweb/markdown/toc.go b/gno.land/pkg/gnoweb/markdown/toc.go index 1b6fcf830a9..66e338b42a7 100644 --- a/gno.land/pkg/gnoweb/markdown/toc.go +++ b/gno.land/pkg/gnoweb/markdown/toc.go @@ -3,6 +3,8 @@ package markdown import ( + ti "github.com/gnolang/gno/gno.land/pkg/gnoweb/markdown/tocitem" + "github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/util" ) @@ -10,35 +12,7 @@ import ( const MaxDepth = 6 type Toc struct { - Items []*TocItem -} - -type TocItem struct { - // Title of this item in the table of contents. - // - // This may be blank for items that don't refer to a heading, and only - // have sub-items. - Title []byte - - // ID is the identifier for the heading that this item refers to. This - // is the fragment portion of the link without the "#". - // - // This may be blank if the item doesn't have an id assigned to it, or - // if it doesn't have a title. - // - // Enable AutoHeadingID in your parser if you expected these to be set - // but they weren't. - ID []byte - - // Items references children of this item. - // - // For a heading at level 3, Items, contains the headings at level 4 - // under that section. - Items []*TocItem -} - -func (i TocItem) Anchor() string { - return "#" + string(i.ID) + Items []*ti.TocItem } type TocOptions struct { @@ -48,24 +22,24 @@ type TocOptions struct { func TocInspect(n ast.Node, src []byte, opts TocOptions) (Toc, error) { // Appends an empty subitem to the given node // and returns a reference to it. - appendChild := func(n *TocItem) *TocItem { - child := new(TocItem) + appendChild := func(n *ti.TocItem) *ti.TocItem { + child := new(ti.TocItem) n.Items = append(n.Items, child) return child } // Returns the last subitem of the given node, // creating it if necessary. - lastChild := func(n *TocItem) *TocItem { + lastChild := func(n *ti.TocItem) *ti.TocItem { if len(n.Items) > 0 { return n.Items[len(n.Items)-1] } return appendChild(n) } - var root TocItem + var root ti.TocItem - stack := []*TocItem{&root} // inv: len(stack) >= 1 + stack := []*ti.TocItem{&root} // inv: len(stack) >= 1 err := ast.Walk(n, func(n ast.Node, entering bool) (ast.WalkStatus, error) { if !entering { return ast.WalkContinue, nil @@ -113,26 +87,7 @@ func TocInspect(n ast.Node, src []byte, opts TocOptions) (Toc, error) { return ast.WalkSkipChildren, nil }) - root.Items = compactItems(root.Items) + root.Items = ti.CompactItems(root.Items) return Toc{Items: root.Items}, err } - -// compactItems removes items with no titles -// from the given list of items. -// -// Children of removed items will be promoted to the parent item. -func compactItems(items []*TocItem) []*TocItem { - result := make([]*TocItem, 0) - for _, item := range items { - if len(item.Title) == 0 { - result = append(result, compactItems(item.Items)...) - continue - } - - item.Items = compactItems(item.Items) - result = append(result, item) - } - - return result -} diff --git a/gno.land/pkg/gnoweb/markdown/tocitem/tocitem.go b/gno.land/pkg/gnoweb/markdown/tocitem/tocitem.go new file mode 100644 index 00000000000..003ce35dfcb --- /dev/null +++ b/gno.land/pkg/gnoweb/markdown/tocitem/tocitem.go @@ -0,0 +1,56 @@ +// Package tocitem contains TocItem type and functions that were moved into +// this subpackage to resolve a circular dependency problem, because they need +// to be imported by both the markdown package and the component package, and +// the markdown package needs to access the component package to render certain +// templates (to date, only ui/command.html but likely others in the future). +// +// We could consider moving all the types into a shared/common package so they +// can be imported by both markdown and component. +package tocitem + +type TocItem struct { + // Title of this item in the table of contents. + // + // This may be blank for items that don't refer to a heading, and only + // have sub-items. + Title []byte + + // ID is the identifier for the heading that this item refers to. This + // is the fragment portion of the link without the "#". + // + // This may be blank if the item doesn't have an id assigned to it, or + // if it doesn't have a title. + // + // Enable AutoHeadingID in your parser if you expected these to be set + // but they weren't. + ID []byte + + // Items references children of this item. + // + // For a heading at level 3, Items, contains the headings at level 4 + // under that section. + Items []*TocItem +} + +func (i TocItem) Anchor() string { + return "#" + string(i.ID) +} + +// CompactItems removes items with no titles +// from the given list of items. +// +// Children of removed items will be promoted to the parent item. +func CompactItems(items []*TocItem) []*TocItem { + result := make([]*TocItem, 0) + for _, item := range items { + if len(item.Title) == 0 { + result = append(result, CompactItems(item.Items)...) + continue + } + + item.Items = CompactItems(item.Items) + result = append(result, item) + } + + return result +} diff --git a/gno.land/pkg/gnoweb/public/js/controller-action-function.js b/gno.land/pkg/gnoweb/public/js/controller-action-function.js index ae402353cc4..9ec42783acf 100644 --- a/gno.land/pkg/gnoweb/public/js/controller-action-function.js +++ b/gno.land/pkg/gnoweb/public/js/controller-action-function.js @@ -1 +1 @@ -import{BaseController as r,debounce as c,escapeShellSpecialChars as d}from"./controller.js";var o=class extends r{sendValue=null;connect(){this.initializeDOM({"send-code":this.getTargets("send-code")}),this._funcName=this.getValue("name")||null,this._initializeArgs(),this._listenForEvents()}_listenForEvents(){this.on("mode:changed",e=>{let t=e.detail.mode;console.log("mode:changed",t),this._updateAllFunctionsMode(t)}),this.on("address:changed",e=>{let t=e.detail.address;this._updateAllFunctionsAddress(t)})}_updateAllFunctionsMode(e){this.getTargets("mode").forEach(t=>{console.log("modeElement in function",t);let n=this.getValue("mode",t)===e;t.classList.toggle("u-inline",n),t.classList.toggle("u-hidden",!n),t.dataset.copyTarget=n&&this._funcName?`action-function-${this._funcName}`:""})}_updateAllFunctionsAddress(e){this.getTargets("address").forEach(t=>{t.textContent=e.trim()||"ADDRESS"})}_sanitizeArgsInput(e){let s=this.getValue("param",e)||"",t=e.value.trim();return s||console.warn("sanitizeArgsInput: param is missing in arg input dataset."),{paramName:s,paramValue:t}}_initializeArgs(){this.getTargets("param-input").forEach(e=>{let{paramName:s,paramValue:t}=this._sanitizeArgsInput(e);s&&this._pushArgsInDOM(s,t)})}_debouncedUpdateAllArgs=c((e,s)=>{e&&this._pushArgsInDOM(e,s)},50);_pushArgsInDOM(e,s){let t=d(s);this.getTargets("arg").filter(i=>this.getValue("arg",i)===e).forEach(i=>{i.textContent=t||""});let n=this.getTarget("function-link");if(n){if(!n.getAttribute("href")){console.warn("No href attribute found for function link");return}let a=new URL(n.href,window.location.origin);a.searchParams.set(e,s),n.href=a.toString()}}updateAllArgs(e){let s=e.target,{paramName:t,paramValue:n}=this._sanitizeArgsInput(s);t&&this._debouncedUpdateAllArgs(t,n)}updateAllFunctionsSend(e){let s=e.params?.send||!1;this.getDOMArray("send-code").forEach(t=>{t.textContent=s?this.getValue("send"):""})}};export{o as ActionFunctionController}; +import{BaseController as o,debounce as d,escapeShellSpecialChars as c}from"./controller.js";var r=class extends o{sendValue=null;connect(){this.initializeDOM({"send-code":this.getTargets("send-code")}),this._funcName=this.getValue("name")||null,this._initializeArgs(),this._listenForEvents()}_listenForEvents(){this.on("mode:changed",t=>{let e=t.detail.mode;this._updateAllFunctionsMode(e)}),this.on("address:changed",t=>{let e=t.detail.address;this._updateAllFunctionsAddress(e)})}_updateAllFunctionsMode(t){this.getTargets("mode").forEach(e=>{let n=this.getValue("mode",e)===t;e.classList.toggle("u-inline",n),e.classList.toggle("u-hidden",!n),e.dataset.copyTarget=n&&this._funcName?`action-function-${this._funcName}`:""})}_updateAllFunctionsAddress(t){this.getTargets("address").forEach(e=>{e.textContent=t.trim()||"ADDRESS"})}_sanitizeArgsInput(t){let s=this.getValue("param",t)||"",e=t.value.trim();return s||console.warn("sanitizeArgsInput: param is missing in arg input dataset."),{paramName:s,paramValue:e}}_initializeArgs(){this.getTargets("param-input").forEach(t=>{let{paramName:s,paramValue:e}=this._sanitizeArgsInput(t);s&&this._pushArgsInDOM(s,e)})}_debouncedUpdateAllArgs=d((t,s)=>{t&&this._pushArgsInDOM(t,s)},50);_pushArgsInDOM(t,s){let e=c(s);this.getTargets("arg").filter(i=>this.getValue("arg",i)===t).forEach(i=>{i.textContent=e||""});let n=this.getTarget("function-link");if(n){if(!n.getAttribute("href")){console.warn("No href attribute found for function link");return}let a=new URL(n.href,window.location.origin);a.searchParams.set(t,s),n.href=a.toString()}}updateAllArgs(t){let s=t.target,{paramName:e,paramValue:n}=this._sanitizeArgsInput(s);e&&this._debouncedUpdateAllArgs(e,n)}updateAllFunctionsSend(t){let s=t.params?.send||!1;this.getDOMArray("send-code").forEach(e=>{e.textContent=s?this.getValue("send"):""})}};export{r as ActionFunctionController}; diff --git a/gno.land/pkg/gnoweb/public/js/controller-form-exec.js b/gno.land/pkg/gnoweb/public/js/controller-form-exec.js new file mode 100644 index 00000000000..2b4e5a5f552 --- /dev/null +++ b/gno.land/pkg/gnoweb/public/js/controller-form-exec.js @@ -0,0 +1 @@ +import{BaseController as n}from"./controller.js";var i=class extends n{connect(){this.initializeDOM({});let t=this.element instanceof HTMLFormElement?this.element:this.element.querySelector("form");t&&t.addEventListener("submit",this._handleSubmit.bind(this))}_handleSubmit(t){t.preventDefault(),t.stopPropagation();let e=this.getTarget("command");e&&e.classList.remove("u-hidden")}};export{i as FormExecController}; diff --git a/gno.land/pkg/gnoweb/public/main.css b/gno.land/pkg/gnoweb/public/main.css index aaaf770faf4..c2ce4825605 100644 --- a/gno.land/pkg/gnoweb/public/main.css +++ b/gno.land/pkg/gnoweb/public/main.css @@ -1,5 +1,5 @@ -: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-grid-1:repeat(1,minmax(0,1fr));--g-grid-10:repeat(10,minmax(0,1fr));--g-space-0:0;--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-600:#54595d;--g-color-gray-900:#080809;--g-color-green-50:#e7efed;--g-color-green-600:#226c57;--g-color-green-900:#144134;--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-red-600:#c95c3e;--g-color-red-900:#6b2521;--g-color-purple-600:#6c3ec9;--g-color-purple-900:#39216b}@supports (color:color(display-p3 0 0 0%)){:root{--g-color-yellow-50:#fff7eb}@media (color-gamut:p3){:root{--g-color-yellow-50:color(display-p3 0.99709 0.97106 0.92232)}}}: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-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-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-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-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)}*,::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(--g-color-light);color:var(--g-color-gray-600);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-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-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-4:calc(var(--cr-space-base)*4);--cr-space-5:calc(var(--cr-space-base)*5);--cr-space-6:calc(var(--cr-space-base)*6);--cr-space-8:calc(var(--cr-space-base)*8);--cr-space-10:calc(var(--cr-space-base)*10);--cr-space-24:calc(var(--cr-space-base)*24);--cr-color-brand-default:var(--s-color-bg-brand-default);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:var(--g-font-medium);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-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(--g-color-gray-900);line-height:var(--g-line-height-tight);margin-top:var(--cr-space-8)}.c-readme-view h1,.c-realm-view h1{font-size:var(--g-font-size-700);font-weight:var(--g-font-bold)}@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(--g-color-gray-600);font-weight:var(--g-font-semibold)}.c-readme-view h3,.c-realm-view h3{font-size:var(--g-font-size-300);margin-top:var(--cr-space-10)}.c-readme-view h4,.c-realm-view h4{font-size:var(--g-font-size-200);margin-top:var(--cr-space-6)}@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 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-8);margin-top:var(--cr-space-8);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-6);margin-top:var(--cr-space-6);text-align:center}.c-readme-view figcaption,.c-realm-view figcaption{color:var(--g-color-gray-600);font-size:var(--g-font-size-100)}.c-readme-view video,.c-realm-view video{margin-bottom:var(--g-space-8);margin-top:var(--g-space-8);max-width:100%}.c-readme-view p,.c-realm-view p{margin-bottom:var(--cr-space-5);margin-top:var(--cr-space-5)}.c-realm-view p:has(>a:only-child>img){margin-bottom:var(--cr-space-8);margin-top:var(--cr-space-8)}.c-readme-view p:has(>a:only-child>img){margin-bottom:var(--cr-space-8);margin-top:var(--cr-space-8)}.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-1) var(--s-color-border-tertiary);color:var(--g-color-gray-600);font-style:var(--g-italic);margin-bottom:var(--cr-space-4);margin-top:var(--cr-space-4);padding-left:var(--g-space-4)}.c-readme-view caption,.c-realm-view caption{color:var(--g-color-gray-600);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-5);margin-top:var(--cr-space-5)}.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-6);margin-top:var(--cr-space-6);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-2);margin-top:var(--cr-space-2)}.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-realm-view pre{background-color:var(--g-color-gray-50);border-radius:var(--g-border-radius-sm);margin-bottom:var(--cr-space-5);margin-top:var(--cr-space-5);overflow-x:auto;padding:var(--cr-space-4)}.c-readme-view :not(pre)>code,.c-realm-view :not(pre)>code{background-color:var(--g-color-gray-100);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-10);margin-top:var(--cr-space-10)}.c-readme-view table,.c-realm-view table{border-collapse:collapse;display:block;margin-bottom:var(--cr-space-8);margin-top:var(--cr-space-8);max-width:100%;overflow-x:auto;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(--g-color-gray-100);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(--g-color-light);border:var(--g-border-gray-300);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-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-main-navivation{color:var(--s-color-text-quaternary);height:auto;position:relative;width:100%}.b-main-navivation>.inner{align-items:center;background-color:currentColor;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-navivation>.inner{padding-right:var(--g-space-8)}}.b-main-navivation>.inner:has([data-role=header-input-search]:focus-within){border-color:var(--s-color-border-tertiary)}.b-main-navivation .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-navivation .searchbar>input{background-color:transparent;height:100%;outline:none;width:100%}.b-main-navivation .searchbar:focus-within+.b-breadcrumb{display:none}.b-main-navivation .network-toggle{align-items:center;background-color:currentColor;border-top-right-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-navivation .network-toggle{display:flex}}.b-main-navivation .network-toggle>svg{color:var(--s-color-text-tertiary);height:var(--g-space-5);width:var(--g-space-5)}.b-main-navivation .network-toggle:hover>svg{color:var(--s-color-text-tertiary-hover)}.b-main-navivation .b-popup-dialog>.inner{color:var(--s-color-text-tertiary);width:var(--g-space-72)}.b-main-navivation .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-navivation .b-popup-dialog .item{display:flex;gap:var(--g-space-1)}.b-main-navivation .b-popup-dialog .item>svg{height:var(--g-space-4);width:var(--g-space-4)}.b-main-navivation .b-popup-dialog .item-content{display:flex;flex-direction:column}.b-main-navivation .b-popup-dialog .item-label{font-size:var(--g-font-size-50)}.b-main-navivation .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{align-items:center}@media (min-width:calc(640 / 16 * 1rem)){.b-footer>nav{padding-bottom:var(--g-space-0)}}.b-footer .logo{align-self:flex-start;grid-column:span 2/span 2;margin-bottom:var(--g-space-3);width:100%}@media (min-width:calc(640 / 16 * 1rem)){.b-footer .logo{width:50%}}@media (min-width:calc(820 / 16 * 1rem)){.b-footer .logo{margin-bottom:var(--g-space-2);width:100%}}.b-footer .logo>svg{height:var(--g-space-7)}.b-footer .menu{display:flex;flex-direction:row;grid-column:span 10/span 10;justify-content:space-between;width:100%}@media (min-width:calc(820 / 16 * 1rem)){.b-footer .menu{display:grid}}.b-footer .menu>ul{display:flex;flex-direction:column;justify-content:flex-start;margin-bottom:var(--g-space-2)}@media (min-width:calc(1020 / 16 * 1rem)){.b-footer .menu>ul{flex-direction:row;gap:var(--g-space-4)}}.b-footer .menu>ul:first-child{grid-column:span 4/span 4}.b-footer .menu>ul:nth-child(2){grid-column:span 3/span 3}@media (min-width:calc(1020 / 16 * 1rem)){.b-footer .menu>ul:nth-child(2){justify-content:flex-end}}.b-footer .menu>ul:nth-child(3){grid-column:span 3/span 3}.b-footer .menu>ul li{margin-bottom:var(--g-space-2);margin-top:var(--g-space-2)}.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);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)}.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{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(ul:empty){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}.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{word-break:break-all}.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}.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;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{color:var(--s-color-text-tertiary);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) +: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-grid-1:repeat(1,minmax(0,1fr));--g-grid-10:repeat(10,minmax(0,1fr));--g-space-0:0;--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-600:#54595d;--g-color-gray-900:#080809;--g-color-green-50:#e7efed;--g-color-green-600:#226c57;--g-color-green-900:#144134;--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-red-600:#c95c3e;--g-color-red-900:#6b2521;--g-color-purple-600:#6c3ec9;--g-color-purple-900:#39216b}@supports (color:color(display-p3 0 0 0%)){:root{--g-color-yellow-50:#fff7eb}@media (color-gamut:p3){:root{--g-color-yellow-50:color(display-p3 0.99709 0.97106 0.92232)}}}: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-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-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-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-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)}*,::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(--g-color-light);color:var(--g-color-gray-600);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-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-4:calc(var(--cr-space-base)*4);--cr-space-5:calc(var(--cr-space-base)*5);--cr-space-6:calc(var(--cr-space-base)*6);--cr-space-8:calc(var(--cr-space-base)*8);--cr-space-10:calc(var(--cr-space-base)*10);--cr-space-24:calc(var(--cr-space-base)*24);--cr-color-brand-default:var(--s-color-bg-brand-default);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:var(--g-font-medium);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-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(--g-color-gray-900);line-height:var(--g-line-height-tight);margin-top:var(--cr-space-8)}.c-readme-view h1,.c-realm-view h1{font-size:var(--g-font-size-700);font-weight:var(--g-font-bold)}@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(--g-color-gray-600);font-weight:var(--g-font-semibold)}.c-readme-view h3,.c-realm-view h3{font-size:var(--g-font-size-300);margin-top:var(--cr-space-10)}.c-readme-view h4,.c-realm-view h4{font-size:var(--g-font-size-200);margin-top:var(--cr-space-6)}@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 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-8);margin-top:var(--cr-space-8);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-6);margin-top:var(--cr-space-6);text-align:center}.c-readme-view figcaption,.c-realm-view figcaption{color:var(--g-color-gray-600);font-size:var(--g-font-size-100)}.c-readme-view video,.c-realm-view video{margin-bottom:var(--g-space-8);margin-top:var(--g-space-8);max-width:100%}.c-readme-view p,.c-realm-view p{margin-bottom:var(--cr-space-5);margin-top:var(--cr-space-5)}.c-realm-view p:has(>a:only-child>img){margin-bottom:var(--cr-space-8);margin-top:var(--cr-space-8)}.c-readme-view p:has(>a:only-child>img){margin-bottom:var(--cr-space-8);margin-top:var(--cr-space-8)}.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-1) var(--s-color-border-tertiary);color:var(--g-color-gray-600);font-style:var(--g-italic);margin-bottom:var(--cr-space-4);margin-top:var(--cr-space-4);padding-left:var(--g-space-4)}.c-readme-view caption,.c-realm-view caption{color:var(--g-color-gray-600);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-5);margin-top:var(--cr-space-5)}.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-6);margin-top:var(--cr-space-6);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-2);margin-top:var(--cr-space-2)}.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-realm-view pre{background-color:var(--g-color-gray-50);border-radius:var(--g-border-radius-sm);margin-bottom:var(--cr-space-5);margin-top:var(--cr-space-5);overflow-x:auto;padding:var(--cr-space-4)}.c-readme-view :not(pre)>code,.c-realm-view :not(pre)>code{background-color:var(--g-color-gray-100);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-10);margin-top:var(--cr-space-10)}.c-readme-view table,.c-realm-view table{border-collapse:collapse;display:block;margin-bottom:var(--cr-space-8);margin-top:var(--cr-space-8);max-width:100%;overflow-x:auto;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(--g-color-gray-100);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(--g-color-light);border:var(--g-border-gray-300);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-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-main-navigation{color:var(--s-color-text-quaternary);height:auto;position:relative;width:100%}.b-main-navigation>.inner{align-items:center;background-color:currentColor;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:currentColor;border-top-right-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{align-items:center}@media (min-width:calc(640 / 16 * 1rem)){.b-footer>nav{padding-bottom:var(--g-space-0)}}.b-footer .logo{align-self:flex-start;grid-column:span 2/span 2;margin-bottom:var(--g-space-3);width:100%}@media (min-width:calc(640 / 16 * 1rem)){.b-footer .logo{width:50%}}@media (min-width:calc(820 / 16 * 1rem)){.b-footer .logo{margin-bottom:var(--g-space-2);width:100%}}.b-footer .logo>svg{height:var(--g-space-7)}.b-footer .menu{display:flex;flex-direction:row;grid-column:span 10/span 10;justify-content:space-between;width:100%}@media (min-width:calc(820 / 16 * 1rem)){.b-footer .menu{display:grid}}.b-footer .menu>ul{display:flex;flex-direction:column;justify-content:flex-start;margin-bottom:var(--g-space-2)}@media (min-width:calc(1020 / 16 * 1rem)){.b-footer .menu>ul{flex-direction:row;gap:var(--g-space-4)}}.b-footer .menu>ul:first-child{grid-column:span 4/span 4}.b-footer .menu>ul:nth-child(2){grid-column:span 3/span 3}@media (min-width:calc(1020 / 16 * 1rem)){.b-footer .menu>ul:nth-child(2){justify-content:flex-end}}.b-footer .menu>ul:nth-child(3){grid-column:span 3/span 3}.b-footer .menu>ul li{margin-bottom:var(--g-space-2);margin-top:var(--g-space-2)}.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);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{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(ul:empty){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}.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{word-break:break-all}.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}.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;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-12);margin-top:var(--g-space-12);padding:var(--g-space-2) var(--g-space-4)}.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_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)}.c-readme-view .gno-form_input,.c-readme-view .gno-form_select,.c-realm-view .gno-form_input,.c-realm-view .gno-form_select{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-base);color:var(--s-color-text-tertiary);display:none;font-size:var(--g-font-size-50);left:var(--g-space-2);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-2);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{border:var(--s-border-secondary);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-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-tertiary);font-weight:var(--g-font-semibold);margin-bottom:var(--g-space-2);margin-top:var(--g-space-5)}.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)}.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]:hover,.c-readme-view .gno-form_selectable input[type=radio]:hover,.c-realm-view .gno-form_selectable input[type=checkbox]:hover,.c-realm-view .gno-form_selectable input[type=radio]:hover{border-color:var(--s-color-border-tertiary)}.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-form_selectable:hover,.c-readme-view .gno-form_selectable:hover+label,.c-realm-view .gno-form_selectable:hover,.c-realm-view .gno-form_selectable:hover+label{color:var(--s-color-text-brand-default);-webkit-text-decoration:underline;text-decoration:underline}.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-caution);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-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-12);margin-top:var(--g-space-12)}.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-base);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{border:var(--s-border-secondary);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-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-tertiary);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_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]:hover,.c-readme-view .gno-form_selectable input[type=radio]:hover,.c-realm-view .gno-form_selectable input[type=checkbox]:hover,.c-realm-view .gno-form_selectable input[type=radio]:hover{border-color:var(--s-color-border-tertiary)}.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-form_selectable:hover,.c-readme-view .gno-form_selectable:hover+label,.c-realm-view .gno-form_selectable:hover,.c-realm-view .gno-form_selectable:hover+label{color:var(--s-color-text-brand-default);-webkit-text-decoration:underline;text-decoration:underline}.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-caution);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-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 diff --git a/gno.land/pkg/gnoweb/render.go b/gno.land/pkg/gnoweb/render.go index 887fac89518..a1d7ea0839c 100644 --- a/gno.land/pkg/gnoweb/render.go +++ b/gno.land/pkg/gnoweb/render.go @@ -1,17 +1,21 @@ package gnoweb import ( + "context" "fmt" "io" "log/slog" gopath "path" "strings" + "sync" + "time" "github.com/alecthomas/chroma/v2" chromahtml "github.com/alecthomas/chroma/v2/formatters/html" "github.com/alecthomas/chroma/v2/lexers" md "github.com/gnolang/gno/gno.land/pkg/gnoweb/markdown" "github.com/gnolang/gno/gno.land/pkg/gnoweb/weburl" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/yuin/goldmark" markdown "github.com/yuin/goldmark-highlighting/v2" "github.com/yuin/goldmark/parser" @@ -20,20 +24,27 @@ import ( // Renderer defines the interface for rendering realms and source files. type Renderer interface { - RenderRealm(w io.Writer, u *weburl.GnoURL, src []byte) (md.Toc, error) + RenderRealm(w io.Writer, u *weburl.GnoURL, src []byte, ctx RealmRenderContext) (md.Toc, error) RenderSource(w io.Writer, name string, src []byte) error } +// RealmRenderContext holds context information for rendering realms +type RealmRenderContext struct { + ChainId string + Remote string +} + // HTMLRenderer implements the Renderer interface for HTML output. type HTMLRenderer struct { logger *slog.Logger cfg *RenderConfig + client ClientAdapter gm goldmark.Markdown ch *chromahtml.Formatter } -func NewHTMLRenderer(logger *slog.Logger, cfg RenderConfig) *HTMLRenderer { +func NewHTMLRenderer(logger *slog.Logger, cfg RenderConfig, client ClientAdapter) *HTMLRenderer { gmOpts := append(cfg.GoldmarkOptions, goldmark.WithExtensions( markdown.NewHighlighting( markdown.WithFormatOptions(cfg.ChromaOptions...), // force using chroma config @@ -42,17 +53,57 @@ func NewHTMLRenderer(logger *slog.Logger, cfg RenderConfig) *HTMLRenderer { return &HTMLRenderer{ logger: logger, cfg: &cfg, + client: client, gm: goldmark.New(gmOpts...), ch: chromahtml.New(cfg.ChromaOptions...), } } // RenderRealm renders a realm to HTML and returns a table of contents. -func (r *HTMLRenderer) RenderRealm(w io.Writer, u *weburl.GnoURL, src []byte) (md.Toc, error) { - ctx := md.NewGnoParserContext(u) +func (r *HTMLRenderer) RenderRealm(w io.Writer, u *weburl.GnoURL, src []byte, ctx RealmRenderContext) (md.Toc, error) { + var mdctx md.GnoContext + mdctx.GnoURL = u + mdctx.ChainId = ctx.ChainId + mdctx.Remote = ctx.Remote + + var once sync.Once + + // Create a lazy function to get funcs signature + mdctx.RealmFuncSigGetter = md.RealmFuncSigGetter(func(fn string) (*vm.FunctionSignature, error) { + var msigs map[string]*vm.FunctionSignature + + var err error + once.Do(func() { + if r.client == nil { + r.logger.Warn("no client configured for fetching function signatures") + return + } + + ctx, cancel := context.WithTimeout(context.TODO(), time.Second*10) + defer cancel() + + var sigs vm.FunctionSignatures + sigs, err = r.client.ListFuncs(ctx, u.Path) + if err != nil { + r.logger.Error("unable to fetch func signature lists", "path", u.Path, "err", err) + return + } + + msigs = make(map[string]*vm.FunctionSignature) + for _, sig := range sigs { + msigs[sig.FuncName] = &sig + } + }) + if err != nil { + return nil, err + } + return msigs[fn], err + }) + + pctx := md.NewGnoParserContext(mdctx) // Use Goldmark for Markdown parsing - doc := r.gm.Parser().Parse(text.NewReader(src), parser.WithContext(ctx)) + doc := r.gm.Parser().Parse(text.NewReader(src), parser.WithContext(pctx)) if err := r.gm.Renderer().Render(w, src, doc); err != nil { return md.Toc{}, fmt.Errorf("unable to render markdown at path %q: %w", u.Path, err) } diff --git a/gno.land/pkg/gnoweb/render_test.go b/gno.land/pkg/gnoweb/render_test.go index 41b0f887440..54205f500db 100644 --- a/gno.land/pkg/gnoweb/render_test.go +++ b/gno.land/pkg/gnoweb/render_test.go @@ -11,7 +11,7 @@ import ( ) func newTestRenderer() *HTMLRenderer { - return NewHTMLRenderer(slog.New(slog.NewTextHandler(&bytes.Buffer{}, nil)), NewDefaultRenderConfig()) + return NewHTMLRenderer(slog.New(slog.NewTextHandler(&bytes.Buffer{}, nil)), NewDefaultRenderConfig(), nil) } func TestRenderer_RenderRealm_Markdown(t *testing.T) { @@ -19,7 +19,10 @@ func TestRenderer_RenderRealm_Markdown(t *testing.T) { w := &bytes.Buffer{} u := &weburl.GnoURL{Path: "/r/test"} src := []byte(`# Hello\n\nThis is a **test**.`) - toc, err := r.RenderRealm(w, u, src) + toc, err := r.RenderRealm(w, u, src, RealmRenderContext{ + ChainId: "dev", + Remote: "127.0.0.1:26657", + }) require.NoError(t, err) assert.Regexp(t, "]*>.*Hello.*", w.String()) assert.Contains(t, w.String(), "test")