PaywallSheet
Plans with a recommended one, the price per month and per year worked out, restore, terms and honest trial wording.
Demo
Nothing bought.
Get it
It needs theme.qwe, as every component does, and price.qwe; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.
$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/price.qwe$ curl -O https://quillolang.org/components/ui/paywall_sheet.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/paywall_sheet.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/paywall_sheet.qwe
let paywall_sheet_demo_plans = [ { id: "week", name: "Weekly", price: "2.99", period: "week" }, { id: "month", name: "Monthly", price: "9.99", period: "month" }, { id: "year", name: "Yearly", price: "59.99", period: "year", trial_days: 7, recommended: true }, { id: "life", name: "For life", price: "149.00", period: "once" }, { id: "bad", name: "Broken", price: "nine dollars", period: "month" },]let paywall_sheet_demo_yen = [ { id: "m", name: "Monthly", price: 980, period: "month" }, { id: "y", name: "Yearly", price: 6800, period: "year", trial_days: 7, recommended: true },]let paywall_sheet_demo_odd = [ { id: "q", name: "Quarterly", price: "4.99", period: "quarter", trial_days: "seven" },]// The same answer a moment later, as a `server fn` gives it: a Promise.fn paywall_sheet_demo_late(answer) = js("Promise.resolve", js_new("Promise", (done) => { done(answer) }))fn paywall_sheet_demo_buy(fail: bool, plan) { if fail { return { ok: false, message: "The store said no." } } if plan.id == "life" { return { ok: false, cancelled: true } } if plan.id == "week" { return null } true}component PaywallSheetDemoLive() { state said = "Nothing bought." state fail = false state slow = false state now = false // A named `let` here and an inline function below: callbacks of both kinds. let buying = (plan) => { let answer = paywall_sheet_demo_buy(fail, plan) slow ? paywall_sheet_demo_late(answer) : answer } <div.demo-stack> <div.demo-row id="paywall-sheet-demo-one"> <PaywallSheet trigger="See plans" title="Keep every note" today="2026-11-01" terms="#terms" privacy="#privacy" benefits={["Unlimited notes", "Photos and voice memos", "Sync across your devices"]} plans={paywall_sheet_demo_plans} on_buy={buying} on_restore={() => { let answer = fail ? { ok: false } : { ok: true, message: "Restored your Yearly plan." } slow ? paywall_sheet_demo_late(answer) : answer }} on_bought={(plan) => { said = `Bought ${plan.id}.` }} on_close={() => { said = `${said} Closed.` }}/> </div> <div.demo-row id="paywall-sheet-demo-two"> <PaywallSheet id="paywall-sheet-yen" trigger="In yen" title="Plans in yen" today="2026-12-28" currency="JPY" plans={paywall_sheet_demo_yen}/> </div> <div.demo-row id="paywall-sheet-demo-three"> <PaywallSheet id="paywall-sheet-odd" trigger="Odd data" title="Odd data" currency="NOPE" today="not a date" plans={paywall_sheet_demo_odd}/> <Button size="lg" variant="outline" id="paywall-sheet-demo-now" on_click={() => { now = true }}>Open from state</Button> </div> {now && <PaywallSheet id="paywall-sheet-now" open={true} title="Opened from state" plans={paywall_sheet_demo_plans} on_close={() => { now = false }}/>} <label><input type="checkbox" id="paywall-sheet-demo-fail" bind:checked={fail}/> Let the store refuse</label> <label><input type="checkbox" id="paywall-sheet-demo-slow" bind:checked={slow}/> Answers come as Promises</label> <p.demo-note id="paywall-sheet-demo-said">{said}</p> </div>}view PaywallSheetDemo() = <PaywallSheetDemoLive/>Source
ui/paywall_sheet.qwe
// PaywallSheet: the plans of a subscription in a sheet from the bottom of the screen, a// centred card on a tablet. One plan is recommended, and every plan shows what it costs a// month and a year, worked out, never rounded in the seller's favour. A trial says when the// first payment comes. Restore, the terms and a way out are always there. It takes no// money itself: on_buy and on_restore are the app's own.//// <PaywallSheet trigger="See plans" title="Keep every note" today={date.today()}// benefits={["Unlimited notes", "Photos and voice memos", "Sync across your devices"]}// terms="/terms" privacy="/privacy" plans={[// { id: "month", name: "Monthly", price: "9.99", period: "month" },// { id: "year", name: "Yearly", price: "59.99", period: "year", trial_days: 7, recommended: true }// ]} on_buy={(plan) => buy(plan.id)} on_restore={() => restore()}/>//// plans: a list of { id, name, price, period, trial_days, recommended }. price: the amount// for one period, as text or a decimal. period: "week", "month", "quarter", "half" (six months),// "year" or "once" (a single payment, nothing renewing). A plan with a price that cannot be read// is left out rather than shown wrong. trial_days: days free before the first payment. recommended: the// one picked to start with, marked "Best value" (or `badge`, for other words); the first plan otherwise.// today: the date a trial starts, so that it can say when it ends; without it, it says how long,// not when. currency ("USD"), locale ("en-US"): how money and dates are written.// title, benefits (a list of text), terms and privacy (addresses), id ("paywall-sheet").// trigger: the text of a button that opens it. Inside one of your components, open it from// your state: open={...} and on_close(). on_buy(plan), on_restore(): inside one of your// components; each may wait, and gives true or { ok: true }, or { ok: false, message }, or// { ok: false, cancelled: true } when the person backed out of the store's own sheet, which// says nothing. An error value or null means it did not go through. on_bought(plan): after on_buy worked.//// Dragging the handle down closes it, past a third of its height or with a flick; Not now,// the Escape key and a tap above it do too. The stores require a working restore,// the price and the length of a subscription, and terms: leave none of them out.import ui/theme.qwe use ui_clsimport ui/button.qwe use Buttonimport ui/price.qwe use price_textlet paywall_sheet_css = css`.ui-paywall-sheet-host { display: contents; }.ui-paywall-sheet { box-sizing: border-box; inset: auto 0 0 0; width: 100%; max-width: 100vw; max-height: calc(100dvh - env(safe-area-inset-top) - 16px); margin: 0 auto; padding: 0; overflow: hidden; border: 1px solid var(--ui-border); border-bottom: 0; border-radius: calc(var(--ui-radius) + 8px) calc(var(--ui-radius) + 8px) 0 0; background: var(--ui-surface); color: var(--ui-fg); box-shadow: var(--ui-shadow-lg); font: 15px/1.45 var(--ui-font); transition: transform .26s cubic-bezier(.2, .8, .2, 1);}.ui-paywall-sheet[open] { display: flex; flex-direction: column; animation: ui-paywall-sheet-up .28s cubic-bezier(.2, .8, .2, 1); }.ui-paywall-sheet::backdrop { background: var(--ui-overlay); }.ui-paywall-sheet-held { transition: none; }@keyframes ui-paywall-sheet-up { from { transform: translateY(100%); } }.ui-paywall-sheet-top { position: relative; flex: none; display: flex; align-items: center; justify-content: flex-end; min-height: 44px; }.ui-paywall-sheet-grip { position: absolute; inset: 0; display: grid; place-items: center; width: 100%; height: 44px; padding: 0; border: 0; background: transparent; cursor: grab; touch-action: none; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;}.ui-paywall-sheet-grip span { width: 40px; height: 5px; border-radius: 999px; background: var(--ui-strong); }.ui-paywall-sheet-grip:focus-visible { outline: none; }.ui-paywall-sheet-grip:focus-visible span { background: var(--ui-accent); box-shadow: var(--ui-focus); }.ui-paywall-sheet-top .ui-btn { position: relative; }.ui-paywall-sheet-body { flex: 1; min-height: 0; overflow: auto; overscroll-behavior: contain; padding: 0 20px 12px; container: ui-paywall-sheet / inline-size; }.ui-paywall-sheet-body h2 { margin: 0 0 8px; font-size: 24px; line-height: 1.25; }.ui-paywall-sheet-good { display: grid; gap: 4px; margin: 0 0 14px; padding: 0; list-style: none; }.ui-paywall-sheet-good li { display: flex; gap: 8px; }.ui-paywall-sheet-good svg { flex: none; width: 20px; height: 20px; margin-top: 1px; color: var(--ui-success); }.ui-paywall-sheet-plans { position: relative; display: grid; gap: 8px; margin: 0 0 12px; padding: 0; border: 0; min-width: 0; }.ui-paywall-sheet-plan { position: relative; display: grid; grid-template-columns: 22px minmax(0, 1fr) auto; gap: 2px 10px; align-items: center; min-height: 64px; padding: 10px 12px; min-width: 0; border: 1.5px solid var(--ui-border); border-radius: var(--ui-radius); background: var(--ui-bg); cursor: pointer;}.ui-paywall-sheet-plan:has(input:checked) { border-color: var(--ui-accent); background: var(--ui-accent-soft); }.ui-paywall-sheet-plan:has(input:focus-visible) { box-shadow: var(--ui-focus); }.ui-paywall-sheet-plan input { width: 20px; height: 20px; margin: 0; accent-color: var(--ui-accent); }.ui-paywall-sheet-name { display: flex; flex-wrap: wrap; align-items: center; gap: 2px 6px; min-width: 0; font-weight: 600; }.ui-paywall-sheet-badge { padding: 1px 7px; border-radius: 999px; background: var(--ui-accent); color: var(--ui-accent-fg); font-size: 11px; font-weight: 600; white-space: nowrap; }.ui-paywall-sheet-costs { display: grid; justify-items: end; font-variant-numeric: tabular-nums; text-align: end; }.ui-paywall-sheet-cost { font-weight: 600; }.ui-paywall-sheet-other { color: var(--ui-muted); font-size: 13px; }.ui-paywall-sheet-save { grid-column: 2 / -1; color: var(--ui-success); font-size: 13px; font-weight: 600; }.ui-paywall-sheet-foot { flex: none; display: grid; gap: 6px; padding: 8px 20px max(10px, env(safe-area-inset-bottom)); border-top: 1px solid var(--ui-border); background: var(--ui-surface); }.ui-paywall-sheet-wording { margin: 0; color: var(--ui-muted); font-size: 13px; }.ui-paywall-sheet-said { margin: 0; font-size: 14px; color: var(--ui-success); }.ui-paywall-sheet-said[data-bad] { color: var(--ui-danger); }.ui-paywall-sheet-links { display: flex; flex-wrap: wrap; justify-content: center; gap: 0 14px; }.ui-paywall-sheet-link { display: inline-flex; align-items: center; min-height: 44px; min-width: 44px; padding: 0 2px; border: 0; background: none; color: var(--ui-muted); font: 13px var(--ui-font); text-decoration: underline; cursor: pointer; }.ui-paywall-sheet-link:focus-visible { outline: none; box-shadow: var(--ui-focus); }@container ui-paywall-sheet (min-width: 560px) { .ui-paywall-sheet-plans { grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); grid-auto-flow: column; } .ui-paywall-sheet-plan { grid-template-columns: 22px minmax(0, 1fr); align-content: start; } .ui-paywall-sheet-costs { grid-column: 1 / -1; justify-items: start; text-align: start; } .ui-paywall-sheet-save { grid-column: 1 / -1; }}/* The whole window, however wide it is: a dialog sits above any pane. */@media (min-width: 700px) { .ui-paywall-sheet { inset: 0; width: min(760px, 92vw); max-height: min(88dvh, 720px); height: fit-content; margin: auto; border-bottom: 1px solid var(--ui-border); border-radius: calc(var(--ui-radius) + 6px); } .ui-paywall-sheet-grip { display: none; }}@media (prefers-reduced-motion: reduce) { .ui-paywall-sheet { transition: none; } .ui-paywall-sheet[open] { animation: none; } }`// Words for each period: "a year", and how many of them fit in a year.fn paywall_sheet_rate(period: str?) = match period { "week" => { per: "a week", every: "week", fits: 52, name: "week" } "month" => { per: "a month", every: "month", fits: 12, name: "month" } "quarter" => { per: "every 3 months", every: "3 months", fits: 4, name: "quarter" } "half" => { per: "every 6 months", every: "6 months", fits: 2, name: "half" } "year" => { per: "a year", every: "year", fits: 1, name: "year" } "once" => { per: "once", every: "", fits: 0, name: "once" } _ => null}// An amount as a decimal, or null when it is not one a price could be.fn paywall_sheet_price(amount) { if amount == null || type_of(amount) == "bool" { return null } let t = trim(str(amount)) if !re.test(t, "^[0-9]{1,9}([.][0-9]{1,6})?$") { return null } dec(t)}// Rounded up to what a currency can charge. A monthly figure rounded down would add up to// less than the year costs, so it is the only rounding here, and it goes against the seller.fn paywall_sheet_up(amount, currency: str) { let units = money.minor_units(currency) // A currency nobody knows gives an error value: two places, as the most common. let scale = dec(`1${repeat("0", is_err(units) ? 2 : units)}`) ceil(amount * scale) / scale}// The plan's cost over a year, or null for a single payment.fn paywall_sheet_year_cost(plan) { let rate = paywall_sheet_rate(get(plan, "period")) let price = paywall_sheet_price(get(plan, "price")) rate == null || rate.fits == 0 || price == null ? null : price * rate.fits}// The plans that can be shown, each with its figures: { plan, rate, price, year, month }.fn paywall_sheet_figures(plans: list, currency: str) -> list { let usable = plans | filter((p) => paywall_sheet_rate(get(p, "period")) != null && paywall_sheet_price(get(p, "price")) != null) for p in usable { let year = paywall_sheet_year_cost(p) { plan: p, rate: paywall_sheet_rate(p.period), price: paywall_sheet_price(p.price), year: year, month: year != null ? paywall_sheet_up(year / 12, currency) : null } }}// The whole per cent saved against `base` a year, never rounded up; null when none.fn paywall_sheet_saving(year, base) { if year == null || base == null || base <= dec(0) || year >= base { return null } let pct = floor((dec(1) - year / base) * dec(100)) pct >= dec(1) ? int(pct) : null}// What savings are measured against: the monthly plan, as people think in months, or// the shortest period there is.fn paywall_sheet_base(rows: list) { let monthly = rows | filter((r) => r.rate.name == "month") if len(monthly) > 0 { return monthly[0].year } let priced = rows | filter((r) => r.year != null) | sort_by((r) => -r.rate.fits) len(priced) > 0 ? priced[0].year : null}fn paywall_sheet_days(n: int) -> str = n == 1 ? "1 day" : `${n} days`// A plan's free days: a whole number within reason, or none. Anything else in the data// is not a trial, as promising one that was not meant would be the worse mistake.fn paywall_sheet_trial(plan) -> int { let n = get(plan, "trial_days") type_of(n) == "int" && n > 0 && n <= 3650 ? n : 0}// When a trial's first payment falls, as text, or null without a good date.fn paywall_sheet_charge_day(today, days: int, locale: str) -> str? { if type_of(today) != "str" || !date.valid(today, "date") { return null } date.format(date.add_days(today, days), { locale: locale, style: "long" })}// What a plan will cost and when, in plain words: the line under the button.fn paywall_sheet_wording(row, today, currency: str, locale: str) -> str { let cost = price_text(row.price, currency, locale) let trial = paywall_sheet_trial(row.plan) if row.rate.name == "once" { return `One payment of ${cost}. Nothing renews.` } let renew = `It renews every ${row.rate.every} until you cancel, which you can do at any time in your phone's settings.` if trial > 0 { let day = paywall_sheet_charge_day(today, trial, locale) let when = day != null ? `Your first payment is on ${day}` : `Your first payment comes when the ${paywall_sheet_days(trial)} are up` return `Free for ${paywall_sheet_days(trial)}, then ${cost} ${row.rate.per}. ${when}, unless you cancel at least a day before. ${renew}` } `${cost} today, then ${row.rate.name == "year" ? "every year" : `every ${row.rate.every}`}. ${renew}`}// The button's words: what pressing it does, money included.fn paywall_sheet_action(row, currency: str, locale: str) -> str { let cost = price_text(row.price, currency, locale) let trial = paywall_sheet_trial(row.plan) if row.rate.name == "once" { return `Buy for ${cost}` } trial > 0 ? `Start ${trial}-day free trial` : `Subscribe for ${cost} ${row.rate.per}`}// A callback's answer: { ok, cancelled, message }. Only true or ok: true is success.fn paywall_sheet_answer(said) { if is_err(said) || said == null { return { ok: false, cancelled: false, message: null } } if said == true { return { ok: true, cancelled: false, message: null } } if type_of(said) != "map" { return { ok: false, cancelled: false, message: null } } { ok: get(said, "ok") == true, cancelled: get(said, "cancelled") == true, message: get(said, "message") }}// Whether letting go closes it: `dy` pixels down, at `speed`, with `height` to go.fn paywall_sheet_closes(dy: num, speed: num, height: num) -> bool = dy >= height / 3 || (dy >= 12 && speed >= 0.5)component PaywallSheet(plans: list = [], title: str = "Choose a plan", benefits: list = [], today = null, currency: str = "USD", locale: str = "en-US", badge: str = "Best value", terms: str? = null, privacy: str? = null, trigger: str? = null, open: bool = false, id: str = "paywall-sheet", on_buy = null, on_restore = null, on_bought = null, on_close = null, class: str? = null) { style global paywall_sheet_css let rows = paywall_sheet_figures(plans, currency) let start = find(rows, (r) => get(r.plan, "recommended") == true) ?? (len(rows) > 0 ? rows[0] : null) state picked = start != null ? get(start.plan, "id") : null state dlg = null state busy = "" state said = "" state said_bad = false state held = false let finger = null let y0 = 0 let last_y = 0 let last_t = 0 let speed = 0 let row = find(rows, (r) => get(r.plan, "id") == picked) ?? start let base = paywall_sheet_base(rows) if open && dlg != null && !dlg.open { dlg.showModal() } if !open && dlg != null && dlg.open && trigger == null { dlg.close() } let tell = (text: str, bad: bool) => { said = text said_bad = bad } let buy = () => { if busy == "" && row != null { busy = "buy" tell("", false) let got = paywall_sheet_answer(js("Promise.resolve", on_buy != null ? on_buy(row.plan) : null)) busy = "" if got.ok { on_bought && on_bought(row.plan) dlg.close() } else if !got.cancelled { tell(got.message ?? "The purchase did not go through. You have not been charged.", true) } } } let restore = () => { if busy == "" { busy = "restore" tell("", false) let got = paywall_sheet_answer(js("Promise.resolve", on_restore != null ? on_restore() : null)) busy = "" if got.ok { tell(got.message ?? "Your purchases are restored.", false) } else if !got.cancelled { tell(got.message ?? "No purchases were found to restore.", true) } } } let spring = () => { finger = null held = false dlg.style.transform = "" } <div.ui-paywall-sheet-host> {trigger != null && <button.ui-btn.ui-btn-solid.ui-btn-lg type="button" aria-haspopup="dialog" @click={() => { dlg.showModal() }}>{trigger}</button>} <dialog class={ui_cls(["ui-paywall-sheet", held && "ui-paywall-sheet-held", class])} aria-labelledby={`${id}-title`} @mount={(el) => { dlg = el if open && !el.open { el.showModal() } }} @close={() => { tell("", false) on_close && on_close() }} @click={(e) => { if e.target.tagName == "DIALOG" { dlg.close() } }}> <div.ui-paywall-sheet-top> <button.ui-paywall-sheet-grip type="button" aria-label="Close" @pointerdown={(e) => { if finger == null { finger = e.pointerId e.target.closest("button").setPointerCapture(e.pointerId) y0 = e.clientY last_y = e.clientY last_t = e.timeStamp speed = 0 held = true } }} @pointermove={(e) => { if e.pointerId == finger { let gap = e.timeStamp - last_t if gap > 0 { speed = speed * 0.4 + (e.clientY - last_y) / gap * 0.6 } last_y = e.clientY last_t = e.timeStamp dlg.style.transform = `translateY(${max([0, e.clientY - y0])}px)` } }} @pointerup={(e) => { if e.pointerId == finger { let dy = e.clientY - y0 let moving = e.timeStamp - last_t > 80 ? 0 : speed let height = dlg.offsetHeight spring() if paywall_sheet_closes(dy, moving, height) { dlg.close() } } }} @pointercancel={(e) => { if e.pointerId == finger { spring() } }} @click={(e) => { // The keyboard's Enter. A finger's tap leaves the sheet where it is, as it may be a slip. if e.detail == 0 { dlg.close() } }}><span aria-hidden="true"></span></button> <Button variant="ghost" tone="neutral" size="lg" on_click={() => { dlg.close() }}>Not now</Button> </div> <div.ui-paywall-sheet-body> <h2 id={`${id}-title`}>{title}</h2> {len(benefits) > 0 && <ul.ui-paywall-sheet-good> {for b in benefits => <li key={b}> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 13l4 4L19 7"/></svg> <span>{b}</span> </li>} </ul>} <fieldset.ui-paywall-sheet-plans> <legend class="ui-sr">Plans</legend> {for r in rows { let pid = get(r.plan, "id") let save = paywall_sheet_saving(r.year, base) let trial = paywall_sheet_trial(r.plan) <label.ui-paywall-sheet-plan key={pid} data-plan={pid}> <input type="radio" name={`${id}-plan`} value={pid} checked={pid == picked} @change={() => { picked = pid; tell("", false) }}/> <span.ui-paywall-sheet-name>{get(r.plan, "name") ?? pid} {get(r.plan, "recommended") == true && <span.ui-paywall-sheet-badge>{badge}</span>}</span> <span.ui-paywall-sheet-costs> <span.ui-paywall-sheet-cost>{price_text(r.price, currency, locale)} {r.rate.per}</span> {r.rate.name != "month" && r.month != null && <span.ui-paywall-sheet-other>{price_text(r.month, currency, locale)} a month</span>} {r.rate.name != "year" && r.year != null && <span.ui-paywall-sheet-other>{price_text(r.year, currency, locale)} a year</span>} {r.rate.name == "once" && <span.ui-paywall-sheet-other>Yours to keep</span>} </span> {(save != null || trial > 0) && <span.ui-paywall-sheet-save> {[trial > 0 ? `${paywall_sheet_days(trial)} free` : null, save != null ? `Save ${save}%` : null] | filter((x) => x != null) | join(". ")} </span>} </label> }} </fieldset> </div> <div.ui-paywall-sheet-foot> {said != "" && <p.ui-paywall-sheet-said role="status" data-bad={said_bad ? "1" : null}>{said}</p>} {row != null && <p.ui-paywall-sheet-wording id={`${id}-wording`}>{paywall_sheet_wording(row, today, currency, locale)}</p>} {row != null ? <Button size="lg" full={true} loading={busy == "buy"} disabled={busy != ""} on_click={buy}>{paywall_sheet_action(row, currency, locale)}</Button> : <p.ui-paywall-sheet-wording>No plans are on offer just now.</p>} <div.ui-paywall-sheet-links> <button.ui-paywall-sheet-link type="button" disabled={busy != ""} @click={restore}>{busy == "restore" ? "Restoring" : "Restore purchases"}</button> {terms != null && <a.ui-paywall-sheet-link href={terms}>Terms</a>} {privacy != null && <a.ui-paywall-sheet-link href={privacy}>Privacy</a>} </div> </div> </dialog> </div>}Related
ActionSheetA list of actions rising from the bottom, with a destructive style and Cancel: swipe down or the phone's back gesture closes it; on a tablet, a card by what opened it.3 views · 9 downloadsActivityRingsProgress rings inside each other that go round again past 100%, drawn once, with a legend and a sentence each for screen readers.3 views · 6 downloadsAdaptiveDialogA sheet on a phone and a centred dialog on a tablet or desktop: the same content, chosen by width, changing live on rotation.3 views · 5 downloadsAlertInboxAlerts by severity, swiped to acknowledge or snooze, with buttons and keys to the same, and a badge that stays right as snoozes run out.3 views · 8 downloadsAlphaRailListA long list with sticky letter headers and an A to Z rail scrubbed by finger, with a bubble showing the letter and a haptic per step.1 view · 6 downloadsAnchoredPopoverA popover with an arrow tied to its button on a tablet and a sheet on a phone, re-anchored on rotation and when a keyboard opens.3 views · 5 downloads
Thread
Thread 0 posts
On PaywallSheet: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.