PaywallMeter
A metered paywall: a slim bar counting the free articles read, a warning on the last, then the gate with the offer, its price, benefits and sign-in.
Demo
Get it
It needs theme.qwe, as every component does; 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/paywall_meter.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/paywall_meter.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/paywall_meter.qwe
view PaywallMeterDemo() = <div.demo-stack style="gap: 20px"> <PaywallMeter id="paywall-meter-demo-bar" used={2} limit={5} subscribe_href="#subscribe" signin_href="#login"/> <PaywallMeter id="paywall-meter-demo-last" used={5} limit={5} subscribe_href="#subscribe"/> <div style="padding-top: 70px"> <PaywallMeter id="paywall-meter-demo-gate" used={6} limit={5} price={1.00m} price_period="week" benefits={["Unlimited articles on any device", "The daily crossword and the archive since 1921", "Cancel anytime"]} resets="Your free articles come back on Nov 1." subscribe_href="#subscribe?offer=intro" signin_href="#login"/> </div> </div>Source
ui/paywall_meter.qwe
// PaywallMeter: the metered paywall's two faces. While free articles are left, a slim// bar: a dot for each free article, those read filled, "You have read 2 of 5 free// articles this month", and Subscribe; on the last one it says so. Once they are used// up, the gate: what happened, the offer with its price, what a subscription brings,// Subscribe, and a way in for subscribers. A view: the server counts, the page shows.//// <PaywallMeter used={2} limit={5} subscribe_href="/subscribe" signin_href="/login"/>// <PaywallMeter used={6} limit={5} price={1.00m} price_period="week" resets="Your free articles come back on Nov 1."// benefits={["Unlimited articles on any device", "The daily crossword", "Cancel anytime"]}// subscribe_href="/subscribe?offer=intro" signin_href="/login"/>//// used: the free articles read in the period, this one included; limit: how many are// free. At the limit this one is the last ("This is your last free article"); past it,// the gate shows instead. period: "this month" (default), "this week". subscribe_href: where Subscribe// goes; subscribe_label: its text ("Subscribe"). signin_href: the link for those who// already pay ("Sign in"). price: the offer's price, a decimal (12.50m), in currency// ("USD") and written for locale ("en-US"); price_period: "week", "month" ("month"),// so it reads "$1.00 a week". offer: the gate's line about the offer, instead of// "Subscribe for $1.00 a week."; benefits: a few lines of what a subscription brings,// ticked. resets: a line on when free articles come back. title: the gate's heading,// instead of "You've read all 5 free articles this month". id: the element's id.import ui/theme.qwe use ui_clslet paywall_meter_css = css`.ui-pwm { color: var(--ui-fg); font: 14px/1.45 var(--ui-font); }.ui-pwm-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 16px; max-width: 760px; padding: 10px 14px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius); background: var(--ui-surface); box-shadow: var(--ui-shadow); }.ui-pwm-dots { display: inline-flex; gap: 5px; flex: none; }.ui-pwm-dot { width: 10px; height: 10px; border: 2px solid var(--ui-accent); border-radius: 50%; }.ui-pwm-dot-on { background: var(--ui-accent); }.ui-pwm-last .ui-pwm-dot { border-color: var(--ui-warning); }.ui-pwm-last .ui-pwm-dot-on { background: var(--ui-warning); }.ui-pwm-text { flex: 1 1 220px; margin: 0; }.ui-pwm-text b { font-weight: 700; }.ui-pwm-actions { display: flex; align-items: center; gap: 12px; flex: none; }.ui-pwm-sub { display: inline-flex; align-items: center; justify-content: center; height: 36px; padding: 0 16px; border-radius: 999px; background: var(--ui-accent); color: var(--ui-accent-fg); font-weight: 650; text-decoration: none; white-space: nowrap; }.ui-pwm-sub:hover { filter: brightness(1.08); }.ui-pwm-in { color: var(--ui-fg); font-weight: 600; text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--ui-strong); }.ui-pwm-in:hover { color: var(--ui-accent); }.ui-pwm a:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-pwm-gate { position: relative; display: grid; justify-items: center; gap: 12px; max-width: 560px; padding: 28px 28px 24px; text-align: center; border: 1px solid var(--ui-border); border-top: 4px solid var(--ui-accent); border-radius: var(--ui-radius); background: var(--ui-surface); box-shadow: var(--ui-shadow-lg); }.ui-pwm-gate::before { content: ""; position: absolute; left: 0; right: 0; bottom: 100%; height: 90px; pointer-events: none; background: linear-gradient(to bottom, transparent, var(--ui-bg)); margin-bottom: 4px; }.ui-pwm-gate .ui-pwm-dots { margin-bottom: 2px; }.ui-pwm .ui-pwm-title { margin: 0; font-size: 22px; line-height: 1.25; font-weight: 750; text-wrap: balance; }.ui-pwm-offer { margin: 0; color: var(--ui-muted); font-size: 16px; }.ui-pwm-offer b { color: var(--ui-fg); }.ui-pwm-list { display: grid; gap: 6px; margin: 4px 0; padding: 0; list-style: none; text-align: left; }.ui-pwm-list li { display: flex; gap: 8px; }.ui-pwm-list li::before { content: "✓"; color: var(--ui-success); font-weight: 800; }.ui-pwm-gate .ui-pwm-sub { height: 44px; min-width: 220px; font-size: 16px; }.ui-pwm-note { margin: 0; color: var(--ui-muted); font-size: 13px; }`// A price as it reads: "$1.00", "€4.99".fn paywall_meter_money(amount, code: str, locale: str) -> str { let m = money.of(dec(amount), code, "half_up") type_of(m) == "error" ? `${format_number(round(dec(amount), 2, "half_up"), locale, 2)} ${code}` : money.format(m, locale)}view PaywallMeter(used: int, limit: int, period: str = "this month", subscribe_href: str = "/subscribe", subscribe_label: str = "Subscribe", signin_href: str? = null, price = null, currency: str = "USD", price_period: str = "month", offer: str? = null, benefits: list = [], resets: str? = null, title: str? = null, locale: str = "en-US", id: str? = null, class: str? = null) { style global paywall_meter_css let read = used < 0 ? 0 : (used > limit ? limit : used) let out = used > limit let last = used == limit let noun = (n: int) => n == 1 ? "free article" : "free articles" let dots = <span.ui-pwm-dots role="img" aria-label={`${read} of ${limit} ${noun(limit)} read`}> {for i in range(limit) => <span class={ui_cls(["ui-pwm-dot", i < read && "ui-pwm-dot-on"])}></span>} </span> let signin = signin_href != null && <a.ui-pwm-in href={signin_href}>Sign in</a> out ? <section class={ui_cls(["ui-pwm", "ui-pwm-gate", class])} id={id} aria-labelledby={id != null ? `${id}-title` : null} aria-label={id == null ? "Subscribe to keep reading" : null}> {dots} <h2.ui-pwm-title id={id != null ? `${id}-title` : null}>{title ?? `You've read all ${limit} ${noun(limit)} ${period}`}</h2> <p.ui-pwm-offer>{offer ?? (price != null ? ["Subscribe for ", <b>{paywall_meter_money(price, currency, locale)} a {price_period}</b>, " to keep reading."] : "Subscribe to keep reading.")}</p> {len(benefits) > 0 && <ul.ui-pwm-list>{for b in benefits => <li>{b}</li>}</ul>} <a.ui-pwm-sub href={subscribe_href}>{subscribe_label}</a> {signin_href != null && <p.ui-pwm-note>Already a subscriber? {signin}</p>} {resets != null && <p.ui-pwm-note>{resets}</p>} </section> : <aside class={ui_cls(["ui-pwm", "ui-pwm-bar", last && "ui-pwm-last", class])} id={id} aria-label="Free articles"> {dots} <p.ui-pwm-text> {last ? ["This is your ", <b>last {noun(1)}</b>, ` ${period}.`] : ["You have read ", <b>{read} of {limit}</b>, ` ${noun(limit)} ${period}.`]} </p> <div.ui-pwm-actions> {signin} <a.ui-pwm-sub href={subscribe_href}>{subscribe_label}</a> </div> </aside>}Related
BreakingNewsA breaking-news banner: the label in a block of colour, the headline and a line more, how long ago it came in, kept up to date, and a close that can be remembered.9 views · 8 downloadsGiftArticleGift this article: a panel with the gift articles left this month, a link anyone can read the story with, Copy and Email, and when more come once used up.9 views · 8 downloadsProrationCalcWorks out a mid-period change of plan by the day: credit for the time left, charge for the new plan and what is due, across month lengths, leap years and downgrades.2 views · 8 downloadsAdSlotAn advertisement's place kept at its size so the story never jumps: standard sizes by name, another on phones, a clear label, a placeholder, sticky in a rail.6 views · 8 downloadsArticleHeaderThe top of a story: its label and section, the headline, the dek, every author grouped by role, published and updated times, and the reading time.8 views · 9 downloadsCorrectionNoticeA correction, clarification, update, editor's note or retraction on a story, dated, with a link to the corrections policy; boxed on top or a line at the end.8 views · 7 downloads
Thread
Thread 0 posts
On PaywallMeter: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.