{
  "generatedFrom": "paywall.contract.json",
  "component": "Paywall",
  "slug": "paywall",
  "group": "Surfaces",
  "tier": "leaf-lit",
  "summary": "Wrap a pro-gated affordance; the shared upsell renders itself — the crown upgrade mark, title, one-sentence body, the fixed unlock line, and Upgrade + See all plans CTAs are baked in.",
  "install": {
    "package": "@ahaslides-product/design",
    "registry": "https://npm.pkg.github.com",
    "scope": "@ahaslides-product",
    "auth": "Published to GitHub Packages — needs a GitHub token with read:packages. Configure the @ahaslides-product scope in .npmrc before installing.",
    "npmrc": "@ahaslides-product:registry=https://npm.pkg.github.com\n//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}   # a GitHub token with read:packages",
    "command": "npm i @ahaslides-product/design",
    "tokenLayer": "import '@ahaslides-product/design/tokens.css';",
    "import": "import '@ahaslides-product/design/aha-paywall';",
    "element": "aha-paywall",
    "registers": "aha-paywall"
  },
  "feeds": {
    "doc": "https://ahaslides-product.github.io/ahaslides-design/paywall/index.html",
    "md": "https://ahaslides-product.github.io/ahaslides-design/paywall/paywall.md",
    "agentJson": "https://ahaslides-product.github.io/ahaslides-design/paywall.agent.json",
    "llms": "https://ahaslides-product.github.io/ahaslides-design/paywall.llms.txt",
    "index": "https://ahaslides-product.github.io/ahaslides-design/llms.txt"
  },
  "recommendedSnippet": "html",
  "vibeCode": "Emit the HTML snippet by default — <aha-paywall> is a standard custom element that renders on open — React/Vue are thin adapters over the same element. Paste into an .html file and it renders with no build step.",
  "frameworks": {
    "react": {
      "via": "web-component",
      "ref": "aha-paywall"
    },
    "vue": {
      "via": "web-component",
      "ref": "aha-paywall"
    },
    "html": {
      "via": "web-component",
      "ref": "aha-paywall",
      "runnable": "paste-and-run — no build step, renders on open"
    }
  },
  "props": [
    {
      "name": "feature-key",
      "type": "string (snake_case)",
      "default": "—",
      "desc": "Stable token (e.g. custom_survey_url) — drives analytics + DOM hooks. Required."
    },
    {
      "name": "feature-label",
      "type": "string",
      "default": "Pro feature",
      "desc": "Short noun-phrase title next to the crown (used as the popover title unless title is set)"
    },
    {
      "name": "title",
      "type": "string",
      "default": "—",
      "desc": "Popover title override; falls back to feature-label. May wrap to two lines"
    },
    {
      "name": "body",
      "type": "string",
      "default": "—",
      "desc": "Exactly one sentence describing the benefit of unlocking; ends with a period"
    },
    {
      "name": "description",
      "type": "string",
      "default": "—",
      "desc": "Popover description override; falls back to body"
    },
    {
      "name": "required-plan",
      "type": "essential | pro",
      "default": "pro",
      "desc": "Drives the unlock line and the analytics plan prop"
    },
    {
      "name": "placement",
      "type": "top | bottom",
      "default": "top",
      "desc": "Popover side relative to the anchor"
    },
    {
      "name": "trigger",
      "type": "click | hover",
      "default": "click",
      "desc": "How the popover opens; click-away + Escape close a click popover"
    },
    {
      "name": "cta-label",
      "type": "string",
      "default": "Upgrade",
      "desc": "The positive CTA verb; override only with a clear reason"
    },
    {
      "name": "plans-label",
      "type": "string",
      "default": "See all plans",
      "desc": "The secondary CTA label — routes to the full pricing page"
    },
    {
      "name": "upgrade-url",
      "type": "string (URL)",
      "default": "ahaslides.com/pricing",
      "desc": "Where Upgrade navigates (new tab, noopener)"
    },
    {
      "name": "plans-url",
      "type": "string (URL)",
      "default": "ahaslides.com/pricing",
      "desc": "Where See all plans navigates (new tab, noopener)"
    },
    {
      "name": "slot (default)",
      "type": "element",
      "default": "upgrade mark",
      "desc": "The gated affordance = the anchor; omit it to use the crown upgrade mark as the anchor"
    },
    {
      "name": "paywall-shown / paywall-upgrade / paywall-plans",
      "type": "CustomEvent<{feature,plan}>",
      "default": "—",
      "desc": "Analytics contract; paywall-upgrade + paywall-plans are cancelable (preventDefault to run your own flow)"
    }
  ],
  "tokens": [
    "color-primary",
    "color-primary-hover",
    "color-primary-active",
    "bg-dark-raised",
    "text-inverse",
    "text-secondary",
    "radius-lg",
    "radius-pill",
    "button-positive-bg",
    "button-default-border",
    "focus-ring-soft"
  ],
  "spec": [
    {
      "label": "Upgrade mark",
      "value": "16×16 purple circle (--aha-color-primary) + white crown — a focusable control with Default/Hover/Active states; the custom mark, not a Phosphor crown"
    },
    {
      "label": "Popover",
      "value": "dark indigo surface #242442 (--aha-bg-dark-raised), 300px wide, 16px padding, radius 12; animates opacity + lift on open via the motion tokens"
    },
    {
      "label": "Anatomy",
      "value": "header (crown + title) · body (one sentence + \"Unlock with the <Plan> plan.\") · footer (Upgrade + See all plans)"
    },
    {
      "label": "Buttons",
      "value": "TWO shared <aha-button>s — Upgrade (variant=positive, green --aha-button-positive-bg) + See all plans (variant=secondary, re-themed for the dark surface); never hand-rolled"
    },
    {
      "label": "Analytics",
      "value": "paywall-shown on open · paywall-upgrade before Upgrade nav · paywall-plans before See-all-plans nav — all { feature, plan }"
    },
    {
      "label": "Behaviour",
      "value": "Upgrade opens ahaslides.com/pricing (new tab, noopener); See all plans opens plans-url; preventDefault either event to run your own flow. Esc / click-away close"
    }
  ],
  "opinion": {
    "whenToUse": [
      {
        "what": "aha-paywall",
        "when": "any feature locked behind a paid plan — editor affordance, share option, results feature, dashboard action"
      },
      {
        "what": "crown-badge anchor",
        "when": "the feature has no natural clickable anchor (e.g. a field label) — omit the slot and the crown is the anchor"
      },
      {
        "what": "required-plan pro",
        "when": "the default gate; use essential only for essential-tier features"
      },
      {
        "what": "never a bespoke upsell",
        "when": "a hand-rolled upgrade modal/tooltip drifts from the presenter app — always this element"
      }
    ],
    "note": "Feature label is a short noun phrase; body is exactly one sentence about the benefit (not \"This is a Pro feature\"); the unlock line is fixed — do not reword. Popover/placement mechanics defer to aha-design-overlays; a single plan-gated setting row lives in the Settings surface."
  },
  "surfaces": [
    "editor",
    "dashboard",
    "settings",
    "audience"
  ],
  "snippets": {
    "html": "<!-- Paste into an .html file and open — no build step. Wrap a gated affordance; the upsell renders itself. -->\n<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/@ahaslides-product/design/lib/tokens.css\">\n<script type=\"module\">\n  import 'https://cdn.jsdelivr.net/npm/@ahaslides-product/design/lib/aha-paywall.js';   // registers <aha-paywall>\n</script>\n\n<!-- Pro gate — the affordance is the anchor. The popover ships the crown mark, title, description,\n     the fixed unlock line, and BOTH CTAs (Upgrade positive + See all plans) — all baked in. -->\n<aha-paywall\n  feature-key=\"streak_bonus\"\n  feature-label=\"Streak answer bonus point\"\n  body=\"Let the participants play quiz and be ranked as teams instead of being ranked individually.\"\n  required-plan=\"pro\">\n  <button>Streak bonus</button>   <!-- the gated affordance (omit for a crown upgrade-mark anchor) -->\n</aha-paywall>\n\n<!-- No natural anchor → the crown upgrade mark IS the anchor (a focusable control); hover-triggered -->\n<aha-paywall\n  feature-key=\"export_results\"\n  feature-label=\"Export results\"\n  body=\"Download every response as a CSV or PDF.\"\n  required-plan=\"essential\"\n  trigger=\"hover\"></aha-paywall>\n\n<script>\n  // analytics contract — the app wires these to its tracker (fires for every paywall)\n  document.querySelectorAll('aha-paywall').forEach((el) => {\n    el.addEventListener('paywall-shown', (e) => console.log('PAYWALL_SHOWN', e.detail));\n    el.addEventListener('paywall-upgrade', (e) => console.log('PAYWALL_UPGRADE_CLICKED', e.detail));\n    el.addEventListener('paywall-plans', (e) => console.log('PAYWALL_PLANS_CLICKED', e.detail));  // See all plans\n  });\n</script>\n",
    "react": "import '@ahaslides-product/design/aha-paywall';   // registers <aha-paywall>\nimport '@ahaslides-product/design/tokens.css';\nimport { useRef, useEffect } from 'react';\n\n// Wrap the gated affordance; the element renders the compliant upsell (crown badge, one-sentence\n// body, fixed unlock line, single Upgrade CTA are baked in). analytics comes back as events.\nexport function CustomUrlSetting() {\n  const ref = useRef(null);\n  useEffect(() => {\n    const el = ref.current, track = (name) => (e) => window.analytics?.track(name, e.detail);\n    el.addEventListener('paywall-shown', track('PAYWALL_SHOWN'));\n    el.addEventListener('paywall-upgrade', track('PAYWALL_UPGRADE_CLICKED'));\n    el.addEventListener('paywall-plans', track('PAYWALL_PLANS_CLICKED'));   // See all plans\n  }, []);\n  return (\n    <>\n      {/* Pro gate — the affordance is the anchor. Popover ships crown mark, title, description,\n          the fixed unlock line, and BOTH CTAs (Upgrade positive + See all plans). */}\n      <aha-paywall\n        ref={ref}\n        feature-key=\"streak_bonus\"\n        feature-label=\"Streak answer bonus point\"\n        body=\"Let the participants play quiz and be ranked as teams instead of being ranked individually.\"\n        required-plan=\"pro\">\n        <button>Streak bonus</button>\n      </aha-paywall>\n\n      {/* Essential gate, no natural anchor → the crown badge is the anchor */}\n      <aha-paywall\n        feature-key=\"export_results\"\n        feature-label=\"Export results\"\n        body=\"Download every response as a CSV or PDF.\"\n        required-plan=\"essential\" />\n    </>\n  );\n}\n",
    "vue": "<script setup>\nimport '@ahaslides-product/design/aha-paywall';   // registers <aha-paywall>\nimport '@ahaslides-product/design/tokens.css';\n\n// The element renders the compliant upsell; analytics comes back as events.\nconst onShown = (e) => window.analytics?.track('PAYWALL_SHOWN', e.detail);\nconst onUpgrade = (e) => window.analytics?.track('PAYWALL_UPGRADE_CLICKED', e.detail);\nconst onPlans = (e) => window.analytics?.track('PAYWALL_PLANS_CLICKED', e.detail);   // See all plans\n</script>\n\n<template>\n  <!-- Pro gate — the affordance is the anchor. Popover ships crown mark, title, description,\n       the fixed unlock line, and BOTH CTAs (Upgrade positive + See all plans). -->\n  <aha-paywall\n    feature-key=\"streak_bonus\"\n    feature-label=\"Streak answer bonus point\"\n    body=\"Let the participants play quiz and be ranked as teams instead of being ranked individually.\"\n    required-plan=\"pro\"\n    @paywall-shown=\"onShown\"\n    @paywall-upgrade=\"onUpgrade\"\n    @paywall-plans=\"onPlans\">\n    <button>Streak bonus</button>\n  </aha-paywall>\n\n  <!-- No natural anchor → the crown upgrade mark is the anchor (a focusable control) -->\n  <aha-paywall\n    feature-key=\"export_results\"\n    feature-label=\"Export results\"\n    body=\"Download every response as a CSV or PDF.\"\n    required-plan=\"essential\"\n    @paywall-shown=\"onShown\"\n    @paywall-upgrade=\"onUpgrade\"\n    @paywall-plans=\"onPlans\" />\n</template>\n"
  }
}
