{
  "docs": {
    "gettingStarted": {
      "hero": {
        "badge": "Getting Started",
        "title": "Documentation",
        "subtitle": "Everything you need to build reactive web apps with No.JS"
      },
      "introduction": {
        "title": "Introduction",
        "text": "No.JS is an HTML-first reactive framework. Build dynamic, data-driven web applications using nothing but HTML attributes — no build step, no virtual DOM, no JSX.",
        "callout": "Zero dependencies · Works in all modern browsers · No build step required"
      },
      "installation": {
        "title": "Installation",
        "cdnSubtitle": "CDN (recommended)",
        "selfHostedSubtitle": "Self-hosted",
        "selfHostedText": "Download dist/iife/no.js and include it with a <script> tag. It's a single file.",
        "npmSubtitle": "npm / ESM",
        "npmText": "When using npm, you must call NoJS.init() manually after the DOM is ready. The CDN script handles this automatically."
      },
      "quickStart": {
        "title": "Quick Start",
        "text": "Create an index.html file: include the script, add some attributes, and you're done. No app.mount(), no createApp(), no NgModule. It just works."
      },
      "howItWorks": {
        "title": "How It Works",
        "text": "On DOMContentLoaded, No.JS walks the DOM looking for elements with known attributes. Each attribute maps to a directive that is executed by priority.",
        "card1Title": "1. Parse",
        "card1Desc": "Walks the DOM looking for elements with known attributes.",
        "card2Title": "2. Resolve",
        "card2Desc": "Each attribute maps to a directive executed by priority (data fetching first, then conditionals, then rendering).",
        "card3Title": "3. React",
        "card3Desc": "All data lives in reactive contexts (Proxy-backed). When data changes, every bound element updates automatically.",
        "card4Title": "4. Scope",
        "card4Desc": "Contexts inherit from parent elements, like lexical scoping. A bind inside an each loop can access both the loop item and ancestor data."
      },
      "coreConcepts": {
        "title": "Core Concepts",
        "reactiveContextSubtitle": "Reactive Context",
        "reactiveContextText": "Every element can have a context — a reactive data object. Contexts are created by state, get, store, etc. Child elements inherit their parent's context automatically.",
        "directivePrioritySubtitle": "Directive Priority",
        "tableCol1": "Priority",
        "tableCol2": "Directives",
        "tableCol3": "Description",
        "tableRow1": "Initialize local/global state",
        "tableRow4": "Structural (add/remove DOM)",
        "tableRow5": "Rendering (update existing DOM)",
        "tableRow7": "Side effects",
        "tableRow1b": "Fetch data, error boundaries, i18n namespace",
        "tableRow2b": "Derived values and side-effect watchers",
        "tableRow5b": "Element references",
        "tableRow15": "Drag and drop setup (Now in NoJS Elements)",
        "expressionSubtitle": "Expression Syntax",
        "expressionText": "Most directive values accept JavaScript expressions evaluated against the current context:"
      }
    },
    "cheatsheet": {
      "hero": {
        "badge": "API Reference",
        "title": "Directive Cheatsheet",
        "subtitle": "Complete reference of every No.JS directive"
      },
      "data": {
        "title": "Data",
        "col1": "Directive",
        "col2": "Example",
        "col3": "Description",
        "base": "Set API base URL for descendants",
        "get": "Fetch data (GET)",
        "post": "Submit data (POST)",
        "put": "Update data (PUT)",
        "patch": "Partial update (PATCH)",
        "delete": "Delete data (DELETE)",
        "as": "Name for fetched data in context",
        "body": "Request body",
        "headers": "Request headers",
        "params": "Query parameters",
        "cached": "Cache responses (memory/local/session)",
        "into": "Write response to a named global store",
        "debounce": "Debounce reactive URL refetches (ms)",
        "retry": "Per-element retry count override",
        "refresh": "Polling interval in ms",
        "success": "Template ID to show on success",
        "then": "Expression to execute on success",
        "redirect": "Path to navigate after success",
        "confirm": "Confirmation message before request",
        "skeleton": "Show/hide a placeholder element during loading",
        "retryDelay": "Delay between retries in ms"
      },
      "state": {
        "title": "State",
        "col1": "Directive",
        "col2": "Example",
        "col3": "Description",
        "state": "Create local reactive state",
        "store": "Define/access global store",
        "computed": "Derived reactive value",
        "watch": "React to value changes",
        "persist": "Attribute of state directive — persists state to storage",
        "persistKey": "Storage key for persistence",
        "persistFields": "Comma-separated fields to persist",
        "persistSchema": "Validate restored keys against initial state",
        "model": "Two-way binding for inputs"
      },
      "rendering": {
        "title": "Rendering",
        "col1": "Directive",
        "col2": "Example",
        "col3": "Description",
        "bind": "Set text content",
        "bindHtml": "Set innerHTML (sanitized)",
        "bindStar": "Bind any attribute",
        "if": "Conditional render",
        "elseIf": "Chained conditional",
        "then": "Template for truthy",
        "else": "Template for falsy",
        "show": "Toggle visibility (CSS)",
        "hide": "Inverse of show",
        "switch": "Switch/case render",
        "case": "Case match",
        "default": "Default case"
      },
      "loops": {
        "title": "Loops",
        "col1": "Directive",
        "col2": "Example",
        "col3": "Description",
        "foreach": "Primary loop directive",
        "each": "Alias of foreach",
        "for": "Alias of foreach",
        "from": "Source array (DEPRECATED — use \"item in array\" syntax instead)",
        "template": "Template to clone",
        "index": "Index variable name",
        "key": "Unique key for diffing",
        "filter": "Filter expression",
        "sort": "Sort property (name only, not prefixed with item variable)",
        "limit": "Max items",
        "offset": "Skip items"
      },
      "events": {
        "title": "Events",
        "col1": "Directive",
        "col2": "Example",
        "col3": "Description",
        "onClick": "Click handler",
        "onSubmit": "Submit handler",
        "onInput": "Input handler",
        "onKeydown": "Key handler",
        "onInit": "Fires immediately during initialization",
        "onMounted": "Lifecycle: mounted",
        "onUnmounted": "Lifecycle: unmounted",
        "throttle": "Throttle handler execution (ms)",
        "self": "Only fires if event target is the element itself",
        "backspace": "Key modifier for Backspace key",
        "onUpdated": "Lifecycle: DOM mutation observed",
        "onError": "Lifecycle: error in subtree"
      },
      "styling": {
        "title": "Styling",
        "col1": "Directive",
        "col2": "Example",
        "col3": "Description",
        "classStar": "Toggle CSS class",
        "classMap": "Class from object",
        "styleStar": "Set inline style",
        "styleMap": "Style from object"
      },
      "forms": {
        "title": "Forms",
        "col1": "Directive",
        "col2": "Example",
        "col3": "Description",
        "validate": "Enable form/field validation (Now in NoJS Elements)",
        "error": "Error template for field",
        "success": "Success template",
        "loading": "Loading template",
        "confirm": "Confirmation dialog",
        "redirect": "Redirect on success"
      },
      "routing": {
        "title": "Routing",
        "col1": "Directive",
        "col2": "Example",
        "col3": "Description",
        "route": "Define route or link",
        "routeView": "Route outlet",
        "routeViewNamed": "Named route outlet",
        "outlet": "Target a named outlet",
        "routeActive": "Active link class",
        "guard": "Route guard condition",
        "routeActiveExact": "Exact match active class for route links",
        "redirect": "Redirect path when guard fails",
        "lazyPriority": "Load remote template before all others (Phase 0)",
        "lazyOnDemand": "Fetch route template only when first visited (route templates only)",
        "routerForward": "Navigate forward in history",
        "routerOn": "Subscribe to route changes",
        "routeWildcard": "Catch-all 404 wildcard route",
        "routerCurrent": "Current route object",
        "routeMatched": "<code>true</code> if an explicit route matched, <code>false</code> for wildcard/fallback",
        "i18nNs": "Auto-derive i18n namespace from route filename",
        "routeViewSrc": "File-based routing outlet",
        "routeIndex": "Filename for root <code>/</code> (default <code>\"index\"</code>)",
        "routeExt": "File extension (default <code>\".tpl\"</code>, fallback <code>\".html\"</code>)",
        "transitionVT": "View Transition API preset on route-view (slide, fade, scale, none)"
      },
      "animation": {
        "title": "Animation",
        "col1": "Directive",
        "col2": "Example",
        "col3": "Description",
        "animate": "Enter animation",
        "animateEnter": "Enter animation",
        "animateLeave": "Leave animation",
        "animateDuration": "Duration in ms",
        "animateStagger": "Stagger delay",
        "transition": "CSS transition (class-based, for regular elements)",
        "transitionVT": "View Transition API preset on route-view (slide, fade, scale, none)"
      },
      "dnd": {
        "title": "Drag and Drop (Now in NoJS Elements)",
        "col1": "Directive",
        "col2": "Example",
        "col3": "Description",
        "drag": "Make element draggable",
        "dragType": "Data type identifier",
        "dragEffect": "Allowed effect (move/copy/link/all)",
        "dragHandle": "Restrict drag to handle selector",
        "dragDisabled": "Disable drag conditionally",
        "dragClass": "Class added while dragging",
        "dragGroup": "Scope drag to a named group",
        "drop": "Make element a drop zone",
        "dropAccept": "Accepted drag type(s)",
        "dropEffect": "Visual feedback effect",
        "dropClass": "Class added on drag-over",
        "dropDisabled": "Disable drop conditionally",
        "dropMax": "Maximum items in drop zone",
        "dropSort": "Enable positional sorting",
        "dropPlaceholder": "Placeholder template during drag-over",
        "dropSettleClass": "Custom CSS class for settle animation",
        "dropEmptyClass": "Custom CSS class for empty state on drag-list",
        "dragList": "Sortable list bound to state array",
        "dragListKey": "Unique key for each item",
        "dragListItem": "Item template selector",
        "dragListCopy": "Copy instead of move on transfer",
        "dragListRemove": "Remove items from source on transfer",
        "dragMultiple": "Lasso / multi-select on children",
        "dragMultipleClass": "Class added to selected items",
        "dropRejectClass": "Class added on rejected drag-over"
      },
      "i18n": {
        "title": "i18n",
        "col1": "Directive",
        "col2": "Example",
        "col3": "Description",
        "t": "Translate key",
        "tStar": "Translation param",
        "tHtml": "Render translation as sanitized HTML"
      },
      "headManagement": {
        "title": "Head Management",
        "col1": "Directive",
        "col2": "Example",
        "col3": "Description",
        "pageTitle": "Set document.title reactively",
        "pageDescription": "Set <meta name=\"description\">",
        "pageCanonical": "Set <link rel=\"canonical\">",
        "pageJsonld": "Set <script type=\"application/ld+json\">"
      },
      "misc": {
        "title": "Misc",
        "col1": "Directive",
        "col2": "Example",
        "col3": "Description",
        "ref": "Named element ref",
        "call": "Trigger API call",
        "trigger": "Emit custom event",
        "use": "Instantiate template",
        "src": "Remote template (see also: lazy)",
        "loading": "Placeholder shown while remote template loads; removed on arrival",
        "include": "Synchronously clone an inline template into the current position",
        "errorBoundary": "Error boundary",
        "var": "Template variable name"
      }
    },
    "stateManagement": {
      "hero": {
        "badge": "Guides",
        "title": "State Management",
        "subtitle": "Local state, global stores, computed properties, and watchers"
      },
      "state": {
        "title": "state — Local State",
        "text": "Creates a reactive context scoped to the element and its children.",
        "preview": "Preview",
        "helloLabel": "Hello,",
        "countLabel": "Count:",
        "resetBtn": "Reset"
      },
      "store": {
        "title": "store — Global Store",
        "text": "A global reactive store accessible from anywhere. Ideal for auth state, theme, shared data."
      },
      "configStores": {
        "title": "Pre-initializing Stores via config()",
        "text": "You can pre-create stores with initial data inside NoJS.config(). Each key in the stores object becomes a named global store, accessible immediately via $store.name.",
        "callout": "If a store already exists (e.g. created via a store attribute), config() will not overwrite it — the existing store is preserved."
      },
      "into": {
        "title": "into — Write Fetch Results to a Store",
        "text": "The into attribute on any HTTP directive writes the response directly into a named global store.",
        "callout": "The store doesn't need to be pre-defined — into will create it if it doesn't exist."
      },
      "computed": {
        "title": "computed — Derived State",
        "text": "Values that are automatically recalculated when dependencies change.",
        "preview": "Preview",
        "priceLabel": "Price:",
        "qtyLabel": "Qty:",
        "subtotalLabel": "Subtotal:",
        "totalLabel": "Total:"
      },
      "watch": {
        "title": "watch — Side Effects",
        "text": "Execute an action whenever a value changes."
      },
      "persistence": {
        "title": "State Persistence",
        "text": "Persist state across page reloads using localStorage or sessionStorage."
      },
      "persistSchema": {
        "title": "persist-schema — Schema Validation",
        "text": "When restoring persisted state, persist-schema validates that restored keys match the initial state shape. Keys not present in the initial state are discarded. This prevents stale or corrupted data from breaking your app after schema changes."
      },
      "scoping": {
        "title": "Context Scoping & Shadowing",
        "text": "Contexts inherit from parent elements like lexical scoping. A child state can shadow a parent property — the child sees its own value, while siblings see the parent's.",
        "callout": "Shadowing is intentional and follows JavaScript's scoping rules. If you need shared state across siblings, use a store instead."
      },
      "notify": {
        "title": "NoJS.notify() — Flush Store Updates",
        "text": "When external JavaScript mutates a store via NoJS.store, call NoJS.notify() to flush all pending DOM updates.",
        "callout": "Only needed when mutating NoJS.store from plain JavaScript — outside of HTML expressions. Store mutations inside on:click or bind expressions are handled automatically."
      }
    },
    "dataBinding": {
      "hero": {
        "badge": "Guides",
        "title": "Data Binding",
        "subtitle": "One-way and two-way data binding with bind and model"
      },
      "bind": {
        "title": "bind — Text Content",
        "text": "Replaces the element's textContent with the evaluated expression."
      },
      "bindHtml": {
        "title": "bind-html — Inner HTML",
        "text": "Renders evaluated expression as HTML. Sanitized by default.",
        "callout": "⚠️ Uses built-in DOMParser-based structural sanitization: strips <script> tags, blocks on* event handlers, and removes javascript: URIs."
      },
      "bindAttr": {
        "title": "bind-* — Attribute Binding",
        "text": "Bind any HTML attribute dynamically."
      },
      "model": {
        "title": "model — Two-Way Binding",
        "text": "For form inputs, model creates automatic two-way data binding.",
        "preview": "Preview",
        "nameLabel": "Name",
        "placeholder": "Type your name...",
        "checkbox": "I agree",
        "agreedLabel": "Agreed:",
        "helloPrefix": "Hello,"
      },
      "radioSelect": {
        "title": "Radio Buttons & Multi-Select",
        "text": "model works with all input types. For radio buttons, bind the same model name to a group — the value updates to the selected radio's value. For multi-select, the model value becomes an array of selected options."
      },
      "commonMistakes": {
        "title": "Common Mistakes",
        "text": "Avoid these common data binding pitfalls:",
        "mistake1": "Using bind-html without understanding sanitization — all content is sanitized by default, but be aware of what gets stripped.",
        "mistake2": "Forgetting that bind replaces textContent — any child elements inside a bind target are removed.",
        "mistake3": "Using model on non-form elements — model only works on input, textarea, and select elements."
      }
    },
    "events": {
      "hero": {
        "badge": "Guides",
        "title": "Event Handling",
        "subtitle": "Bind DOM events directly in HTML with on:event syntax"
      },
      "handlers": {
        "title": "on:* — Event Handlers",
        "text": "Bind any DOM event directly in HTML. Access state and context variables directly in the handler expression.",
        "preview": "Preview",
        "inputPlaceholder": "Type something...",
        "youTyped": "You typed:",
        "countLabel": "Count:"
      },
      "modifiers": {
        "title": "Event Modifiers",
        "text": "Modifiers let you control event behavior directly in the attribute:"
      },
      "eventAndEl": {
        "title": "$event & $el",
        "text": "$event is the native DOM event. $el refers to the current element."
      },
      "lifecycle": {
        "title": "Lifecycle Hooks",
        "col1": "Hook",
        "col2": "When",
        "onInit": "Directive first processed",
        "onMounted": "Element inserted into visible DOM",
        "onUpdated": "DOM mutation observed (via MutationObserver)",
        "onUnmounted": "Element removed from DOM",
        "onError": "Error in this element's subtree"
      },
      "keyModifiers": {
        "title": "Key Modifiers",
        "text": "Use key modifiers on keyboard events to filter by specific keys. Combine with Ctrl, Alt, Shift, or Meta for shortcuts.",
        "col1": "Modifier",
        "col2": "Key",
        "enter": "Enter / Return",
        "escape": "Escape",
        "space": "Space",
        "tab": "Tab",
        "backspace": "Backspace",
        "arrowUp": "Arrow Up",
        "arrowDown": "Arrow Down",
        "arrowLeft": "Arrow Left",
        "arrowRight": "Arrow Right",
        "ctrl": "Ctrl (Control)",
        "alt": "Alt (Option on Mac)",
        "shift": "Shift",
        "meta": "Meta (Cmd on Mac, Win on Windows)",
        "delete": "Delete"
      }
    },
    "conditionals": {
      "hero": {
        "badge": "Guides",
        "title": "Conditionals",
        "subtitle": "Control rendering with if, show, hide, and switch"
      },
      "ifThenElse": {
        "title": "if / then / else",
        "text": "Conditionally render elements or templates based on expressions.",
        "preview": "Preview",
        "checkbox": "Logged in",
        "welcome": "✅ Welcome back!",
        "login": "Please log in."
      },
      "elseIf": {
        "title": "else-if — Chained Conditionals"
      },
      "showHide": {
        "title": "show / hide",
        "text": "Toggles display: none without adding/removing DOM elements. Better for frequently toggled elements.",
        "comparisonTitle": "if vs show",
        "colIf": "if",
        "colShow": "show",
        "mechanism": "Mechanism",
        "mechanismIf": "Adds/removes DOM elements",
        "mechanismShow": "Toggles CSS display",
        "bestFor": "Best for",
        "bestForIf": "Rarely toggled content",
        "bestForShow": "Frequently toggled content",
        "preservesState": "Preserves state",
        "preservesIf": "No (re-creates)",
        "preservesShow": "Yes"
      },
      "switchCase": {
        "title": "switch / case",
        "text": "Render one of many templates based on a value.",
        "inlineSubtitle": "Inline Content (no templates)",
        "multiValueSubtitle": "Multi-Value Case"
      }
    },
    "loops": {
      "hero": {
        "badge": "Guides",
        "title": "Iteration Directives",
        "subtitle": "Iterate over arrays with foreach, each, and for"
      },
      "foreach": {
        "title": "foreach — Iterate Over Arrays",
        "text": "The primary iteration directive. Use foreach=\"item in array\" to loop over arrays. each and for are aliases with identical behavior.",
        "preview": "Preview",
        "col1": "Attribute",
        "col2": "Description",
        "foreach": "Expression: item in array",
        "from": "Source array (DEPRECATED — use item in array syntax)",
        "index": "Variable name for the index (default: $index)",
        "key": "Unique key expression for element identification and tracking",
        "else": "Template ID to render when array is empty",
        "filter": "Expression to filter items",
        "sort": "Property path to sort by (prefix - for descending)",
        "limit": "Maximum number of items to render",
        "offset": "Number of items to skip",
        "template": "External template ID (optional — children become template if omitted)"
      },
      "fullExample": {
        "title": "Full Example with All Attributes",
        "text": "A complete example showing foreach with filtering, sorting, pagination, and an empty-state fallback."
      },
      "aliases": {
        "title": "Aliases: each and for",
        "text": "each and for are aliases of foreach — they share the same handler and support all the same attributes."
      },
      "inline": {
        "title": "Inline Children Template",
        "text": "When no template attribute is specified, the element's children become the repeating template."
      },
      "deprecated": {
        "title": "Deprecated: from Attribute",
        "text": "The from attribute still works but is deprecated. Use the \"item in array\" syntax instead. Using from will emit a console warning."
      },
      "contextVars": {
        "title": "Loop Context Variables",
        "col1": "Variable",
        "col2": "Description",
        "index": "Current index (0-based)",
        "count": "Total number of items",
        "first": "true if first item",
        "last": "true if last item",
        "even": "true if index is even",
        "odd": "true if index is odd"
      },
      "nested": {
        "title": "Nested Loops",
        "text": "Child loops (foreach, each, or for) can access parent scope variables."
      },
      "reactivity": {
        "title": "Reactivity",
        "text": "Loop directives are fully reactive. When the source array changes (items added, removed, or reordered), the DOM updates automatically. Use the key attribute for efficient diffing — without it, the entire list re-renders on every change."
      },
      "objectIteration": {
        "title": "Object Iteration",
        "text": "To iterate over an object's entries, use the keys or values filter to convert it to an array first.",
        "callout": "Direct object iteration is not supported — always convert to an array with keys, values, or Object.entries() in a computed expression."
      }
    },
    "templates": {
      "hero": {
        "badge": "Guides",
        "title": "Templates",
        "subtitle": "Reusable HTML fragments with variables, slots, and remote loading"
      },
      "basic": {
        "title": "Basic Template",
        "text": "Templates are reusable HTML fragments that are never rendered directly. They are cloned when referenced by directives like then, else, template, loading, error, etc."
      },
      "var": {
        "title": "Template Variables (var)",
        "text": "Templates can declare which variable they expect from the calling context."
      },
      "slots": {
        "title": "Template Slots",
        "text": "Allow templates to accept projected content."
      },
      "remote": {
        "title": "Remote Templates (src)",
        "text": "Load templates from external HTML files."
      },
      "recursive": {
        "subtitle": "Recursive Loading",
        "text": "Remote templates are loaded recursively — if a remote template itself contains <template src=\"...\"> elements, those are automatically resolved too:"
      },
      "remoteRoutes": {
        "subtitle": "Remote Templates in Routes",
        "text": "Remote templates inside route content are also automatically resolved before the route renders. See Routing for details."
      },
      "lazy": {
        "title": "Lazy Loading (lazy)",
        "text": "Control when remote templates are fetched using the lazy attribute on <template src=\"...\"> elements. NoJS loads templates in phases to optimise time-to-first-render.",
        "col1": "Value",
        "col2": "Behaviour",
        "absent": "(absent)",
        "absentDesc": "Default auto-prioritisation: content-include templates and the current route template load before first render; other route templates are preloaded in the background after first render.",
        "priorityDesc": "Force load before everything else — even before regular content includes. Useful for critical shared layout templates.",
        "ondemandDesc": "Only valid on route templates. Never preloaded — fetched lazily the first time the user navigates to that route. Ideal for heavy or rarely-visited pages."
      },
      "phases": {
        "subtitle": "Loading Phases",
        "text": "Templates are resolved in four ordered phases: Phase 0 fetches lazy=\"priority\" templates first; Phase 1 fetches all other non-route templates plus the active route template (blocking before first render); Phase 2 preloads remaining route templates in the background after first render; and on-demand fetches lazy=\"ondemand\" route templates only when the user first navigates to them."
      },
      "loading": {
        "title": "Loading Placeholder (loading)",
        "text1": "Show a placeholder template while a remote template is being fetched. The placeholder is inserted synchronously — before any network request — and removed automatically once the real content arrives. Works for both static content-includes and nested templates inside route pages.",
        "text2": "Both plain IDs and #id syntax are accepted. The placeholder template is cloned each time, so it can be reused across multiple remote templates:"
      },
      "include": {
        "title": "Inline Template Include (include)",
        "text1": "Clone an inline template into the current position synchronously, before any fetches. Useful for injecting reusable markup (e.g. icon sets, common fragments) without making a network request.",
        "text2": "include and loading serve different purposes: include clones inline content permanently; loading inserts a temporary placeholder that disappears once a remote template finishes loading."
      }
    },
    "dataFetching": {
      "hero": {
        "badge": "Guides",
        "title": "Data Fetching",
        "subtitle": "Declarative HTTP requests — just add attributes to HTML elements"
      },
      "baseUrl": {
        "title": "Base URL",
        "text1": "Set once on any ancestor element. All descendant get, post, etc. resolve relative URLs against it.",
        "text2": "Override for specific sections:",
        "text3": "Absolute URLs skip base resolution:"
      },
      "config": {
        "title": "Programmatic Configuration"
      },
      "headers": {
        "title": "Per-Request Headers"
      },
      "get": {
        "title": "get — Fetch and Render Data",
        "attributesTitle": "Attributes",
        "col1": "Attribute",
        "col2": "Type",
        "col3": "Description",
        "get": "URL to fetch (GET request)",
        "as": "Name to assign the response in the context. Default: \"data\"",
        "loading": "Template ID to show while loading (e.g. \"#skeleton\")",
        "error": "Template ID to show on fetch error",
        "empty": "Template ID to show when response is empty array/null",
        "refresh": "Auto-refresh interval in ms (polling)",
        "cached": "Cache responses. cached = memory, cached=\"local\" = localStorage, cached=\"session\" = sessionStorage",
        "into": "Write response to a named global store",
        "debounce": "Debounce in ms (useful with reactive URLs)",
        "headers": "JSON string of additional headers",
        "params": "Expression that resolves to query params object"
      },
      "fullExample": {
        "title": "Full Example"
      },
      "reactiveUrls": {
        "title": "Reactive URLs",
        "text": "URLs that reference state variables re-fetch automatically when those values change."
      },
      "mutations": {
        "title": "post, put, patch, delete — Mutating Requests",
        "text": "Used on forms or triggered via call.",
        "formSubmissionTitle": "Form Submission",
        "putPatchDeleteTitle": "PUT / PATCH / DELETE"
      },
      "mutationAttrs": {
        "title": "Mutation Attributes",
        "col1": "Attribute",
        "col2": "Description",
        "method": "URL for the request",
        "body": "Request body (JSON string with interpolation). For forms, auto-serializes fields",
        "success": "Template ID to render on success. Receives response as var",
        "error": "Template ID to render on error. Receives error as var",
        "loading": "Template ID to show during request",
        "confirm": "Show browser confirm() dialog before sending",
        "redirect": "URL to navigate to on success (SPA route)",
        "then": "Expression to execute on success (e.g. \"users.push(result)\")",
        "into": "Write response to a named global store",
        "cached": "Cache responses (memory/local/session). Note: caching only applies to GET requests."
      },
      "lifecycle": {
        "title": "Request Lifecycle"
      },
      "liveDemo": {
        "title": "Live Demo — API Fetch",
        "label": "Result"
      }
    },
    "routing": {
      "hero": {
        "badge": "Guides",
        "title": "Routing",
        "subtitle": "Full client-side SPA navigation with no page reloads"
      },
      "definition": {
        "title": "Route Definition"
      },
      "params": {
        "title": "Route Parameters & Query"
      },
      "context": {
        "title": "$route — Route Context",
        "col1": "Property",
        "col2": "Description",
        "path": "Current path (e.g. \"/users/42\")",
        "params": "Route parameters (e.g. { id: \"42\" })",
        "query": "Query string params (e.g. { q: \"hello\" })",
        "hash": "URL hash (e.g. \"#section\")",
        "matched": "Whether an explicit route matched (true) or a wildcard/fallback is rendering (false)"
      },
      "activeStyle": {
        "title": "Active Route Styling"
      },
      "guards": {
        "title": "Route Guards"
      },
      "programmatic": {
        "title": "Programmatic Navigation",
        "callout": "$router.push() and $router.replace() return Promises — navigation (including remote template loading) is fully async. In on:click handlers the return value is ignored, but in scripts you can await them:"
      },
      "nested": {
        "title": "Nested Routes"
      },
      "remoteTemplates": {
        "title": "Remote Templates in Routes",
        "text1": "Route templates can include <template src=\"...\"> to load content from external files. They are automatically resolved before the route renders:",
        "text2": "Nested remote templates (a remote template that itself contains more <template src>) are recursively loaded."
      },
      "fileBased": {
        "title": "File-Based Routing",
        "text": "Instead of declaring each route template manually, point your <code>route-view</code> outlet at a folder. No.JS will automatically resolve route paths to template files inside that folder.",
        "howItWorks": "How it works",
        "list1": "Add <code>route-view</code> to your outlet element — file-based routing is enabled by default (config <code>router.templates: \"pages\"</code>). Override per-outlet with <code>src=\"folder/\"</code>.",
        "list2": "When a user navigates to <code>/analytics</code>, No.JS resolves it to <code>pages/analytics.tpl</code>",
        "list3": "The template is fetched, cached, and rendered — automatically",
        "attributesTitle": "Attributes",
        "colAttr": "Attribute",
        "colDefault": "Default",
        "colDesc": "Description",
        "srcDesc": "Base folder for template resolution (per-outlet override; config: <code>router.templates</code>)",
        "routeIndexDesc": "Filename for the root route <code>/</code>",
        "extDesc": "File extension appended to route segments (fallback: <code>\".html\"</code>)",
        "i18nNsDesc": "When present, auto-derives i18n namespace from filename",
        "callout": "<strong>Config default:</strong> The default <code>router.templates</code> is <code>\"pages\"</code>, so file-based routing works out of the box — just add <code>route-view</code> to your outlet. Override with <code>NoJS.config({ router: { templates: 'views' } })</code> or per-outlet via <code>src=\"./custom/\"</code>.",
        "exampleTitle": "Example — SaaS Dashboard",
        "exampleText": "That's it — <strong>two lines</strong> for a full SPA with six routes.",
        "mixingTitle": "Mixing Explicit & File-Based Routes",
        "mixingText": "Explicit <code>&lt;template route=\"...\"&gt;</code> declarations <strong>always take priority</strong>. This lets you combine both approaches — use file-based routing for simple pages and explicit templates for routes that need guards, params, or named outlets:",
        "autoI18nTitle": "Auto i18n Namespace",
        "autoI18nText": "When the <code>route-view</code> element has an <code>i18n-ns</code> attribute (even without a value), No.JS automatically loads the i18n namespace matching the filename:",
        "autoI18nText2": "This replaces the need to add <code>i18n-ns=\"...\"</code> on each route template individually."
      },
      "lazyLoading": {
        "title": "Lazy Template Loading",
        "text": "The lazy attribute on <template src=\"...\"> controls when a remote template is fetched relative to the first render. Use it to prioritise critical templates and defer heavy or rarely-visited pages.",
        "col1": "Value",
        "col2": "Phase",
        "col3": "Behaviour",
        "absent": "(absent)",
        "absentPhase": "1 or 2",
        "absentDesc": "Auto: non-route templates and the active route template load before first render (Phase 1); other route templates preload in the background after first render (Phase 2).",
        "priorityPhase": "0",
        "priorityDesc": "Load before everything else — even before regular content includes. Use for critical shared layout templates.",
        "ondemandPhase": "on demand",
        "ondemandDesc": "Only valid on route templates. Never preloaded — fetched the first time the user navigates to that route. Ideal for heavy or rarely-visited pages."
      },
      "anchor": {
        "title": "Anchor Links",
        "text1": "When using useHash: true, the URL hash (#) is used for routing (e.g. #/docs). This normally conflicts with standard anchor links like <a href=\"#section\"> — but No.JS handles it automatically in both hash and history modes.",
        "text2": "Anchor links that point to an element id on the page are intercepted by the router: the target element is scrolled into view smoothly, and the clicked link receives an active class. The route itself is not affected.",
        "howItWorks": "How it works:",
        "list1": "Clicking <a href=\"#introduction\"> scrolls to <div id=\"introduction\"> with smooth behavior",
        "list2": "The .active class is toggled on the clicked link (and removed from siblings)",
        "list3": "The current route path is preserved — no navigation occurs",
        "list4": "Links with a route attribute are always treated as route navigation, not anchors",
        "tip": "Tip: Style the active anchor link with .active in your CSS — the router manages the class for you."
      },
      "namedOutlets": {
        "title": "Named Outlets (route-view)",
        "text": "Multiple route-view outlets can coexist in the same page. Give each outlet a name via the attribute value, and point route templates at specific outlets using the outlet attribute.",
        "callout": "Outlets with no matching template for the current route are always cleared on navigation."
      },
      "catchAll": {
        "title": "404 / Catch-All Routes",
        "text": "Use <code>route=\"*\"</code> to define a <strong>wildcard catch-all</strong> template that renders when no explicit route matches the current path. The wildcard is always evaluated last, regardless of DOM order.",
        "text2": "Explicit routes <strong>always take priority</strong> — the wildcard only fires when <code>matchRoute()</code> returns no match.",
        "fallbackTitle": "Automatic 404 Fallback",
        "fallbackText": "If you don't define a <code>route=\"*\"</code> template, No.JS automatically shows a minimal built-in 404 page when no route matches. This ensures users always see something meaningful instead of a blank outlet.",
        "fallbackTip": "The built-in fallback is intentionally minimal and unstyled. Define your own <code>route=\"*\"</code> template for production apps.",
        "namedTitle": "Named Outlet Wildcards",
        "namedText": "Each named outlet can have its own wildcard fallback. When no route matches for an outlet, the framework resolves fallbacks in this order:",
        "namedList1": "<strong>Local wildcard</strong> — <code>&lt;template route=\"*\" outlet=\"{name}\"&gt;</code> for that specific outlet",
        "namedList2": "<strong>Global wildcard</strong> — <code>&lt;template route=\"*\"&gt;</code> (the default outlet's wildcard), used only for non-default outlets",
        "namedList3": "<strong>Built-in 404</strong> — the framework's minimal fallback page",
        "namedText2": "If the sidebar has no local wildcard, it falls back to the global <code>route=\"*\"</code>. If neither exists, the built-in 404 is used.",
        "matchedTitle": "$route.matched",
        "matchedText": "The <code>$route.matched</code> boolean tells you whether the current path hit an explicit route (<code>true</code>) or a wildcard/fallback (<code>false</code>). Use it for conditional rendering inside your templates:",
        "matchedText2": "<code>$route.matched</code> is set <strong>before</strong> the template renders, so it's always available during processing.",
        "remoteTitle": "Remote 404 Template",
        "remoteText": "Wildcard routes support all the same attributes as regular route templates, including <code>src</code> for remote loading:",
        "remoteText2": "The remote template is fetched, cached, and rendered just like any other route template — and it has full access to <code>$route.path</code>, <code>$route.matched</code>, and all other framework features.",
        "fileBasedTitle": "File-Based Routing 404",
        "fileBasedText": "When using file-based routing, navigating to a path whose <code>.tpl</code> file doesn't exist on the server (HTTP 404 or other error) automatically triggers the wildcard fallback chain.",
        "fileBasedText2": "The failed HTTP response is <strong>not</strong> cached — subsequent navigations to other paths are unaffected."
      },
      "headAttributes": {
        "title": "Route Head Attributes",
        "text": "Route templates can declare <code>page-title</code> and <code>page-description</code> directly on the <code>&lt;template&gt;</code> tag. When the route activates, the corresponding <code>&lt;head&gt;</code> tags are updated automatically.",
        "text2": "Both static strings and dynamic expressions are supported:",
        "colAttr": "Attribute",
        "colDesc": "Description",
        "pageTitleDesc": "Sets <code>document.title</code> when the route is active",
        "pageDescriptionDesc": "Sets the <code>&lt;meta name=\"description\"&gt;</code> content when the route is active",
        "callout": "For full head management (canonical URLs, JSON-LD, Open Graph), see the <a href=\"/docs/head-management\">Head Management</a> guide."
      },
      "focusBehavior": {
        "title": "Accessibility — Focus Management",
        "text": "After each navigation, No.JS moves focus to a predictable element inside the new route content. This ensures screen readers announce the page change and keyboard users land in a useful spot.",
        "text2": "When focusBehavior is 'auto' (the default), the framework walks a priority list:",
        "priority1": "<code>[autofocus]</code> — an element with the <code>autofocus</code> attribute inside the route template",
        "priority2": "<code>h1</code> — the first <code>&lt;h1&gt;</code> inside the route content",
        "priority3": "<code>[tabindex=\"-1\"]</code> — a manually focusable container",
        "priority4": "The route-view element itself (last resort)",
        "defaultTitle": "Default Behavior",
        "defaultText": "Focus management is <strong>enabled by default</strong> — you don't need to configure anything. The <code>'auto'</code> strategy works out of the box.",
        "timingTitle": "Timing",
        "timingText": "Focus is moved <strong>after</strong> the route template is fully rendered and all remote templates inside it are resolved. This guarantees the target element exists in the DOM.",
        "sideEffectsTitle": "Side Effects",
        "sideEffectsText": "The focused element receives a <code>tabindex=\"-1\"</code> if it doesn't already have a tabindex, and the page scrolls to bring it into view via <code>scrollIntoView({ block: 'nearest' })</code>.",
        "futureTitle": "Future Values",
        "futureText": "Currently only <code>'auto'</code> is supported. Future versions may add <code>'none'</code> (opt out) and <code>'target'</code> (focus a specific selector).",
        "ariaLiveTitle": "ARIA Live Region",
        "ariaLiveText": "For screen readers that don't respond to focus changes, add <code>aria-live=\"polite\"</code> to your <code>route-view</code>. The browser will announce new content as it appears:"
      },
      "viewTransitions": {
        "title": "View Transitions",
        "text": "The <code>transition</code> attribute on <code>route-view</code> now uses the <strong>View Transition API</strong> by default. Just pick a preset name and route changes are animated natively by the browser — no manual CSS required.",
        "presetsTitle": "Built-in Presets",
        "presetsText": "No.JS ships with four built-in transition presets:",
        "colPreset": "Preset",
        "colEffect": "Effect",
        "presetSlide": "Directional slide — content slides left/right based on navigation direction (forward/backward)",
        "presetFade": "Crossfade — old content fades out while new content fades in",
        "presetScale": "Scale zoom — old content scales down while new content scales up",
        "presetNone": "Instant swap — no animation, content replaces immediately",
        "configTitle": "Configuration",
        "configText": "The View Transition API is <strong>enabled by default</strong> via <code>router.viewTransition: true</code>. Set it to <code>false</code> to fall back to the legacy class-based transition system.",
        "customCssTitle": "Custom CSS",
        "customCssText": "For custom animations, target the <code>::view-transition-old(route-content)</code> and <code>::view-transition-new(route-content)</code> pseudo-elements. The <code>view-transition-name: route-content</code> is set automatically on any outlet with a <code>transition</code> attribute.",
        "howItWorksTitle": "How it Works",
        "howItWorksText": "When a route change occurs with a <code>transition</code> preset:",
        "howStep1": "The router detects navigation direction (<strong>forward</strong> or <strong>backward</strong>) from the history stack",
        "howStep2": "<code>document.startViewTransition()</code> is called, capturing the current outlet state",
        "howStep3": "The new route content is rendered inside the outlet",
        "howStep4": "The browser animates between old and new snapshots using the preset CSS rules",
        "deprecationTitle": "Migration from Class-Based Transitions",
        "deprecationText": "The old class-based transition system (<code>*-enter</code>, <code>*-enter-active</code>, <code>*-leave</code>, etc.) on <code>route-view</code> is <strong>deprecated</strong>. It still works when <code>router.viewTransition</code> is set to <code>false</code>, but the View Transition API is the recommended approach going forward.",
        "deprecationCallout": "<strong>Migration is mostly automatic.</strong> If you already use <code>transition=\"fade\"</code> on a <code>route-view</code>, it now uses the View Transition API by default — no code changes needed. Your custom <code>.fade-enter</code> / <code>.fade-leave</code> CSS is simply ignored (unless you set <code>viewTransition: false</code>)."
      }
    },
    "formsValidation": {
      "hero": {
        "badge": "Guides",
        "title": "Forms & Validation",
        "subtitle": "Declarative form submission with built-in and custom validation rules"
      },
      "submission": {
        "title": "Declarative Form Submission"
      },
      "rules": {
        "title": "Validation Rules"
      },
      "perRuleErrors": {
        "title": "Per-Rule Error Messages",
        "text": "Use error-{rule} attributes to set a custom message for a specific rule, or error for a generic fallback."
      },
      "errorTemplates": {
        "title": "Error Templates",
        "text": "Point an error attribute to a <template> using a # prefix to render rich error UI. Inside the template, $error contains the message and $rule the failing rule name."
      },
      "errorClass": {
        "title": "Error CSS Class",
        "text": "Use error-class on the form or on individual fields to toggle a CSS class when a field is invalid and touched."
      },
      "formContext": {
        "title": "$form — Form Context",
        "text": "Inside any <form> with the validate attribute, $form provides:",
        "col1": "Property",
        "col2": "Type",
        "col3": "Description",
        "valid": "true if all fields pass validation",
        "dirty": "true if any field has been modified",
        "touched": "true if any field has been focused and blurred",
        "submitting": "true while the request is in flight",
        "pending": "true while async validators are running",
        "errors": "Map of field names → error messages",
        "values": "Current form values",
        "firstError": "Error message of the first invalid field (DOM order)",
        "errorCount": "Number of fields currently failing validation",
        "fields": "Per-field state object (valid, error, dirty, touched)",
        "reset": "Reset form to initial values, clear errors and classes"
      },
      "formFields": {
        "title": "$form.fields — Per-Field State",
        "text": "$form.fields exposes individual field state keyed by field name.",
        "asTitle": "Field Aliases with as",
        "asText": "Use the as attribute to expose a field's state under a custom name in the context."
      },
      "validateOn": {
        "title": "Validation Triggers (validate-on)",
        "text": "By default, validation runs on input and focusout. Use validate-on to change when visual feedback appears.",
        "note": "Internally, $form data is always kept up-to-date regardless of validate-on. The trigger only controls when visual feedback is shown."
      },
      "validateIf": {
        "title": "Conditional Validation (validate-if)",
        "text": "Skip validation for a field based on a condition. When validate-if evaluates to false, the field is treated as valid."
      },
      "autoDisable": {
        "title": "Auto-Disable Submit Buttons",
        "text": "Submit buttons are automatically disabled when the form is invalid. Buttons with type=\"button\" are not affected."
      },
      "customValidators": {
        "title": "Custom Validators"
      },
      "liveDemo": {
        "title": "Live Demo — Registration Form",
        "label": "Result",
        "usernameLabel": "Username",
        "usernamePlaceholder": "Choose a username",
        "emailLabel": "Email",
        "emailPlaceholder": "you@example.com",
        "ageLabel": "Age",
        "agePlaceholder": "Your age",
        "roleLabel": "Role",
        "roleDefault": "Select a role",
        "roleDev": "Developer",
        "roleDesign": "Designer",
        "roleMgr": "Manager",
        "termsLabel": "I agree to the terms",
        "registerButton": "Register",
        "successMessage": "Registration complete! Welcome aboard."
      }
    },
    "styling": {
      "hero": {
        "badge": "Guides",
        "title": "Dynamic Styling",
        "subtitle": "Toggle CSS classes and inline styles reactively"
      },
      "classToggle": {
        "title": "class-* — Toggle Classes",
        "text": "Add or remove CSS classes reactively based on expressions. Use class-{name} for individual toggles or class-map for multiple classes at once.",
        "multiObject": "Multiple Classes from Object",
        "multiObjectText": "Use class-map with an object expression to toggle multiple classes. Keys are class names, values are boolean expressions.",
        "fromArray": "From Array"
      },
      "classStatic": {
        "title": "Static class Interaction",
        "text": "class-* directives work alongside static class attributes. No.JS only toggles the directive-managed classes — static classes are never removed.",
        "callout": "You can safely combine class=\"card\" with class-active=\"isActive\". The card class is always present; active is toggled."
      },
      "inlineStyles": {
        "title": "style-* — Inline Styles",
        "text": "Set inline CSS properties reactively using style-{property} attributes. Property names use kebab-case (e.g. style-font-size, style-background-color).",
        "fromObject": "From Object",
        "fromObjectText": "Use style-map with an object expression to set multiple inline styles at once."
      },
      "cssCustomProperties": {
        "title": "CSS Custom Properties",
        "text": "Use style-* with CSS custom properties (variables) for theme-aware reactive styling. Property names follow the same kebab-case convention.",
        "callout": "CSS custom properties set via style-* are scoped to the element and inherited by its children — just like regular CSS custom properties."
      },
      "liveDemo": {
        "title": "Live Demo — Dynamic Styling",
        "label": "Result",
        "toggleButton": "Toggle Active"
      }
    },
    "animations": {
      "hero": {
        "badge": "Guides",
        "title": "Animations & Transitions",
        "subtitle": "Declarative enter/leave animations and CSS transitions"
      },
      "enterLeave": {
        "title": "animate — Enter/Leave Animations",
        "attrsTitle": "Animation Attributes",
        "col1": "Attribute",
        "col2": "Description",
        "row1": "CSS animation class added when the element enters",
        "row2": "CSS animation class added when the element leaves",
        "row3": "Duration in milliseconds forwarded to animationDuration and used as the fallback timeout. When omitted the fallback fires on the next event-loop tick (0 ms)",
        "row4": "Delay in milliseconds between each item in a loop (each / foreach)",
        "fallbackCallout": "Fallback timeout — No.JS listens for animationend / transitionend to clean up classes and trigger re-renders after leave animations. If the event never fires (e.g. CSS is absent, element is detached) a setTimeout safety net ensures the pipeline is never permanently stalled. When animate-duration is omitted the timeout is 0 ms — it fires on the next event-loop tick with no artificial wait. Passing an explicit animate-duration=\"300\" sets both animation-duration on the target element and the safety-net timeout to 300 ms."
      },
      "transition": {
        "title": "transition — CSS Transition Classes",
        "viewTransitionNote": "<strong>Note:</strong> Route transitions on <code>route-view</code> now use the <a href=\"/docs/routing#view-transitions\">View Transition API</a> by default. The class-based <code>transition</code> attribute described below still applies to <strong>regular elements</strong> (e.g. <code>if</code>, <code>show</code>).",
        "text1": "Follows a convention similar to Vue's transition system.",
        "text2": "No.JS adds/removes classes during the transition:",
        "col1": "Class",
        "col2": "When",
        "row1": "Start state of enter",
        "row2": "Active state of enter",
        "row3": "End state of enter",
        "row4": "Start state of leave",
        "row5": "Active state of leave",
        "row6": "End state of leave"
      },
      "loopAnimations": {
        "title": "Loop Animations",
        "text": "All loop directives (foreach, each, for) support enter/leave animations and stagger."
      },
      "builtIn": {
        "title": "Built-in Animation Names",
        "text": "No.JS ships with these CSS animations:"
      },
      "a11y": {
        "title": "Accessibility — Reduced Motion",
        "text": "No.JS automatically respects the prefers-reduced-motion media query. When the user prefers reduced motion, all CSS animations are disabled via a built-in CSS rule that sets animation-duration and transition-duration to 0.01ms.",
        "callout": "This applies globally — no per-element configuration needed. Users who prefer reduced motion see instant state changes with no animation."
      },
      "liveDemo": {
        "title": "Live Demo — Toggle Animation",
        "label": "Result",
        "toggleButton": "Toggle",
        "demoText": "Hello, Animated World! ✨"
      }
    },
    "dnd": {
      "hero": {
        "badge": "Guides",
        "title": "Drag and Drop",
        "subtitle": "Declarative drag, drop, sortable lists and multi-select — zero JavaScript"
      },
      "drag": {
        "title": "drag — Make an Element Draggable",
        "preview": "Preview",
        "demoText": "Drag fruits into the basket:",
        "fruitsLabel": "Fruits",
        "basketLabel": "Basket",
        "col1": "Attribute",
        "col2": "Type",
        "col3": "Default",
        "col4": "Description",
        "dragDesc": "The value being dragged",
        "dragTypeDesc": "Named type — only matching <code>drop-accept</code> zones respond",
        "dragEffectDesc": "Maps to <code>dataTransfer.effectAllowed</code>",
        "dragHandleDesc": "Restricts grab area to a child element",
        "dragImageDesc": "Custom drag ghost element",
        "dragImageOffsetDesc": "Pixel offset for custom drag image",
        "dragDisabledDesc": "When truthy, disables dragging",
        "dragClassDesc": "Class added while dragging",
        "dragGhostClassDesc": "Class added to the drag image element",
        "dragGroupDesc": "Group name for multi-select"
      },
      "drop": {
        "title": "drop — Define a Drop Zone",
        "preview": "Preview",
        "demoText": "Drag items between zones:",
        "zoneALabel": "Zone A",
        "zoneBLabel": "Zone B",
        "col1": "Attribute",
        "col2": "Type",
        "col3": "Default",
        "col4": "Description",
        "dropDesc": "Expression executed on drop",
        "dropAcceptDesc": "Accepted <code>drag-type</code>(s). Use <code>\"*\"</code> for any",
        "dropEffectDesc": "Maps to <code>dataTransfer.dropEffect</code>",
        "dropClassDesc": "Class added when valid item hovers",
        "dropRejectClassDesc": "Class added when item is rejected (wrong type or max exceeded)",
        "dropDisabledDesc": "When truthy, disables dropping",
        "dropMaxDesc": "Max items the zone accepts",
        "dropSortDesc": "Enables sortable reorder by position",
        "dropPlaceholderDesc": "Shows placeholder at insertion point",
        "dropPlaceholderClassDesc": "Class for the placeholder",
        "dropSettleClassDesc": "Custom CSS class for the settle animation",
        "dropEmptyClassDesc": "Custom CSS class for empty state on drop zone"
      },
      "dragList": {
        "title": "drag-list — Sortable List",
        "text": "A higher-level directive that combines <code>drag</code> and <code>drop</code> into a sortable list bound to a state array.",
        "preview": "Preview",
        "todoLabel": "To Do",
        "doneLabel": "Done",
        "col1": "Attribute",
        "col2": "Type",
        "col3": "Default",
        "col4": "Description",
        "dragListDesc": "Path to array in state",
        "templateDesc": "Template for each item",
        "dragListKeyDesc": "Unique key per item for stable identity",
        "dragListItemDesc": "Loop variable name in template",
        "dropSortDesc": "Layout direction",
        "dropAcceptDesc": "Types accepted (defaults to same list)",
        "dragListCopyDesc": "Copy items instead of moving",
        "dragListRemoveDesc": "Remove items when dragged out",
        "dragDisabledDesc": "Disables dragging from this list",
        "dropDisabledDesc": "Disables dropping into this list",
        "dropMaxDesc": "Max items allowed",
        "dropSettleClassDesc": "Custom CSS class for the settle animation",
        "dropEmptyClassDesc": "Custom CSS class for empty state on drag-list",
        "dropPlaceholderDesc": "Shows a placeholder where the item will be dropped"
      },
      "dragListEvents": {
        "title": "Drag-List Events",
        "col1": "Event",
        "col3": "Description",
        "reorderDesc": "Item reordered within same list",
        "receiveDesc": "Item received from another list",
        "removeDesc": "Item removed (dragged out)",
        "preview": "Preview",
        "inboxLabel": "Inbox",
        "archiveLabel": "Archive",
        "eventLogLabel": "Event log:"
      },
      "dragMultiple": {
        "title": "drag-multiple — Multi-Select",
        "text": "Enable click-to-select on child elements, then drag all selected items at once.",
        "preview": "Preview",
        "demoText": "Click items to select, <kbd>Ctrl</kbd>+click for multi, then drag to the drop zone:",
        "availableLabel": "Available",
        "collectedLabel": "Collected",
        "col1": "Attribute",
        "col2": "Default",
        "col3": "Description",
        "dragMultipleDesc": "Enables click-to-select",
        "dragMultipleClassDesc": "Class added to selected items",
        "dragGroupDesc": "Group name — all selected items move together",
        "selectionTitle": "Selection behavior:",
        "selCol1": "Action",
        "selCol2": "Result",
        "selClick": "Click",
        "selClickResult": "Selects single item (replaces previous)",
        "selCtrlClick": "Ctrl/Cmd + Click",
        "selCtrlClickResult": "Adds to selection",
        "selEscape": "Escape",
        "selEscapeResult": "Clears all selections",
        "selDrag": "Drag a selected item",
        "selDragResult": "<code>$drag</code> becomes an array of all selected items"
      },
      "implicitVars": {
        "title": "Implicit Variables",
        "text": "These variables are available inside <code>drop</code> expressions and <code>on:drop</code> handlers:",
        "col1": "Variable",
        "col2": "Type",
        "col3": "Description",
        "dragDesc": "The dragged value. Array if multi-select",
        "dragTypeDesc": "The <code>drag-type</code> of the item",
        "dragEffectDesc": "The <code>drag-effect</code>",
        "dropIndexDesc": "Insertion index within the drop zone",
        "sourceDesc": "<code>{ list, index, el }</code> — source info",
        "targetDesc": "<code>{ list, index, el }</code> — target info",
        "preview": "Preview",
        "demoText": "Each item has a different <code>drag-type</code>:",
        "dropHere": "Drop here to inspect"
      },
      "cssClasses": {
        "title": "CSS Classes",
        "text": "Automatically injected by No.JS:",
        "col1": "Class",
        "col2": "When applied",
        "draggingDesc": "On the source element while dragging",
        "dragOverDesc": "On the drop zone while a valid item hovers",
        "dropRejectDesc": "On the drop zone when the item is rejected (wrong type or max exceeded)",
        "dropPlaceholderDesc": "On the insertion placeholder",
        "selectedDesc": "On multi-selected items",
        "dropSettleDesc": "Brief settle animation on drop",
        "dragListEmptyDesc": "On a <code>drag-list</code> when it has no items",
        "preview": "Preview",
        "demoText": "Drag an item and watch the classes apply:",
        "dropZoneHint": "Drop zone (max 2) — watch <code>.nojs-drag-over</code> / <code>.nojs-drop-reject</code>"
      },
      "a11y": {
        "title": "Accessibility",
        "text": "No.JS automatically adds ARIA attributes and keyboard support:",
        "col1": "Feature",
        "col2": "Details",
        "draggableDesc": "Set on drag sources",
        "ariaGrabbedDesc": "Reflects drag state (<code>true</code>/<code>false</code>)",
        "ariaDropeffectDesc": "Set on drop zones",
        "roleListboxDesc": "On <code>drag-list</code> containers",
        "roleOptionDesc": "On <code>drag-list</code> items",
        "tabindexDesc": "For keyboard access",
        "keyboardTitle": "Keyboard shortcuts:",
        "keyCol1": "Key",
        "keyCol2": "Action",
        "spaceDesc": "Grab the focused item",
        "escapeDesc": "Cancel the drag",
        "arrowDesc": "Navigate between items while dragging",
        "enterDesc": "Drop at current position",
        "preview": "Preview",
        "demoText": "Use <kbd>Tab</kbd> to focus, <kbd>Space</kbd> to grab, <kbd>↑↓</kbd> to move, <kbd>Enter</kbd> to drop:"
      }
    },
    "filters": {
      "hero": {
        "badge": "Guides",
        "title": "Filters & Pipes",
        "subtitle": "Transform values in bind expressions using the | pipe syntax"
      },
      "text": {
        "title": "Text Filters"
      },
      "number": {
        "title": "Number Filters"
      },
      "array": {
        "title": "Array Filters"
      },
      "date": {
        "title": "Date Filters"
      },
      "utility": {
        "title": "Utility Filters"
      },
      "object": {
        "title": "Object Filters"
      },
      "referenceTable": {
        "title": "Complete Filter Reference",
        "text": "All 32 built-in filters at a glance:",
        "col1": "Filter",
        "col2": "Category",
        "col3": "Description",
        "uppercase": "Converts text to UPPERCASE",
        "lowercase": "Converts text to lowercase",
        "capitalize": "Capitalizes first letter of each word",
        "truncate": "Truncates text to N characters with ellipsis",
        "slugify": "Converts to URL-friendly slug",
        "trim": "Removes leading/trailing whitespace",
        "encodeUri": "Encodes a URI component",
        "currency": "Formats number as currency",
        "number": "Formats number with locale grouping",
        "percent": "Formats number as percentage",
        "ordinal": "Adds ordinal suffix (1st, 2nd, 3rd)",
        "filesize": "Formats bytes to human-readable size",
        "reverse": "Reverses array or string",
        "unique": "Removes duplicate values",
        "pluck": "Extracts a property from each item",
        "where": "Filters array by property value",
        "sortBy": "Sorts array by property",
        "first": "Returns the first element of an array",
        "last": "Returns the last element of an array",
        "date": "Formats Date object",
        "datetime": "Formats Date with time",
        "relative": "Time elapsed since date (e.g. '5 minutes ago')",
        "fromNow": "Time until future date (e.g. 'in 3 hours')",
        "json": "Serializes value as JSON string",
        "default": "Returns fallback if value is null/undefined",
        "keys": "Returns object keys as array",
        "values": "Returns object values as array",
        "count": "Returns the length of an array",
        "join": "Joins array elements with a separator",
        "stripHtml": "Removes HTML tags from a string",
        "nl2br": "Converts newlines to <br> tags",
        "debug": "Outputs value to console and returns it"
      },
      "chaining": {
        "title": "Chaining Filters"
      },
      "custom": {
        "title": "Custom Filters"
      },
      "liveDemo": {
        "title": "Live Demo — Filters",
        "label": "Result",
        "uppercaseLabel": "Uppercase:",
        "slugifyLabel": "Slugify:"
      }
    },
    "i18n": {
      "hero": {
        "badge": "Guides",
        "title": "Internationalization (i18n)",
        "subtitle": "Multi-language support with translations, pluralization, and locale-aware formatting"
      },
      "setup": {
        "title": "Setup"
      },
      "externalFiles": {
        "title": "External Locale Files",
        "text": "Instead of inlining all translations in JavaScript, you can load them from external JSON files. This is ideal for large apps with many locales or when translations are managed by a separate tool.",
        "flatSubtitle": "Flat Mode (one file per locale)",
        "flatText": "Structure:",
        "nsSubtitle": "Namespace Mode (split by feature)",
        "nsText": "Split translations by feature for code-splitting and on-demand loading:",
        "nsRouteSubtitle": "Namespace per Route",
        "nsRouteText": "Use i18n-ns on a route template to load a namespace on-demand when the route is navigated to:",
        "nsElementSubtitle": "Namespace on Any Element",
        "nsElementText": "Use i18n-ns on any element to load a namespace before its children are processed:",
        "cachingSubtitle": "Caching",
        "cachingText": "Fetched JSON files are cached in memory by default. Set cache: false during development:"
      },
      "usage": {
        "title": "Usage"
      },
      "formatting": {
        "title": "Number & Date Formatting"
      },
      "fallback": {
        "title": "Fallback Behavior",
        "text": "When a translation key is missing in the current locale, No.JS falls back to the fallbackLocale (default: same as defaultLocale). If the key is missing in both, the raw key path is displayed.",
        "callout": "Set fallbackLocale explicitly to ensure users always see meaningful text, even for partially translated locales."
      },
      "detection": {
        "title": "Browser Locale Detection",
        "text": "Enable automatic locale detection from the browser's navigator.language setting with detectBrowser: true. The detected locale is matched against your available locales.",
        "callout": "When combined with persist: true, the detected locale is saved so subsequent visits use the same language without re-detecting."
      },
      "liveDemo": {
        "title": "Live Demo — Locale Switcher",
        "label": "Result",
        "localeLabel": "Locale:"
      }
    },
    "actionsRefs": {
      "hero": {
        "badge": "API Reference",
        "title": "Actions & Refs",
        "subtitle": "Trigger API calls, emit custom events, and reference DOM elements"
      },
      "call": {
        "title": "call — Trigger API Requests from Any Element"
      },
      "trigger": {
        "title": "trigger — Emit Custom Events"
      },
      "triggerAttrs": {
        "title": "Trigger Attributes",
        "text": "Customize trigger behavior with additional attributes:",
        "col1": "Attribute",
        "col2": "Description",
        "triggerData": "Data payload attached to the CustomEvent's detail property"
      },
      "ref": {
        "title": "ref — Named References",
        "text": "Access DOM elements without querySelector:"
      },
      "refsMap": {
        "title": "$refs — Ref Map",
        "text": "All elements with ref are accessible via $refs in the current scope:"
      }
    },
    "customDirectives": {
      "hero": {
        "badge": "API Reference",
        "title": "Custom Directives",
        "subtitle": "Extend No.JS with your own attribute-driven behaviors"
      },
      "directive": {
        "title": "NoJS.directive()"
      },
      "usage": {
        "title": "Usage"
      },
      "priority": {
        "title": "Priority Levels",
        "text": "Priority determines when your directive runs relative to built-in directives. Lower numbers run first.",
        "col1": "Range",
        "col2": "When",
        "range0": "Before everything (state initialization)",
        "range1": "After state, with data fetching",
        "range10": "With structural directives (if, each)",
        "range20": "With rendering directives (bind, on:*)",
        "range30": "After everything (validation, side effects)"
      },
      "disposal": {
        "title": "Disposal & Cleanup",
        "text": "Custom directives must clean up after themselves. Use the _onDispose callback to remove event listeners, clear timers, and unsubscribe watchers.",
        "callout": "Directives registered after init (via plugins) are not frozen — but they cannot override built-in directives."
      },
      "webComponents": {
        "title": "Web Components Compatibility",
        "text": "No.JS directives work on custom elements:"
      },
      "componentPatterns": {
        "title": "Component-like Patterns with Templates"
      }
    },
    "errorHandling": {
      "hero": {
        "badge": "API Reference",
        "title": "Error Handling",
        "subtitle": "Per-element error templates, retry logic, and global error handlers"
      },
      "perElement": {
        "title": "Per-Element Error Handling"
      },
      "retry": {
        "title": "Retry Behavior",
        "text": "Failed HTTP requests (5xx errors and network failures) are automatically retried. Configure the number of retries per element with the retry attribute, and the delay between retries with retry-delay.",
        "callout": "Retries only apply to server errors (5xx) and network failures. Client errors (4xx) are not retried."
      },
      "globalHandler": {
        "title": "Global Error Handler"
      },
      "errorBoundary": {
        "title": "error-boundary — Catch Errors in Subtree"
      },
      "boundaryEvents": {
        "title": "Error Boundary Events",
        "text": "When an error boundary catches an error, it dispatches a nojs:error CustomEvent on the boundary element. Listen with on:error to log errors or show notifications.",
        "callout": "The $event.detail object contains: message (string), source (element), and error (original Error object)."
      },
      "expressionErrors": {
        "title": "Expression Errors",
        "text": "When an expression fails to evaluate (e.g. accessing a property of undefined), No.JS catches the error, logs a warning via _warn(), and returns undefined. One broken expression never crashes the entire page."
      }
    },
    "configuration": {
      "hero": {
        "badge": "API Reference",
        "title": "Configuration & Security",
        "subtitle": "Global settings, request interceptors, and security best practices"
      },
      "globalSettings": {
        "title": "Global Settings"
      },
      "configStores": {
        "title": "Pre-initializing Stores",
        "text": "Use the stores key in NoJS.config() to pre-create named global stores with initial data. Each entry becomes a reactive store accessible via $store.name anywhere in your markup.",
        "callout": "Stores created via config() will not overwrite stores that already exist — the first definition wins."
      },
      "configOptions": {
        "title": "Config Option Details",
        "sanitizeTitle": "sanitize",
        "sanitizeType": "Type: boolean | Default: true",
        "sanitizeText": "Controls whether HTML content rendered via bind-html is sanitized through a DOMParser-based structural sanitizer. When enabled, all potentially dangerous tags and attributes (e.g., <script>, onerror) are stripped before insertion into the DOM.",
        "devtoolsTitle": "devtools",
        "devtoolsType": "Type: boolean | Default: false",
        "devtoolsText": "Enables the No.JS devtools panel, accessible via window.__NOJS_DEVTOOLS__. When active, it exposes reactive state, registered directives, active routes, and component trees for inspection in the browser console.",
        "templatesCacheTitle": "templates.cache",
        "templatesCacheType": "Type: boolean | Default: true",
        "templatesCacheText1": "Controls whether the HTML content of remotely-fetched .tpl files is stored in an in-memory Map after the first request. On repeated navigations to the same route, the cached HTML is used directly and no HTTP request is made. The cache lives for the duration of the page session (no TTL — template assets are static).",
        "templatesCacheText2": "Set to false during local development if you want changes to .tpl files to be reflected without a hard page reload.",
        "loadPathTitle": "i18n.loadPath",
        "loadPathType": "Type: string | null | Default: null",
        "loadPathText": "URL template for loading locale JSON files via fetch. Use {locale} and optionally {ns} as placeholders. When null, translations must be provided inline via NoJS.i18n({ locales }).",
        "nsTitle": "i18n.ns",
        "nsType": "Type: string[] | Default: []",
        "nsText": "Array of namespace identifiers to preload at init(). Each namespace corresponds to a separate JSON file per locale. Additional namespaces can be loaded on-demand via the i18n-ns directive or route attribute.",
        "cacheTitle": "i18n.cache",
        "cacheType": "Type: boolean | Default: true",
        "cacheText": "Controls whether fetched locale JSON files are stored in an in-memory Map after the first request. Set to false during development for hot-reload of translation files."
      },
      "apiProperties": {
        "title": "API Properties",
        "baseApiUrlTitle": "NoJS.baseApiUrl",
        "baseApiUrlText": "Getter/setter for the base API URL used by all fetch directives and NoJS.http calls. Can be read or reassigned at runtime.",
        "versionTitle": "NoJS.version",
        "versionText": "Read-only property that returns the current No.JS framework version string."
      },
      "interceptors": {
        "title": "Request Interceptors"
      },
      "security": {
        "title": "Security",
        "xssTitle": "XSS Protection",
        "xssList1": "bind always sets textContent, never innerHTML — safe by default.",
        "xssList2": "bind-html sanitizes content using a built-in DOMParser-based structural sanitizer (strips <script> tags, blocks on* event handlers, removes javascript: URIs).",
        "xssList3": "Template expressions are evaluated by a custom sandboxed parser — no eval() or Function() is used, and dangerous properties like __proto__ and constructor are blocked.",
        "csrfTitle": "CSRF Protection",
        "cspSecTitle": "Content Security Policy",
        "cspSecText1": "No.JS uses a custom expression parser that is fully CSP-compliant — no eval() or Function() constructor is used. No unsafe-eval directive is required in your Content Security Policy."
      }
    },
    "plugins": {
      "hero": {
        "badge": "API Reference",
        "title": "Plugins",
        "subtitle": "Extend No.JS with reusable packages — analytics, auth, feature flags, and more"
      },
      "use": {
        "title": "NoJS.use()",
        "text": "Register a plugin before or after NoJS.init(). If the app is already initialized, the plugin's init hook runs immediately.",
        "objectFormTitle": "Object Form",
        "objectFormText": "The standard way to define a plugin. Provide a name, an install function, and optional lifecycle hooks.",
        "functionTitle": "Function Shorthand",
        "functionText": "For simple plugins, pass a named function. The function name becomes the plugin name.",
        "functionCallout": "Anonymous functions and arrow functions are rejected — the plugin must have a name.",
        "optionsTitle": "Options",
        "optionsText": "The second argument to NoJS.use() is passed to the plugin's install function.",
        "optionsTrustedNote": "The trusted option is special — see Trusted Interceptors below."
      },
      "interface": {
        "title": "Plugin Interface",
        "thProperty": "Property",
        "thType": "Type",
        "thRequired": "Required",
        "thDescription": "Description",
        "yes": "Yes",
        "no": "No",
        "nameDesc": "Unique identifier. Duplicate names are rejected.",
        "versionDesc": "Semver string for debugging.",
        "capabilitiesDesc": "Declared capabilities (logged in debug mode).",
        "installDesc": "Called synchronously by NoJS.use().",
        "initDesc": "Called after NoJS.init() completes (DOM is ready).",
        "disposeDesc": "Called during NoJS.dispose() for cleanup.",
        "lifecycleTitle": "Lifecycle",
        "lifecycleInstall": "install runs immediately and synchronously. Use it to register interceptors, globals, directives, event listeners, and stores.",
        "lifecycleInit": "init runs after the DOM is processed and the router is active. Use it for work that depends on rendered elements or route state.",
        "lifecycleDispose": "dispose runs during app teardown. Use it to close WebSocket connections, clear intervals, flush pending analytics, etc.",
        "duplicateTitle": "Duplicate Detection",
        "duplicateText": "A plugin name can only be registered once. If NoJS.use() is called again with the same name but a different object, a warning is logged and the call is ignored.",
        "freezingTitle": "Directive Registry Freezing",
        "freezingText": "Core directives (state, bind, get, on:*, etc.) are frozen after the framework loads. Plugins can register new directives but cannot override built-in ones."
      },
      "globals": {
        "title": "NoJS.global()",
        "text": "Inject a reactive variable accessible as $name in any template expression.",
        "namingTitle": "Naming Conventions",
        "namingPrefix": "Global names are accessed with a $ prefix in templates: NoJS.global('foo', ...) becomes $foo.",
        "namingNamespace": "Use your plugin name as a namespace to avoid collisions: $analytics, $auth, $featureFlags.",
        "reservedTitle": "Reserved Names",
        "reservedText": "The following names cannot be used with NoJS.global():",
        "reservedProto": "Prototype pollution vectors (__proto__, constructor, prototype) are also blocked.",
        "reactivityTitle": "Reactivity",
        "reactivityText": "Object values passed to NoJS.global() are automatically wrapped in a reactive context. Mutations trigger DOM updates just like store or state changes.",
        "ownershipTitle": "Ownership Tracking",
        "ownershipText": "When a plugin registers a global during its install phase, it becomes the owner. If a different plugin overwrites that global, a warning is logged."
      },
      "sentinels": {
        "title": "Interceptor Sentinels",
        "text": "Request interceptors can return special objects keyed by sentinel Symbols to control the fetch pipeline.",
        "cancelTitle": "NoJS.CANCEL — Abort the Request",
        "cancelText": "Return an object with [NoJS.CANCEL]: true to prevent the request from being sent. The fetch throws an AbortError.",
        "respondTitle": "NoJS.RESPOND — Serve a Cached Response",
        "respondText": "Return an object with [NoJS.RESPOND]: data to short-circuit the request and return data directly as the response. No HTTP request is made.",
        "replaceTitle": "NoJS.REPLACE — Replace Response Data",
        "replaceText": "Return an object with [NoJS.REPLACE]: data from a response interceptor to replace the parsed response body with custom data.",
        "summaryTitle": "Summary",
        "thSentinel": "Sentinel",
        "thUsedIn": "Used In",
        "thEffect": "Effect",
        "cancelUsedIn": "Request interceptor",
        "respondUsedIn": "Request interceptor",
        "replaceUsedIn": "Response interceptor",
        "cancelEffect": "Aborts the request (throws AbortError)",
        "respondEffect": "Returns data directly, skips HTTP call",
        "replaceEffect": "Replaces the parsed response body"
      },
      "trusted": {
        "title": "Trusted Interceptors",
        "text": "By default, interceptors receive redacted copies of requests and responses — sensitive headers and URL parameters are stripped or replaced with [REDACTED].",
        "fullAccess": "Auth plugins that need access to the real headers can be installed with { trusted: true }.",
        "callout": "A console warning is logged when a plugin is installed with trusted access. Only grant trusted to plugins you control or have audited.",
        "redactedTitle": "Redacted Headers",
        "requestLabel": "Request headers stripped before passing to untrusted interceptors:",
        "responseLabel": "Response headers stripped from the response proxy:"
      },
      "dispose": {
        "title": "NoJS.dispose()",
        "text": "Tears down the entire application: disposes plugins, clears globals, and removes interceptors.",
        "orderTitle": "Disposal Order",
        "orderStep1": "Plugins are disposed in reverse installation order (last installed, first disposed).",
        "orderStep2": "Each plugin's dispose function is given a 3-second timeout. If it exceeds the timeout, an error is logged and disposal continues with the next plugin.",
        "orderStep3": "After all plugins are disposed, globals and interceptors are cleared.",
        "asyncTitle": "Async Dispose",
        "asyncText": "Plugin dispose functions can be async. The framework awaits each one (subject to the 3-second timeout).",
        "callout": "Plugins cannot be installed during disposal. Calls to NoJS.use() while dispose() is running are ignored with a warning."
      },
      "security": {
        "title": "Security Guidelines",
        "text": "When authoring plugins, follow these practices to keep your application secure.",
        "namespaceTitle": "Namespace Everything",
        "namespaceText": "Prefix globals, stores, and event names with your plugin name to avoid collisions.",
        "evalTitle": "Never Use eval or Function",
        "evalText": "Values passed to NoJS.global() are checked for dangerous references. eval and Function are blocked.",
        "cleanupTitle": "Clean Up in dispose",
        "cleanupText": "Always provide a dispose hook that clears intervals, closes connections, and removes event listeners.",
        "overwriteTitle": "Avoid Overwriting Others' Globals",
        "overwriteText": "If your plugin detects that a global is already owned by another plugin, consider using a different name rather than silently overwriting.",
        "validateTitle": "Validate Options",
        "validateText": "Check required options in install and warn early."
      },
      "example": {
        "title": "Complete Example",
        "text": "A full analytics plugin demonstrating the plugin lifecycle, globals, interceptors, and disposal."
      }
    },
    "headManagement": {
      "hero": {
        "badge": "Guides",
        "title": "Head Management",
        "subtitle": "Manage document title, meta description, canonical URL, and JSON-LD from any element"
      },
      "intro": {
        "text": "No.JS lets you control <head> metadata declaratively from anywhere in your markup. Use hidden elements with page-title, page-description, page-canonical, and page-jsonld attributes — they are evaluated reactively and update the document head automatically.",
        "routingTip": "For route-level head management, you can also use <code>page-title</code> and <code>page-description</code> directly on <code>&lt;template route=\"...\"&gt;</code> tags. See <a href=\"/docs/routing#route-head-attributes\">Route Head Attributes</a>."
      },
      "placement": {
        "title": "Placement",
        "text": "Place head-management elements inside any state scope. They must have the hidden attribute — they are never rendered visually.",
        "reactive": "All values are reactive — when the underlying state changes, the document head is updated immediately."
      },
      "pageTitle": {
        "title": "page-title",
        "text": "Sets document.title to the evaluated expression.",
        "expression": "The value is a standard No.JS expression evaluated against the element's context."
      },
      "pageDescription": {
        "title": "page-description",
        "text": "Sets the content attribute of the <meta name=\"description\"> tag. If the tag doesn't exist, it is created.",
        "duplicate": "Only one page-description should be active at a time. If multiple are present, the last one processed wins."
      },
      "pageCanonical": {
        "title": "page-canonical",
        "text": "Sets the href attribute of the <link rel=\"canonical\"> tag. If the tag doesn't exist, it is created.",
        "existing": "If a canonical link already exists in your HTML, the attribute value is updated in place."
      },
      "pageJsonld": {
        "title": "page-jsonld",
        "text": "Injects a <script type=\"application/ld+json\"> block into the document head. The inner text of the element is used as the JSON-LD payload.",
        "scriptNote": "The content is inserted as-is — make sure it is valid JSON. Use <code>{variable}</code> interpolation to inject dynamic values.",
        "marker": "The generated script tag is marked with <code>data-nojs-jsonld</code> so it can be identified and cleaned up on unmount.",
        "fullExampleTitle": "Full Product Page Example"
      },
      "notes": {
        "title": "Notes & Edge Cases",
        "competingTitle": "Multiple Directives Competing",
        "competingText": "If multiple elements set the same head attribute (e.g. two page-title elements), the last one processed wins. When an element is unmounted, its head contribution is reverted to the previous value.",
        "cleanupTitle": "Cleanup on Unmount",
        "cleanupText": "When an element with a head directive is removed from the DOM (e.g. inside an if block or a route change), its head contribution is automatically cleaned up.",
        "captureTitle": "JSON-LD Template Capture",
        "captureText": "The page-jsonld element's innerHTML is captured at processing time. If you need reactive JSON-LD, place the element inside a state scope and use {expression} interpolation."
      }
    }
  }
}
