[{"data":1,"prerenderedAt":59},["ShallowReactive",2],{"doc-pricing":3},{"html":4,"headings":5},"\u003Cp>Show your plans on your site, in your app and in your onboarding, and change\nthem from the panel without a release. Write the plans, features and words in\nHelpBites; link each plan to Stripe if you like, so its prices and checkout come\nfrom Stripe and a change there shows everywhere. Then add one line to your site\nor paste one file into your app.\u003C\u002Fp>\n\u003Ch2 id=\"a-table\">A table\u003Ca class=\"heading-anchor\" href=\"#a-table\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>A project has up to 50 tables, each with up to 6 plans and 40 feature rows.\nEach table is drawn with one of five templates, and your site or app can pick a\ndifferent one when it shows it:\u003C\u002Fp>\n\u003Cdiv class=\"table-wrap\">\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Template\u003C\u002Fth>\n\u003Cth>Suits\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>tiers\u003C\u002Fcode> (Cards)\u003C\u002Ftd>\n\u003Ctd>Plans side by side, the highlighted one standing out. The classic pricing page.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>rows\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>One compact line a plan: sidebars, settings screens, narrow spaces.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>paywall\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Benefits, selectable price rows and one button, like an app's upgrade screen.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>matrix\u003C\u002Fcode> (Comparison)\u003C\u002Ftd>\n\u003Ctd>Every feature by plan, in a table.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>spotlight\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>One wide card a plan: a single plan or a lifetime deal.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\u003C\u002Fdiv>\n\u003Cp>A \u003Cstrong>plan\u003C\u002Fstrong> has a name, a description, a badge (&quot;Most popular&quot;), whether it is\nhighlighted, up to four prices (weekly, monthly, yearly and one-time), a label\nfor a plan with no price (&quot;Custom&quot;), a line under the price (&quot;per seat&quot;), a free\ntrial and a button.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>Features\u003C\u002Fstrong> are rows shared by every plan: tick what each plan includes, or\ngive it a value (&quot;10&quot;, &quot;Unlimited&quot;) that shows beside the feature and in the\ncomparison table's cell. Headings group rows. With \u003Cstrong>Each plan builds on the one\nbefore\u003C\u002Fstrong>, a card says &quot;Everything in Starter, plus:&quot; and lists only what it adds.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>Look:\u003C\u002Fstrong> accent, background and text colours (empty follows the project's\naccent and the visitor's light or dark), corner radius, font (system, rounded,\nserif, mono), spacing, flat, outlined or shadowed cards, and how the highlighted\nplan stands out.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>Drafts and versions.\u003C\u002Fstrong> You edit a working copy. Nothing changes on your site\nor in your app until you publish; each publish is a new version, and\n\u003Cstrong>Unpublish\u003C\u002Fstrong> takes the table down everywhere.\u003C\u002Fp>\n\u003Ch2 id=\"stripe\">Stripe\u003Ca class=\"heading-anchor\" href=\"#stripe\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>Connect Stripe under \u003Cstrong>Revenue\u003C\u002Fstrong> in the panel, with a restricted key. Pricing\ntables use these permissions of that key:\u003C\u002Fp>\n\u003Cdiv class=\"table-wrap\">\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Permission\u003C\u002Fth>\n\u003Cth>For\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>Products: Read, Prices: Read\u003C\u002Ftd>\n\u003Ctd>The product and price pickers, and the prices shown.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Payment Links: Read\u003C\u002Ftd>\n\u003Ctd>Buttons that open a Stripe Payment Link.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Checkout Sessions: Write\u003C\u002Ftd>\n\u003Ctd>Buttons that open Stripe Checkout.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Checkout Sessions: Read\u003C\u002Ftd>\n\u003Ctd>Counting purchases and revenue for the table's numbers and A\u002FB tests.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\u003C\u002Fdiv>\n\u003Cp>Pick a \u003Cstrong>Stripe product\u003C\u002Fstrong> for a plan and each slot takes the product's current\nprice for that period, or pick a price yourself. A Stripe price wins over a\nwritten one, so Stripe stays the truth for what you charge; the plan's name,\ndescription and features stay yours (empty name and description show Stripe's,\nand \u003Cstrong>Add Stripe's features\u003C\u002Fstrong> copies the product's marketing features into\nrows). Plans without Stripe show the prices you write.\u003C\u002Fp>\n\u003Cp>HelpBites reads Stripe through the key and keeps the answer for five minutes\n(\u003Cstrong>Read Stripe again\u003C\u002Fstrong> asks now). Publishing keeps a copy of the Stripe prices it\nused, so a published table still shows the right prices if Stripe is briefly\nunreachable. \u003Cstrong>Check before publishing\u003C\u002Fstrong> lists what would not work: a plan with\nno price, a plan set to Checkout without a Stripe price, a Payment Link button\nwith no active link for its price, a product or price archived in Stripe. When a\ncheckout fails (a key without &quot;Checkout Sessions: Write&quot;, say), the editor shows\nthe last error.\u003C\u002Fp>\n\u003Cp>Metered and tiered prices are left out: a table shows fixed prices.\u003C\u002Fp>\n\u003Ch2 id=\"billing-periods-and-currencies\">Billing periods and currencies\u003Ca class=\"heading-anchor\" href=\"#billing-periods-and-currencies\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>Each plan has four slots: \u003Ccode>week\u003C\u002Fcode>, \u003Ccode>month\u003C\u002Fcode>, \u003Ccode>year\u003C\u002Fcode> and \u003Ccode>once\u003C\u002Fcode>. When the plans have\nprices for two or more of the periods that bill again, the table shows a toggle\n(with the best yearly saving as a badge); a one-time price shows on its own.\n\u003Cstrong>Toggle starts on\u003C\u002Fstrong> picks the slot it opens on. A yearly price shows as the\nyearly total, or as a month billed yearly; in an iPhone app, show the total, as\nthe App Store wants the amount billed to stand out.\u003C\u002Fp>\n\u003Cp>The table has a \u003Cstrong>currency\u003C\u002Fstrong>: written prices are in it, and a Stripe price that\nsells in several currencies shows in it. Visitors can see another one:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>A Stripe price with \u003Cstrong>currency options\u003C\u002Fstrong> can be shown in each of its\ncurrencies.\u003C\u002Fli>\n\u003Cli>A written price can have \u003Cstrong>prices in other currencies\u003C\u002Fstrong> (under the plan's\nprices in the panel, or \u003Ccode>amounts\u003C\u002Fcode> in the API: \u003Ccode>{ &quot;eur&quot;: 9, &quot;gbp&quot;: 8 }\u003C\u002Fcode>).\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Ask for a currency with \u003Ccode>currency=eur\u003C\u002Fcode> (the web script's \u003Ccode>data-currency\u003C\u002Fcode>, or\n\u003Ccode>auto\u003C\u002Fcode> for the visitor's region). The table shows in it when every price has it,\nand lists in \u003Ccode>currencies\u003C\u002Fcode> every currency all its prices share, for a switcher.\nOtherwise it shows in its own currency.\u003C\u002Fp>\n\u003Ch2 id=\"buttons-and-checkout\">Buttons and checkout\u003Ca class=\"heading-anchor\" href=\"#buttons-and-checkout\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>Each plan's button does one of four things:\u003C\u002Fp>\n\u003Cdiv class=\"table-wrap\">\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Button\u003C\u002Fth>\n\u003Cth>What happens\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>Stripe Checkout\u003C\u002Ftd>\n\u003Ctd>HelpBites opens a new Stripe Checkout Session for the price, with the plan's trial and \u003Cstrong>Accept promotion codes\u003C\u002Fstrong>. The key needs &quot;Checkout Sessions: Write&quot;; without it, a Payment Link for the same price is used when there is one.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Stripe Payment Link\u003C\u002Ftd>\n\u003Ctd>The Payment Link that sells the price.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>A link of yours\u003C\u002Ftd>\n\u003Ctd>Your sign-up page, a contact form, anything. \u003Ccode>{plan}\u003C\u002Fcode> and \u003Ccode>{interval}\u003C\u002Fcode> are filled in with the plan's key and \u003Ccode>week\u003C\u002Fcode>, \u003Ccode>month\u003C\u002Fcode>, \u003Ccode>year\u003C\u002Fcode> or \u003Ccode>once\u003C\u002Fcode>.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>No button\u003C\u002Ftd>\n\u003Ctd>Just the plan.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\u003C\u002Fdiv>\n\u003Cp>A Checkout button links to \u003Ccode>GET \u002Fv1\u002Fpricing\u002F{pk}\u002F{key}\u002Fcheckout?plan=pro&amp;interval=year\u003C\u002Fcode>,\nwhich answers \u003Ccode>303\u003C\u002Fcode> to Stripe, so a plain link, an email button or an app's\n\u003Ccode>openURL\u003C\u002Fcode> all work. Add \u003Ccode>prefilled_email\u003C\u002Fcode>, \u003Ccode>client_reference_id\u003C\u002Fcode> (your id for the\nbuyer) and \u003Ccode>return_url\u003C\u002Fcode>. After paying, Stripe sends the buyer to the table's\n\u003Cstrong>After paying\u003C\u002Fstrong> URL, else \u003Ccode>return_url\u003C\u002Fcode> (when its site is one of the project's\nallowed origins, or any while it lists none), else the project's site, else the\nhosted page, with \u003Ccode>?checkout=success&amp;session_id=…\u003C\u002Fcode>; going back adds\n\u003Ccode>?checkout=canceled\u003C\u002Fcode>. \u003Ccode>POST\u003C\u002Fcode> the same URL with JSON for \u003Ccode>{ url }\u003C\u002Fcode> instead.\u003C\u002Fp>\n\u003Cp>The session's metadata carries \u003Ccode>helpbites_table\u003C\u002Fcode>, \u003Ccode>helpbites_plan\u003C\u002Fcode>,\n\u003Ccode>helpbites_interval\u003C\u002Fcode> and, during a test, \u003Ccode>helpbites_experiment\u003C\u002Fcode> and\n\u003Ccode>helpbites_run\u003C\u002Fcode>. \u003Ccode>client_reference_id\u003C\u002Fcode> is not verified (anyone can send any): give\nthe account in a Stripe webhook of your own, matched by the email or the\nmetadata, with your own checks.\u003C\u002Fp>\n\u003Ch2 id=\"plug-and-play-web\">Plug and play: web\u003Ca class=\"heading-anchor\" href=\"#plug-and-play-web\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>One script tag draws the table where you put it:\u003C\u002Fp>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-html\">\u003Ccode>\u003Cspan class=\"shj-syn-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">script\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">src\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"https:\u002F\u002FYOUR-SUPPORT-HOST\u002Fpricing.js\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">data-project\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"pk_…\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">data-table\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"pricing\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">defer\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">script\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&gt;\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>It is MIT-licensed, has no dependencies, and draws from JSON in your page (in a\nshadow root, so your CSS and its CSS never collide). Without \u003Ccode>data-table\u003C\u002Fcode> it\ndraws the project's first published table.\u003C\u002Fp>\n\u003Ch3 id=\"options\">Options\u003Ca class=\"heading-anchor\" href=\"#options\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh3>\n\u003Cp>Set them as \u003Ccode>data-*\u003C\u002Fcode> attributes or in \u003Ccode>init\u003C\u002Fcode>:\u003C\u002Fp>\n\u003Cdiv class=\"table-wrap\">\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Option\u003C\u002Fth>\n\u003Cth>Meaning\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>table\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The table's key.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>target\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A selector to draw into, instead of where the script tag is.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>template\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>tiers\u003C\u002Fcode>, \u003Ccode>rows\u003C\u002Fcode>, \u003Ccode>paywall\u003C\u002Fcode>, \u003Ccode>matrix\u003C\u002Fcode> or \u003Ccode>spotlight\u003C\u002Fcode>: overrides the table's.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>theme\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>light\u003C\u002Fcode>, \u003Ccode>dark\u003C\u002Fcode> or \u003Ccode>system\u003C\u002Fcode>. Defaults to a \u003Ccode>dark\u003C\u002Fcode> or \u003Ccode>light\u003C\u002Fcode> class on your \u003Ccode>&lt;html&gt;\u003C\u002Fcode>, then the colour behind the table.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>accent\u003C\u002Fcode>, \u003Ccode>radius\u003C\u002Fcode>, \u003Ccode>font\u003C\u002Fcode>, \u003Ccode>density\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Override the table's look.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>interval\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>week\u003C\u002Fcode>, \u003Ccode>month\u003C\u002Fcode> or \u003Ccode>year\u003C\u002Fcode>: where the toggle starts.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>currency\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A three-letter code, or \u003Ccode>auto\u003C\u002Fcode> for the visitor's region. A switcher offers the others when there are any.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>locale\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A language tag. Defaults to your \u003Ccode>&lt;html lang&gt;\u003C\u002Fcode>, then the browser's. See \u003Ca href=\"\u002Fdocs\u002Flanguages\">Languages\u003C\u002Fa>.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>show\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The parts to show: \u003Ccode>title,subtitle,toggle,currency,features,footnote,logo\u003C\u002Fcode> by default, \u003Ccode>none\u003C\u002Fcode> for just the plans.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>current\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The key of the visitor's plan: its button says &quot;Current plan&quot;.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>email\u003C\u002Fcode>, \u003Ccode>ref\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Filled into checkout: the buyer's email and your id for them.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>return\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Where checkout comes back to. Default: this page.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>host\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Where HelpBites is, when you serve \u003Ccode>pricing.js\u003C\u002Fcode> from your own site.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\u003C\u002Fdiv>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-html\">\u003Ccode>\u003Cspan class=\"shj-syn-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">script\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&gt;\u003C\u002Fspan>\n  window\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-syn-class\">HelpBitesPricing\u003C\u002Fspan> \u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan> window\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-syn-class\">HelpBitesPricing\u003C\u002Fspan> \u003Cspan class=\"shj-syn-oper\">||\u003C\u002Fspan> []\n  \u003Cspan class=\"shj-syn-class\">HelpBitesPricing\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-syn-func\">push\u003C\u002Fspan>([\u003Cspan class=\"shj-syn-str\">'init'\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">,\u003C\u002Fspan> {\n    email\u003Cspan class=\"shj-syn-oper\">:\u003C\u002Fspan> currentUser\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>email\u003Cspan class=\"shj-syn-oper\">,\u003C\u002Fspan> ref\u003Cspan class=\"shj-syn-oper\">:\u003C\u002Fspan> currentUser\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>id\u003Cspan class=\"shj-syn-oper\">,\u003C\u002Fspan> current\u003Cspan class=\"shj-syn-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">'starter'\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">,\u003C\u002Fspan>\n    labels\u003Cspan class=\"shj-syn-oper\">:\u003C\u002Fspan> { yearly\u003Cspan class=\"shj-syn-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">'Annual'\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">,\u003C\u002Fspan> choose\u003Cspan class=\"shj-syn-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">'Choose'\u003C\u002Fspan> }\u003Cspan class=\"shj-syn-oper\">,\u003C\u002Fspan>\n    \u003Cspan class=\"shj-syn-cmnt\">\u002F\u002F Return false to handle it yourself: your own checkout, a sign-up first.\n\u003C\u002Fspan>    onSelect\u003Cspan class=\"shj-syn-oper\">:\u003C\u002Fspan> (plan\u003Cspan class=\"shj-syn-oper\">,\u003C\u002Fspan> interval\u003Cspan class=\"shj-syn-oper\">,\u003C\u002Fspan> price) \u003Cspan class=\"shj-syn-kwd\">=&gt;\u003C\u002Fspan> {}\n  }])\n  \u003Cspan class=\"shj-syn-class\">HelpBitesPricing\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-syn-func\">push\u003C\u002Fspan>([\u003Cspan class=\"shj-syn-str\">'render'\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">,\u003C\u002Fspan> { table\u003Cspan class=\"shj-syn-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">'teams'\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">,\u003C\u002Fspan> target\u003Cspan class=\"shj-syn-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">'#team-pricing'\u003C\u002Fspan> }])   \u003Cspan class=\"shj-syn-cmnt\">\u002F\u002F a second table\n\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">script\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-syn-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">script\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">src\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"https:\u002F\u002FYOUR-SUPPORT-HOST\u002Fpricing.js\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">data-project\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"pk_…\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">data-table\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-syn-str\">\"pricing\"\u003C\u002Fspan> \u003Cspan class=\"shj-syn-class\">defer\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">script\u003C\u002Fspan>\u003Cspan class=\"shj-syn-oper\">&gt;\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>Every outcome is also a \u003Ccode>helpbites:pricing\u003C\u002Fcode> event on \u003Ccode>window\u003C\u002Fcode>, with\n\u003Ccode>detail.type\u003C\u002Fcode> and the table's key.\u003C\u002Fp>\n\u003Ch3 id=\"restyle-it\">Restyle it\u003Ca class=\"heading-anchor\" href=\"#restyle-it\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh3>\n\u003Cp>The table's look is the starting point; your CSS can change any of it. Set these\nvariables on \u003Ccode>.help-bites-pricing\u003C\u002Fcode>:\u003C\u002Fp>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-css\">\u003Ccode>\u003Cspan class=\"shj-syn-var\">.help-bites-pricing\u003C\u002Fspan> {\n  --\u003Cspan class=\"shj-syn-var\">hb-pricing-accent\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">#0ea5e9\u003C\u002Fspan>;\n  --\u003Cspan class=\"shj-syn-var\">hb-pricing-on-accent\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">#fff\u003C\u002Fspan>;   \u003Cspan class=\"shj-syn-cmnt\">\u002F* text on the accent *\u002F\u003C\u002Fspan>\n  --\u003Cspan class=\"shj-syn-var\">hb-pricing-bg\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">transparent\u003C\u002Fspan>;\n  --\u003Cspan class=\"shj-syn-var\">hb-pricing-card\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">#fff\u003C\u002Fspan>;\n  --\u003Cspan class=\"shj-syn-var\">hb-pricing-text\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">#0f172a\u003C\u002Fspan>;\n  --\u003Cspan class=\"shj-syn-var\">hb-pricing-muted\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">#64748b\u003C\u002Fspan>;\n  --\u003Cspan class=\"shj-syn-var\">hb-pricing-border\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">#e2e8f0\u003C\u002Fspan>;\n  --\u003Cspan class=\"shj-syn-var\">hb-pricing-radius\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">20\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">px\u003C\u002Fspan>\u003Cspan class=\"shj-syn-num\">\u003C\u002Fspan>;\n  --\u003Cspan class=\"shj-syn-var\">hb-pricing-font\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"Inter\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-num\">sans-serif\u003C\u002Fspan>;\n  --\u003Cspan class=\"shj-syn-var\">hb-pricing-gap\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">20\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">px\u003C\u002Fspan>\u003Cspan class=\"shj-syn-num\">\u003C\u002Fspan>;\n  --\u003Cspan class=\"shj-syn-var\">hb-pricing-max-width\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">1100\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">px\u003C\u002Fspan>\u003Cspan class=\"shj-syn-num\">\u003C\u002Fspan>;\n}\n\u003Cspan class=\"shj-syn-var\">.help-bites-pricing\u003C\u002Fspan>\u003Cspan class=\"shj-syn-var\">::part\u003C\u002Fspan>(\u003Cspan class=\"shj-syn-kwd\">plan-highlighted\u003C\u002Fspan>) { \u003Cspan class=\"shj-syn-var\">transform\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-func\">scale\u003C\u002Fspan>(\u003Cspan class=\"shj-syn-num\">1.03\u003C\u002Fspan>); }\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>Parts you can style: \u003Ccode>root\u003C\u002Fcode>, \u003Ccode>head\u003C\u002Fcode>, \u003Ccode>title\u003C\u002Fcode>, \u003Ccode>subtitle\u003C\u002Fcode>, \u003Ccode>toggle\u003C\u002Fcode>,\n\u003Ccode>toggle-option\u003C\u002Fcode>, \u003Ccode>savings\u003C\u002Fcode>, \u003Ccode>plans\u003C\u002Fcode>, \u003Ccode>plan\u003C\u002Fcode>, \u003Ccode>plan-highlighted\u003C\u002Fcode>, \u003Ccode>badge\u003C\u002Fcode>, \u003Ccode>name\u003C\u002Fcode>,\n\u003Ccode>description\u003C\u002Fcode>, \u003Ccode>price\u003C\u002Fcode>, \u003Ccode>amount\u003C\u002Fcode>, \u003Ccode>period\u003C\u002Fcode>, \u003Ccode>compare\u003C\u002Fcode>, \u003Ccode>note\u003C\u002Fcode>, \u003Ccode>trial\u003C\u002Fcode>, \u003Ccode>button\u003C\u002Fcode>,\n\u003Ccode>includes\u003C\u002Fcode>, \u003Ccode>features\u003C\u002Fcode>, \u003Ccode>feature\u003C\u002Fcode>, \u003Ccode>value\u003C\u002Fcode>, \u003Ccode>hint\u003C\u002Fcode>, \u003Ccode>option\u003C\u002Fcode>, \u003Ccode>table\u003C\u002Fcode>,\n\u003Ccode>footnote\u003C\u002Fcode> and \u003Ccode>links\u003C\u002Fcode>. The layout is HelpBites'.\u003C\u002Fp>\n\u003Ch2 id=\"a-hosted-page\">A hosted page\u003Ca class=\"heading-anchor\" href=\"#a-hosted-page\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>Every published table has a page of its own, to link to from an email or open in\nan app's web view, or to put in an iframe:\u003C\u002Fp>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-plain\">\u003Ccode>https:\u002F\u002FYOUR-SUPPORT-HOST\u002Fpricing\u002FPROJECT_PUBLIC_KEY\u002Fpricing\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>It shows in the visitor's language, and checkout comes back to it when the table\nnames nowhere else.\u003C\u002Fp>\n\u003Ch2 id=\"plug-and-play-swift\">Plug and play: Swift\u003Ca class=\"heading-anchor\" href=\"#plug-and-play-swift\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>Paste this file into your app. It needs no package: plain \u003Ccode>URLSession\u003C\u002Fcode> against\nthe public API.\u003C\u002Fp>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-plain\">\u003Ccode>\u002F\u002F HelpBitesPricing.swift. MIT. Shows a HelpBites pricing table. iOS 17, macOS 14.\nimport SwiftUI\n\nstruct HelpBitesPricingTable: Codable, Equatable {\n  struct Price: Codable, Equatable {\n    let interval, currency, appStoreProduct: String\n    let amount: Double\n    let monthly: Double?\n    let intervalCount, trialDays: Int\n    let href: String?\n  }\n  struct Line: Codable, Equatable, Identifiable { let id, text, hint, value: String; let included: Bool }\n  struct Action: Codable, Equatable { let type, label: String; let href: String? }\n  struct Plan: Codable, Equatable, Identifiable {\n    let id, key, name, description, badge, customPrice, priceNote, includes: String\n    let highlighted: Bool\n    let savings: Int\n    let prices: [String: Price]\n    let action: Action\n    let lines: [Line]\n  }\n  struct Content: Codable, Equatable { let title, subtitle, footnote, weeklyLabel, monthlyLabel, yearlyLabel, savingsLabel: String }\n  struct Style: Codable, Equatable { let accent, font: String; let radius: Double }\n  struct Options: Codable, Equatable { let defaultInterval, yearlyAs: String }\n  struct Experiment: Codable, Equatable { let table, run: String }\n  let key, locale, currency: String\n  let version: Int\n  let intervals, currencies: [String]\n  let experiment: Experiment?\n  let content: Content\n  let style: Style\n  let options: Options\n  let plans: [Plan]\n}\n\nenum HelpBitesPricing {\n  \u002F\u002F\u002F HelpBites (8 s), else the last copy that loaded. `currency` is a three-letter code: the table's own when not every price has it.\n  static func load(host: URL, projectKey: String, table key: String, locale: String = Locale.preferredLanguages.first ?? \u003Cspan class=\"shj-syn-str\">\"en\"\u003C\u002Fspan>,\n                   currency: String = Locale.current.currency?.identifier ?? \u003Cspan class=\"shj-syn-str\">\"\"\u003C\u002Fspan>) async -&gt; HelpBitesPricingTable? {\n    let saved = URL.cachesDirectory.appending(path: \u003Cspan class=\"shj-syn-str\">\"helpbites-pricing-\\(key)-\\(locale)-\\(currency).json\"\u003C\u002Fspan>)\n    var url = URLComponents(url: host.appending(path: \u003Cspan class=\"shj-syn-str\">\"v1\u002Fpricing\u002F\\(projectKey)\u002F\\(key)\"\u003C\u002Fspan>), resolvingAgainstBaseURL: false)!\n    url.queryItems = [URLQueryItem(name: \u003Cspan class=\"shj-syn-str\">\"locale\"\u003C\u002Fspan>, value: locale), URLQueryItem(name: \u003Cspan class=\"shj-syn-str\">\"currency\"\u003C\u002Fspan>, value: currency.lowercased()), URLQueryItem(name: \u003Cspan class=\"shj-syn-str\">\"visitor\"\u003C\u002Fspan>, value: visitor)]\n    let request = URLRequest(url: url.url!, cachePolicy: .reloadRevalidatingCacheData, timeoutInterval: 8)\n    if let answer = try? await URLSession.shared.data(for: request), let status = (answer.1 as? HTTPURLResponse)?.statusCode {\n      if status == 404 { try? FileManager.default.removeItem(at: saved); return nil }\n      if status == 200, let table = decode(answer.0) { try? answer.0.write(to: saved); return table }\n    }\n    return (try? Data(contentsOf: saved)).flatMap(decode)\n  }\n\n  \u002F\u002F\u002F An id kept for this install: the table's numbers count each person once, and an A\u002FB test shows them the same table.\n  static var visitor: String {\n    if let id = UserDefaults.standard.string(forKey: \u003Cspan class=\"shj-syn-str\">\"helpbites.visitor\"\u003C\u002Fspan>) { return id }\n    let id = \u003Cspan class=\"shj-syn-str\">\"v_\"\u003C\u002Fspan> + UUID().uuidString.replacingOccurrences(of: \u003Cspan class=\"shj-syn-str\">\"-\"\u003C\u002Fspan>, with: \u003Cspan class=\"shj-syn-str\">\"\"\u003C\u002Fspan>).prefix(16).lowercased()\n    UserDefaults.standard.set(id, forKey: \u003Cspan class=\"shj-syn-str\">\"helpbites.visitor\"\u003C\u002Fspan>)\n    return id\n  }\n\n  \u002F\u002F\u002F A view or a click for the table's numbers: sent and forgotten.\n  static func count(_ type: String, host: URL, projectKey: String, table: HelpBitesPricingTable, plan: String? = nil, interval: String? = nil) {\n    var request = URLRequest(url: host.appending(path: \u003Cspan class=\"shj-syn-str\">\"v1\u002Fpricing\u002F\\(projectKey)\u002F\\(table.key)\u002Fevents\"\u003C\u002Fspan>))\n    request.httpMethod = \u003Cspan class=\"shj-syn-str\">\"POST\"\u003C\u002Fspan>\n    request.setValue(\u003Cspan class=\"shj-syn-str\">\"application\u002Fjson\"\u003C\u002Fspan>, forHTTPHeaderField: \u003Cspan class=\"shj-syn-str\">\"Content-Type\"\u003C\u002Fspan>)\n    var body = [\u003Cspan class=\"shj-syn-str\">\"type\"\u003C\u002Fspan>: type, \u003Cspan class=\"shj-syn-str\">\"visitor\"\u003C\u002Fspan>: visitor]\n    if let plan { body[\u003Cspan class=\"shj-syn-str\">\"plan\"\u003C\u002Fspan>] = plan }\n    if let interval { body[\u003Cspan class=\"shj-syn-str\">\"interval\"\u003C\u002Fspan>] = interval }\n    if let experiment = table.experiment { body[\u003Cspan class=\"shj-syn-str\">\"experiment\"\u003C\u002Fspan>] = experiment.table; body[\u003Cspan class=\"shj-syn-str\">\"run\"\u003C\u002Fspan>] = experiment.run }\n    request.httpBody = try? JSONEncoder().encode(body)\n    Task.detached { _ = try? await URLSession.shared.data(for: request) }\n  }\n\n  private struct Envelope: Decodable { let table: HelpBitesPricingTable }\n  static func decode(_ data: Data) -&gt; HelpBitesPricingTable? { try? JSONDecoder().decode(Envelope.self, from: data).table }\n}\n\nstruct HelpBitesPricingView: View {\n  let host: URL, projectKey: String, table: String\n  var locale: String?\n  var currency: String?\n  \u002F\u002F\u002F The key of the user's plan: its button says \u003Cspan class=\"shj-syn-str\">\"Current plan\"\u003C\u002Fspan>.\n  var current: String?\n  var accent: Color?\n  var cornerRadius: CGFloat?\n  var design: Font.Design?\n  \u002F\u002F\u002F Return true when you handled it (StoreKit with `price.appStoreProduct`, a sign-up first); otherwise its link opens.\n  var onSelect: (HelpBitesPricingTable.Plan, HelpBitesPricingTable.Price?) -&gt; Bool = { _, _ in false }\n  @State private var loaded: HelpBitesPricingTable?\n  @State private var interval = \u003Cspan class=\"shj-syn-str\">\"\"\u003C\u002Fspan>\n  @Environment(\\.openURL) private var openURL\n\n  var body: some View {\n    Group {\n      if let loaded { content(loaded) } else { ProgressView().frame(maxWidth: .infinity, minHeight: 160) }\n    }\n    .task {\n      guard let table = await HelpBitesPricing.load(host: host, projectKey: projectKey, table: self.table, locale: locale ?? Locale.preferredLanguages.first ?? \u003Cspan class=\"shj-syn-str\">\"en\"\u003C\u002Fspan>,\n                                                    currency: currency ?? Locale.current.currency?.identifier ?? \u003Cspan class=\"shj-syn-str\">\"\"\u003C\u002Fspan>) else { return }\n      let recurring = table.intervals.filter { $0 != \u003Cspan class=\"shj-syn-str\">\"once\"\u003C\u002Fspan> }\n      interval = recurring.contains(table.options.defaultInterval) ? table.options.defaultInterval : recurring.first ?? table.intervals.first ?? \u003Cspan class=\"shj-syn-str\">\"\"\u003C\u002Fspan>\n      loaded = table\n      HelpBitesPricing.count(\u003Cspan class=\"shj-syn-str\">\"view\"\u003C\u002Fspan>, host: host, projectKey: projectKey, table: table)\n    }\n  }\n\n  private func content(_ t: HelpBitesPricingTable) -&gt; some View {\n    let tint = accent ?? hexColor(t.style.accent) ?? .accentColor\n    let radius = cornerRadius ?? t.style.radius\n    let recurring = t.intervals.filter { $0 != \u003Cspan class=\"shj-syn-str\">\"once\"\u003C\u002Fspan> }\n    return VStack(spacing: 16) {\n      if !t.content.title.isEmpty { Text(t.content.title).font(.title2.weight(.bold)).multilineTextAlignment(.center) }\n      if !t.content.subtitle.isEmpty { Text(t.content.subtitle).foregroundStyle(.secondary).multilineTextAlignment(.center) }\n      if recurring.count &gt; 1 {\n        Picker(\u003Cspan class=\"shj-syn-str\">\"Billing period\"\u003C\u002Fspan>, selection: $interval) {\n          ForEach(recurring, id: \\.self) { slot in Text(verbatim: periodLabel(slot, t)).tag(slot) }\n        }\n        .pickerStyle(.segmented)\n      }\n      ForEach(t.plans) { plan in card(plan, t, tint: tint, radius: radius) }\n      if !t.content.footnote.isEmpty { Text(t.content.footnote).font(.footnote).foregroundStyle(.secondary).multilineTextAlignment(.center) }\n    }\n    .fontDesign(design ?? Self.designs[t.style.font] ?? .default)\n  }\n\n  private func card(_ plan: HelpBitesPricingTable.Plan, _ t: HelpBitesPricingTable, tint: Color, radius: CGFloat) -&gt; some View {\n    let price = plan.prices[interval] ?? plan.prices[\u003Cspan class=\"shj-syn-str\">\"once\"\u003C\u002Fspan>] ?? plan.prices.values.first\n    return VStack(alignment: .leading, spacing: 12) {\n      HStack {\n        Text(plan.name).font(.headline)\n        Spacer(minLength: 8)\n        if !plan.badge.isEmpty {\n          Text(plan.badge).font(.caption.weight(.semibold)).padding(.horizontal, 8).padding(.vertical, 3)\n            .background(tint.opacity(0.15), in: Capsule()).foregroundStyle(tint)\n        }\n      }\n      if !plan.description.isEmpty { Text(plan.description).font(.subheadline).foregroundStyle(.secondary) }\n      priceLines(plan, price, t)\n      if !plan.includes.isEmpty { Text(plan.includes).font(.footnote.weight(.semibold)) }\n      ForEach(plan.lines) { line in\n        HStack(alignment: .firstTextBaseline, spacing: 8) {\n          Image(systemName: line.included ? \u003Cspan class=\"shj-syn-str\">\"checkmark\"\u003C\u002Fspan> : \u003Cspan class=\"shj-syn-str\">\"minus\"\u003C\u002Fspan>).font(.footnote.weight(.bold)).foregroundStyle(line.included ? tint : Color.secondary)\n          Text(line.text).foregroundStyle(line.included ? Color.primary : Color.secondary)\n          Spacer(minLength: 8)\n          if !line.value.isEmpty { Text(line.value).fontWeight(.semibold) }\n        }\n        .font(.subheadline)\n      }\n      if plan.action.type != \u003Cspan class=\"shj-syn-str\">\"none\"\u003C\u002Fspan> { button(plan, price, t, tint: tint, radius: radius) }\n    }\n    .padding(18)\n    .frame(maxWidth: .infinity, alignment: .leading)\n    .background(.background, in: .rect(cornerRadius: radius))\n    .overlay(RoundedRectangle(cornerRadius: radius).strokeBorder(plan.highlighted ? tint : Color.secondary.opacity(0.25), lineWidth: plan.highlighted ? 2 : 1))\n  }\n\n  @ViewBuilder private func priceLines(_ plan: HelpBitesPricingTable.Plan, _ price: HelpBitesPricingTable.Price?, _ t: HelpBitesPricingTable) -&gt; some View {\n    if let price {\n      let monthly = price.interval == \u003Cspan class=\"shj-syn-str\">\"year\"\u003C\u002Fspan> &#38;&#38; t.options.yearlyAs == \u003Cspan class=\"shj-syn-str\">\"monthly\"\u003C\u002Fspan> ? price.monthly : nil\n      HStack(alignment: .firstTextBaseline, spacing: 4) {\n        Text(price.amount == 0 ? String(localized: \u003Cspan class=\"shj-syn-str\">\"Free\"\u003C\u002Fspan>) : money(monthly ?? price.amount, price.currency)).font(.largeTitle.weight(.bold))\n        if price.amount &gt; 0 { Text(verbatim: period(price, monthly: monthly != nil)).foregroundStyle(.secondary) }\n      }\n      if price.interval == \u003Cspan class=\"shj-syn-str\">\"year\"\u003C\u002Fspan>, plan.savings &gt; 0 {\n        let words = t.content.savingsLabel.isEmpty ? String(localized: \u003Cspan class=\"shj-syn-str\">\"Save \\(plan.savings)%\"\u003C\u002Fspan>) : t.content.savingsLabel.replacingOccurrences(of: \u003Cspan class=\"shj-syn-str\">\"{percent}\"\u003C\u002Fspan>, with: \u003Cspan class=\"shj-syn-str\">\"\\(plan.savings)\"\u003C\u002Fspan>)\n        Text(verbatim: words).font(.caption.weight(.semibold)).foregroundStyle(.green)\n      }\n      if price.trialDays &gt; 0 { Text(\u003Cspan class=\"shj-syn-str\">\"\\(price.trialDays)-day free trial\"\u003C\u002Fspan>).font(.footnote.weight(.medium)) }\n    } else if !plan.customPrice.isEmpty {\n      Text(plan.customPrice).font(.largeTitle.weight(.bold))\n    }\n    if !plan.priceNote.isEmpty { Text(plan.priceNote).font(.footnote).foregroundStyle(.secondary) }\n  }\n\n  private func button(_ plan: HelpBitesPricingTable.Plan, _ price: HelpBitesPricingTable.Price?, _ t: HelpBitesPricingTable, tint: Color, radius: CGFloat) -&gt; some View {\n    let isCurrent = current == plan.key\n    let label = isCurrent ? String(localized: \u003Cspan class=\"shj-syn-str\">\"Current plan\"\u003C\u002Fspan>) : !plan.action.label.isEmpty ? plan.action.label\n      : (price?.trialDays ?? 0) &gt; 0 ? String(localized: \u003Cspan class=\"shj-syn-str\">\"Start free trial\"\u003C\u002Fspan>) : String(localized: \u003Cspan class=\"shj-syn-str\">\"Get started\"\u003C\u002Fspan>)\n    return Button {\n      HelpBitesPricing.count(\u003Cspan class=\"shj-syn-str\">\"click\"\u003C\u002Fspan>, host: host, projectKey: projectKey, table: t, plan: plan.key, interval: price?.interval)\n      if onSelect(plan, price) { return }\n      guard let link = price?.href ?? plan.action.href, var url = URLComponents(string: link) else { return }\n      var items = url.queryItems ?? []\n      \u002F\u002F The same visitor at checkout, so a purchase during an A\u002FB test counts for the table they saw.\n      if link.contains(\u003Cspan class=\"shj-syn-str\">\"\u002Fcheckout?\"\u003C\u002Fspan>) { items.append(URLQueryItem(name: \u003Cspan class=\"shj-syn-str\">\"visitor\"\u003C\u002Fspan>, value: HelpBitesPricing.visitor)) }\n      else if let test = t.experiment, plan.action.type == \u003Cspan class=\"shj-syn-str\">\"payment_link\"\u003C\u002Fspan>, !items.contains(where: { $0.name == \u003Cspan class=\"shj-syn-str\">\"client_reference_id\"\u003C\u002Fspan> }) {\n        items.append(URLQueryItem(name: \u003Cspan class=\"shj-syn-str\">\"client_reference_id\"\u003C\u002Fspan>, value: \u003Cspan class=\"shj-syn-str\">\"hbx__\\(test.table)__\\(t.key)__\\(test.run)\"\u003C\u002Fspan>))\n      }\n      url.queryItems = items.isEmpty ? nil : items\n      if let target = url.url { openURL(target) }\n    } label: {\n      Text(verbatim: label).fontWeight(.semibold).frame(maxWidth: .infinity, minHeight: 44)\n        .background(plan.highlighted ? tint : tint.opacity(0.12), in: .rect(cornerRadius: max(6, radius * 0.6)))\n        .foregroundStyle(plan.highlighted ? Color.white : tint)\n    }\n    .buttonStyle(.plain)\n    .disabled(isCurrent)\n  }\n\n  private func periodLabel(_ slot: String, _ t: HelpBitesPricingTable) -&gt; String {\n    let own = [\u003Cspan class=\"shj-syn-str\">\"week\"\u003C\u002Fspan>: t.content.weeklyLabel, \u003Cspan class=\"shj-syn-str\">\"month\"\u003C\u002Fspan>: t.content.monthlyLabel, \u003Cspan class=\"shj-syn-str\">\"year\"\u003C\u002Fspan>: t.content.yearlyLabel][slot] ?? \u003Cspan class=\"shj-syn-str\">\"\"\u003C\u002Fspan>\n    if !own.isEmpty { return own }\n    return slot == \u003Cspan class=\"shj-syn-str\">\"week\"\u003C\u002Fspan> ? String(localized: \u003Cspan class=\"shj-syn-str\">\"Weekly\"\u003C\u002Fspan>) : slot == \u003Cspan class=\"shj-syn-str\">\"month\"\u003C\u002Fspan> ? String(localized: \u003Cspan class=\"shj-syn-str\">\"Monthly\"\u003C\u002Fspan>) : String(localized: \u003Cspan class=\"shj-syn-str\">\"Yearly\"\u003C\u002Fspan>)\n  }\n\n  private func period(_ price: HelpBitesPricingTable.Price, monthly: Bool) -&gt; String {\n    let n = price.intervalCount\n    if monthly { return String(localized: \u003Cspan class=\"shj-syn-str\">\"\u002Fmonth, billed yearly\"\u003C\u002Fspan>) }\n    switch price.interval {\n    case \u003Cspan class=\"shj-syn-str\">\"week\"\u003C\u002Fspan>: return n &gt; 1 ? String(localized: \u003Cspan class=\"shj-syn-str\">\"every \\(n) weeks\"\u003C\u002Fspan>) : String(localized: \u003Cspan class=\"shj-syn-str\">\"\u002Fweek\"\u003C\u002Fspan>)\n    case \u003Cspan class=\"shj-syn-str\">\"month\"\u003C\u002Fspan>: return n &gt; 1 ? String(localized: \u003Cspan class=\"shj-syn-str\">\"every \\(n) months\"\u003C\u002Fspan>) : String(localized: \u003Cspan class=\"shj-syn-str\">\"\u002Fmonth\"\u003C\u002Fspan>)\n    case \u003Cspan class=\"shj-syn-str\">\"year\"\u003C\u002Fspan>: return n &gt; 1 ? String(localized: \u003Cspan class=\"shj-syn-str\">\"every \\(n) years\"\u003C\u002Fspan>) : String(localized: \u003Cspan class=\"shj-syn-str\">\"\u002Fyear\"\u003C\u002Fspan>)\n    default: return String(localized: \u003Cspan class=\"shj-syn-str\">\"once\"\u003C\u002Fspan>)\n    }\n  }\n\n  private func money(_ amount: Double, _ currency: String) -&gt; String {\n    amount.formatted(.currency(code: currency.uppercased()).precision(.fractionLength(amount.rounded() == amount ? 0 : 2)))\n  }\n\n  private static let designs: [String: Font.Design] = [\u003Cspan class=\"shj-syn-str\">\"rounded\"\u003C\u002Fspan>: .rounded, \u003Cspan class=\"shj-syn-str\">\"serif\"\u003C\u002Fspan>: .serif, \u003Cspan class=\"shj-syn-str\">\"mono\"\u003C\u002Fspan>: .monospaced]\n}\n\nprivate func hexColor(_ hex: String) -&gt; Color? {\n  guard hex.count == 7, hex.hasPrefix(\u003Cspan class=\"shj-syn-str\">\"#\"\u003C\u002Fspan>), let v = Int(hex.dropFirst(), radix: 16) else { return nil }\n  return Color(red: Double(v &gt;&gt; 16 &#38; 255) \u002F 255, green: Double(v &gt;&gt; 8 &#38; 255) \u002F 255, blue: Double(v &#38; 255) \u002F 255)\n}\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>Then, wherever you sell:\u003C\u002Fp>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-plain\">\u003Ccode>ScrollView {\n  HelpBitesPricingView(host: URL(string: \u003Cspan class=\"shj-syn-str\">\"https:\u002F\u002FYOUR-SUPPORT-HOST\"\u003C\u002Fspan>)!, projectKey: \u003Cspan class=\"shj-syn-str\">\"pk_…\"\u003C\u002Fspan>, table: \u003Cspan class=\"shj-syn-str\">\"pricing\"\u003C\u002Fspan>)\n    .padding()\n}\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>\u003Cstrong>In-App Purchase.\u003C\u002Fstrong> In an iPhone or iPad app, digital goods are usually sold\nwith In-App Purchase, and Apple's rules on linking to other ways to pay differ\nby country and change; check the App Review Guidelines before showing a Stripe\nbutton in your app. Give each price an \u003Cstrong>App Store product\u003C\u002Fstrong> in the panel and\nbuy it with StoreKit in \u003Ccode>onSelect\u003C\u002Fcode>:\u003C\u002Fp>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-plain\">\u003Ccode>HelpBitesPricingView(host: host, projectKey: \u003Cspan class=\"shj-syn-str\">\"pk_…\"\u003C\u002Fspan>, table: \u003Cspan class=\"shj-syn-str\">\"paywall\"\u003C\u002Fspan>, current: user.planKey) { plan, price in\n  guard let id = price?.appStoreProduct, !id.isEmpty else { return false }\n  Task { if let product = try? await Product.products(for: [id]).first { _ = try? await product.purchase() } }\n  return true\n}\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>\u003Cstrong>Customise it\u003C\u002Fstrong> with \u003Ccode>accent\u003C\u002Fcode>, \u003Ccode>cornerRadius\u003C\u002Fcode>, \u003Ccode>design\u003C\u002Fcode> (\u003Ccode>.rounded\u003C\u002Fcode>,\n\u003Ccode>.serif\u003C\u002Fcode>…), \u003Ccode>locale\u003C\u002Fcode> and \u003Ccode>currency\u003C\u002Fcode> (the user's region's by default), or keep\n\u003Ccode>HelpBitesPricing.load\u003C\u002Fcode> and draw the table your own way. It is your file, so\nchange anything. The words the snippet adds (&quot;Get started&quot;, &quot;\u002Fmonth&quot;, &quot;Save 20%&quot;)\nare your app's strings: add them to your \u003Ccode>Localizable.xcstrings\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>Offline.\u003C\u002Fstrong> It shows the last copy it loaded when HelpBites cannot be reached,\nand nothing on a first launch without a network.\u003C\u002Fp>\n\u003Ch2 id=\"in-onboarding\">In onboarding\u003Ca class=\"heading-anchor\" href=\"#in-onboarding\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>An onboarding step can show a pricing table under its text: pick it under the\nstep in the onboarding editor. It suits a paywall at the end of a tour; the\nstep's own button becomes the quiet way on, and choosing a plan finishes the\ntour. The flow's JSON then has the published table in the step's \u003Ccode>pricing\u003C\u002Fcode>, in\nthe flow's language, in the same shape as \u003Ccode>GET \u002Fv1\u002Fpricing\u002F…\u003C\u002Fcode>: decode it with\n\u003Ccode>HelpBitesPricingTable\u003C\u002Fcode> from the Swift snippet above to draw it after the\n\u003Ca href=\"\u002Fdocs\u002Fonboarding\">Onboarding\u003C\u002Fa> snippet's screens, or show \u003Ccode>HelpBitesPricingView\u003C\u002Fcode>\nfor the same table. A step whose table is a draft shows its text only.\u003C\u002Fp>\n\u003Ch2 id=\"numbers-and-ab-tests\">Numbers and A\u002FB tests\u003Ca class=\"heading-anchor\" href=\"#numbers-and-ab-tests\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>Each table counts views and unique visitors (pricing.js, the hosted page, the\nonboarding tour and the Swift snippet send them), clicks on each plan's button,\ncheckouts started through HelpBites, and purchases and revenue read from Stripe:\ncompleted Checkout Sessions that HelpBites started for the table, and those that\ncame through a Payment Link the table shows. Stripe is checked every ten\nminutes while a table is published, or now with \u003Cstrong>Check Stripe now\u003C\u002Fstrong>. The\nnumbers are under \u003Cstrong>Results\u003C\u002Fstrong> in the editor, by day and by plan.\u003C\u002Fp>\n\u003Cp>An \u003Cstrong>A\u002FB test\u003C\u002Fstrong> splits a table's visitors between it and up to three other\ntables, by weight:\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>Add a variant\u003C\u002Fstrong> copies the table into a new draft (\u003Ccode>pricing-b\u003C\u002Fcode>). Change\nwhat you want to test: prices, the template, the words, which plan is\nhighlighted. Publish it. Or test against a table you already have.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Start test.\u003C\u002Fstrong> Your site and app keep asking for \u003Ccode>pricing\u003C\u002Fcode>; each visitor\ngets one of the tables, the same one every visit (by the visitor id the web\nscript and the Swift snippet keep). The JSON's \u003Ccode>key\u003C\u002Fcode> says which one they got,\nand \u003Ccode>experiment\u003C\u002Fcode> names the test.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Results\u003C\u002Fstrong> shows each table's visitors, clicks, purchases, conversion rate,\nlift over the original and how sure a two-proportion test is of the\ndifference. It says &quot;Too early&quot; until each table has 100 visitors and there\nare 5 purchases, and marks a table \u003Cstrong>Ahead\u003C\u002Fstrong> once it is better with 95%\ncertainty.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Stop test\u003C\u002Fstrong> keeps the original, or \u003Cstrong>Stop and use\u003C\u002Fstrong> a variant: its plans,\nwords and look become the original's working copy, to publish under the key\nyour site and app already use.\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>Each start is a new run with numbers of its own; variants and weights stay\nfixed while it runs. Purchases count for the table the visitor saw when they\ncame through its button: Checkout links carry the test, and Payment Links get a\n\u003Ccode>client_reference_id\u003C\u002Fcode> of \u003Ccode>hbx__&lt;table&gt;__&lt;shown&gt;__&lt;run&gt;\u003C\u002Fcode> unless you set your own.\u003C\u002Fp>\n\u003Ch2 id=\"suggest-with-ai\">Suggest with AI\u003Ca class=\"heading-anchor\" href=\"#suggest-with-ai\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>\u003Cstrong>Suggest with AI\u003C\u002Fstrong> reads the project's connected GitHub repos (read-only, up\nto three), the table as it stands, your Stripe products and prices, the last 90\ndays of revenue by plan and the table's own numbers, and proposes plan names and\ndescriptions, a feature list with each plan's values, and prices. Choose what\nit works on (words, features, prices), which repos to read, and anything it\nshould know. Nothing changes until you \u003Cstrong>Apply\u003C\u002Fstrong> it to the editor, and then you\nsave or publish as usual. A plan linked to Stripe keeps its link: a different\nprice for it comes back as advice to create that price in Stripe. It uses the\nworkspace's AI provider (or the server's, metered in AI credits).\u003C\u002Fp>\n\u003Ch2 id=\"languages\">Languages\u003Ca class=\"heading-anchor\" href=\"#languages\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>Add languages to a table and translate its words with AI or by hand, as with\nonboarding (see \u003Ca href=\"\u002Fdocs\u002Flanguages\">Languages\u003C\u002Fa>). Every visible string can be\ntranslated: the title and small print, the toggle labels, each plan's name,\ndescription, badge, price label, note and button, each feature and tooltip, and\neach value in the comparison table. The table is requested in the visitor's\nlanguage and answers with that translation, or its own language when it has\nnone; \u003Ccode>locale\u003C\u002Fcode> says which. Words the table adds itself (&quot;Monthly&quot;, &quot;\u002Fyear&quot;,\n&quot;Get started&quot;) come in ten languages in pricing.js.\u003C\u002Fp>\n\u003Ch2 id=\"your-own-ui\">Your own UI\u003Ca class=\"heading-anchor\" href=\"#your-own-ui\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>The published table is one public request, open to any origin:\u003C\u002Fp>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-bash\">\u003Ccode>\u003Cspan class=\"shj-syn-func\">curl\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -s\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"https:\u002F\u002FYOUR-SUPPORT-HOST\u002Fv1\u002Fpricing\u002FPROJECT_PUBLIC_KEY\u002Fpricing?locale=es&#38;currency=eur&#38;visitor=v_8f2c1d4e\"\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-json\">\u003Ccode>{\n  \u003Cspan class=\"shj-syn-var\">\"table\"\u003C\u002Fspan>: {\n    \u003Cspan class=\"shj-syn-var\">\"key\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"pricing\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"name\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"Pricing\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"locale\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"es\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"locales\"\u003C\u002Fspan>: [\u003Cspan class=\"shj-syn-str\">\"en\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-str\">\"es\"\u003C\u002Fspan>], \u003Cspan class=\"shj-syn-var\">\"version\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">3\u003C\u002Fspan>,\n    \u003Cspan class=\"shj-syn-var\">\"template\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"tiers\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"intervals\"\u003C\u002Fspan>: [\u003Cspan class=\"shj-syn-str\">\"month\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-str\">\"year\"\u003C\u002Fspan>], \u003Cspan class=\"shj-syn-var\">\"currency\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"eur\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"currencies\"\u003C\u002Fspan>: [\u003Cspan class=\"shj-syn-str\">\"usd\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-str\">\"eur\"\u003C\u002Fspan>],\n    \u003Cspan class=\"shj-syn-var\">\"experiment\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">null\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"savings\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">16\u003C\u002Fspan>,\n    \u003Cspan class=\"shj-syn-var\">\"content\"\u003C\u002Fspan>: { \u003Cspan class=\"shj-syn-var\">\"title\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"Precios sencillos\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"subtitle\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"…\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"footnote\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"…\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"monthlyLabel\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"yearlyLabel\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"savingsLabel\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"\"\u003C\u002Fspan> },\n    \u003Cspan class=\"shj-syn-var\">\"style\"\u003C\u002Fspan>: { \u003Cspan class=\"shj-syn-var\">\"accent\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"#5e6ad2\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"radius\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">16\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"font\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"system\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"density\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"comfortable\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"surface\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"outline\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"highlight\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"border\"\u003C\u002Fspan> },\n    \u003Cspan class=\"shj-syn-var\">\"options\"\u003C\u002Fspan>: { \u003Cspan class=\"shj-syn-var\">\"defaultInterval\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"year\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"yearlyAs\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"total\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"cumulative\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-bool\">true\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"showExcluded\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-bool\">false\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"compare\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-bool\">false\u003C\u002Fspan> },\n    \u003Cspan class=\"shj-syn-var\">\"plans\"\u003C\u002Fspan>: [{\n      \u003Cspan class=\"shj-syn-var\">\"key\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"pro\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"name\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"Pro\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"description\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"…\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"badge\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"Más popular\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"highlighted\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-bool\">true\u003C\u002Fspan>,\n      \u003Cspan class=\"shj-syn-var\">\"prices\"\u003C\u002Fspan>: {\n        \u003Cspan class=\"shj-syn-var\">\"month\"\u003C\u002Fspan>: { \u003Cspan class=\"shj-syn-var\">\"interval\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"month\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"amount\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">11\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"currency\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"eur\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"intervalCount\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">1\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"monthly\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">null\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"trialDays\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">14\u003C\u002Fspan>,\n                   \u003Cspan class=\"shj-syn-var\">\"href\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"https:\u002F\u002FYOUR-SUPPORT-HOST\u002Fv1\u002Fpricing\u002Fpk_…\u002Fpricing\u002Fcheckout?plan=pro&#38;interval=month\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"source\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"stripe\"\u003C\u002Fspan>,\n                   \u003Cspan class=\"shj-syn-var\">\"stripePrice\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"price_…\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"appStoreProduct\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"com.acme.pro.monthly\"\u003C\u002Fspan> },\n        \u003Cspan class=\"shj-syn-var\">\"year\"\u003C\u002Fspan>: { \u003Cspan class=\"shj-syn-var\">\"interval\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"year\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"amount\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">110\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"monthly\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">9.17\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"…\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"…\"\u003C\u002Fspan> }\n      },\n      \u003Cspan class=\"shj-syn-var\">\"savings\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">16\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"action\"\u003C\u002Fspan>: { \u003Cspan class=\"shj-syn-var\">\"type\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"checkout\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"label\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"href\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-num\">null\u003C\u002Fspan> },\n      \u003Cspan class=\"shj-syn-var\">\"lines\"\u003C\u002Fspan>: [{ \u003Cspan class=\"shj-syn-var\">\"id\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"ft_…\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"text\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"Proyectos\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"hint\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"value\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"10\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"included\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-bool\">true\u003C\u002Fspan> }],\n      \u003Cspan class=\"shj-syn-var\">\"includes\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"Starter\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"cells\"\u003C\u002Fspan>: { \u003Cspan class=\"shj-syn-var\">\"ft_…\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"10\"\u003C\u002Fspan> }\n    }],\n    \u003Cspan class=\"shj-syn-var\">\"features\"\u003C\u002Fspan>: [{ \u003Cspan class=\"shj-syn-var\">\"id\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"ft_…\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"kind\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"feature\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"text\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"Proyectos\"\u003C\u002Fspan>, \u003Cspan class=\"shj-syn-var\">\"hint\"\u003C\u002Fspan>: \u003Cspan class=\"shj-syn-str\">\"\"\u003C\u002Fspan> }]\n  }\n}\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>Amounts are in the currency's units (19.99). A price's \u003Ccode>href\u003C\u002Fcode> is where its button\ngoes (\u003Ccode>null\u003C\u002Fcode> for no button). \u003Ccode>GET \u002Fv1\u002Fpricing\u002F{pk}\u003C\u002Fcode> answers the project's first\npublished table. The answer has an \u003Ccode>ETag\u003C\u002Fcode> and may be cached for a minute.\u003C\u002Fp>\n\u003Cp>To count views and clicks from your own UI, send them, and keep sending the same\n\u003Ccode>visitor\u003C\u002Fcode> you asked with:\u003C\u002Fp>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-bash\">\u003Ccode>\u003Cspan class=\"shj-syn-func\">curl\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -s\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -X\u003C\u002Fspan> POST https:\u002F\u002FYOUR-SUPPORT-HOST\u002Fv1\u002Fpricing\u002FPROJECT_PUBLIC_KEY\u002Fpricing\u002Fevents \\\n \u003Cspan class=\"shj-syn-kwd\"> -H\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"Content-Type: application\u002Fjson\"\u003C\u002Fspan> \\\n \u003Cspan class=\"shj-syn-kwd\"> -d\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">'{\"type\":\"click\",\"visitor\":\"v_8f2c1d4e\",\"plan\":\"pro\",\"interval\":\"year\"}'\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>During a test, add the JSON's \u003Ccode>experiment.table\u003C\u002Fcode> and \u003Ccode>experiment.run\u003C\u002Fcode> as\n\u003Ccode>experiment\u003C\u002Fcode> and \u003Ccode>run\u003C\u002Fcode>, and use the \u003Ccode>key\u003C\u002Fcode> of the table you were given in the\npath. The answer is always \u003Ccode>204\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch2 id=\"managing-tables-over-the-api\">Managing tables over the API\u003Ca class=\"heading-anchor\" href=\"#managing-tables-over-the-api\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>With an admin API key (\u003Ccode>ak_…\u003C\u002Fcode>, \u003Cstrong>Settings → API keys\u003C\u002Fstrong>) or from an\n\u003Ca href=\"\u002Fdocs\u002Fmcp\">AI assistant\u003C\u002Fa>:\u003C\u002Fp>\n\u003Cdiv class=\"code-block\">\u003Cbutton type=\"button\" class=\"copy-btn\" data-copy aria-label=\"Copy to clipboard\">Copy\u003C\u002Fbutton>\u003Cpre class=\"shj-lang-bash\">\u003Ccode>\u003Cspan class=\"shj-syn-cmnt\"># Create a draft (it starts with example plans that fit the template)\u003C\u002Fspan>\n\u003Cspan class=\"shj-syn-func\">curl\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -s\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -X\u003C\u002Fspan> POST https:\u002F\u002FYOUR-SUPPORT-HOST\u002Fv1\u002Fadmin\u002Fpricing-tables \\\n \u003Cspan class=\"shj-syn-kwd\"> -H\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"Authorization: Bearer ak_…\"\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -H\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"Content-Type: application\u002Fjson\"\u003C\u002Fspan> \\\n \u003Cspan class=\"shj-syn-kwd\"> -d\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">'{\"project\":\"PROJECT_ID\",\"name\":\"Pricing\",\"template\":\"paywall\"}'\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-syn-cmnt\"># Your Stripe products and prices, to link plans to\u003C\u002Fspan>\n\u003Cspan class=\"shj-syn-func\">curl\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -s\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"https:\u002F\u002FYOUR-SUPPORT-HOST\u002Fv1\u002Fadmin\u002Fstripe\u002Fcatalog?project=PROJECT_ID\"\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -H\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"Authorization: Bearer ak_…\"\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-syn-cmnt\"># Change the words and the look (only what you send changes; a list replaces the old one)\u003C\u002Fspan>\n\u003Cspan class=\"shj-syn-func\">curl\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -s\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -X\u003C\u002Fspan> PATCH https:\u002F\u002FYOUR-SUPPORT-HOST\u002Fv1\u002Fadmin\u002Fpricing-tables\u002FTABLE_ID \\\n \u003Cspan class=\"shj-syn-kwd\"> -H\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"Authorization: Bearer ak_…\"\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -H\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"Content-Type: application\u002Fjson\"\u003C\u002Fspan> \\\n \u003Cspan class=\"shj-syn-kwd\"> -d\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">'{\"content\":{\"title\":\"Simple pricing\"},\"style\":{\"accent\":\"#0ea5e9\",\"radius\":20}}'\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-syn-cmnt\"># Publish it\u003C\u002Fspan>\n\u003Cspan class=\"shj-syn-func\">curl\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -s\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -X\u003C\u002Fspan> POST https:\u002F\u002FYOUR-SUPPORT-HOST\u002Fv1\u002Fadmin\u002Fpricing-tables\u002FTABLE_ID\u002Fpublish\u003Cspan class=\"shj-syn-kwd\"> -H\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"Authorization: Bearer ak_…\"\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-syn-cmnt\"># Its numbers over the last 30 days, and its A\u002FB test\u003C\u002Fspan>\n\u003Cspan class=\"shj-syn-func\">curl\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -s\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"https:\u002F\u002FYOUR-SUPPORT-HOST\u002Fv1\u002Fadmin\u002Fpricing-tables\u002FTABLE_ID\u002Fstats?days=30\"\u003C\u002Fspan>\u003Cspan class=\"shj-syn-kwd\"> -H\u003C\u002Fspan> \u003Cspan class=\"shj-syn-str\">\"Authorization: Bearer ak_…\"\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>The rest: \u003Ccode>GET \u002Fv1\u002Fadmin\u002Fpricing-tables?project=…\u003C\u002Fcode>, \u003Ccode>GET\u003C\u002Fcode> and \u003Ccode>DELETE …\u002F{id}\u003C\u002Fcode>,\n\u003Ccode>POST …\u002F{id}\u002Funpublish\u003C\u002Fcode>, \u003Ccode>…\u002Ftranslate\u003C\u002Fcode>, \u003Ccode>…\u002Fpreview\u003C\u002Fcode> (the working copy, or the\nfields you send, as apps would get it), \u003Ccode>…\u002Fvariants\u003C\u002Fcode>, \u003Ccode>…\u002Fexperiment\u002Fstart\u003C\u002Fcode>,\n\u003Ccode>…\u002Fexperiment\u002Fstop\u003C\u002Fcode> and \u003Ccode>…\u002Fsuggest\u003C\u002Fcode>. Every field and answer is in the\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Felrumo\u002Fhelp-bites-oss\u002Fblob\u002Fmain\u002Fdocs\u002F_docs\u002Fscalar\" target=\"_blank\" rel=\"noopener\">API reference\u003C\u002Fa>. Reading a table needs a role that can edit\ncontent; changing one, reading Stripe and the numbers need one that can run\nmarketing.\u003C\u002Fp>\n\u003Ch2 id=\"limits-and-rules\">Limits and rules\u003Ca class=\"heading-anchor\" href=\"#limits-and-rules\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cul>\n\u003Cli>50 tables a project, 6 plans a table, 40 feature rows, 3 variants a test, 20\nlanguages a table.\u003C\u002Fli>\n\u003Cli>Keys are lowercase letters, digits and dashes, up to 40, unique in the project\n(a plan's key, in the table).\u003C\u002Fli>\n\u003Cli>Written prices are 0 to 1,000,000 in the currency's units; trials are up to 730\ndays; corner radius 0 to 32.\u003C\u002Fli>\n\u003Cli>Button links must be \u003Ccode>http\u003C\u002Fcode> or \u003Ccode>https\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>Public reads are limited to 1,200 a minute per address, checkouts to 30 and\nevents to 600.\u003C\u002Fli>\n\u003Cli>A test's variants must be published tables of the same project; weights are\n0 to 100.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"not-built-yet\">Not built yet\u003Ca class=\"heading-anchor\" href=\"#not-built-yet\" aria-label=\"Link to this section\">#\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cul>\n\u003Cli>Usage-based (metered) and tiered Stripe prices are not shown.\u003C\u002Fli>\n\u003Cli>No Stripe Customer Portal: a &quot;Manage plan&quot; button is a link of yours.\u003C\u002Fli>\n\u003Cli>The Swift snippet draws cards only; the other templates are web-only (the\nnative app's preview draws all five).\u003C\u002Fli>\n\u003Cli>Purchases are counted from Stripe only; App Store purchases are not counted.\u003C\u002Fli>\n\u003C\u002Ful>\n",[6,10,13,16,19,22,26,29,32,35,38,41,44,47,50,53,56],{"id":7,"text":8,"level":9},"a-table","A table",2,{"id":11,"text":12,"level":9},"stripe","Stripe",{"id":14,"text":15,"level":9},"billing-periods-and-currencies","Billing periods and currencies",{"id":17,"text":18,"level":9},"buttons-and-checkout","Buttons and checkout",{"id":20,"text":21,"level":9},"plug-and-play-web","Plug and play: web",{"id":23,"text":24,"level":25},"options","Options",3,{"id":27,"text":28,"level":25},"restyle-it","Restyle it",{"id":30,"text":31,"level":9},"a-hosted-page","A hosted page",{"id":33,"text":34,"level":9},"plug-and-play-swift","Plug and play: Swift",{"id":36,"text":37,"level":9},"in-onboarding","In onboarding",{"id":39,"text":40,"level":9},"numbers-and-ab-tests","Numbers and A\u002FB tests",{"id":42,"text":43,"level":9},"suggest-with-ai","Suggest with AI",{"id":45,"text":46,"level":9},"languages","Languages",{"id":48,"text":49,"level":9},"your-own-ui","Your own UI",{"id":51,"text":52,"level":9},"managing-tables-over-the-api","Managing tables over the API",{"id":54,"text":55,"level":9},"limits-and-rules","Limits and rules",{"id":57,"text":58,"level":9},"not-built-yet","Not built yet",1792677543076]