Harbor Federal
7.49%APR
- Monthly
- $400.66
- Term
- 60 months (5 years)
- Fees
- None
- Interest
- $4,039.60
- Total cost
- $24,039.60
Loan offers side by side: each lender's APR, term, monthly payment, fees and total cost, with the cheapest in total marked.
7.49%APR
6.99%APR
$1,339.40 more than the cheapest
Choose Summit Lending8.25%APR
6.75%APR
5.9%APR
5.4%APR
142,44 € more than the cheapest
Auswählen Alpen KreditIt 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/loan_offers.qweimport ui/theme.qwe use theme_cssuse ui/loan_offers.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>The code of the preview above.
view LoanOffersDemo() { <div.demo-stack style="padding-top: 10px"> <LoanOffers id="loan-offers-demo-personal" amount={20000} offers={[ { lender: "Harbor Federal", apr: 7.49, term: 60, fees: 0, href: "#harbor", note: "No early repayment fee." }, { lender: "Summit Lending", apr: 6.99, term: 60, fees: 600, tag: "Pre-approved", href: "#summit" }, { lender: "Bluewater Bank", apr: 8.25, term: 72, fees: 150, href: "#bluewater", note: "Lowest monthly payment." }, { lender: "Northwind Credit Union", apr: 6.75, rate: 6.5, term: 48, fees: 250, tag: "Members", href: "#northwind" }, ]}/> <div.demo-narrow style="max-width: 520px"> <LoanOffers id="loan-offers-demo-eur" amount={12000} currency="EUR" locale="de-DE" choose_label="Auswählen" offers={[ { lender: "Rheinland Bank", apr: 5.9, term: 36, href: "#rheinland" }, { lender: "Alpen Kredit", apr: 5.4, term: 36, fees: 240, monthly: 361.81, href: "#alpen" }, ]}/> </div> </div>}// LoanOffers: loan offers side by side, for one amount: each lender's APR, term,// monthly payment, fees and what the loan costs in all, the cheapest in total// marked ("Lowest total cost"), and how much more each other one costs. The// monthly payment is worked out from the rate when an offer does not give it// (the annuity formula, rounded half up to the cent), in decimals.//// <LoanOffers amount={20000} offers={[// { lender: "Harbor Federal", apr: 7.49, term: 60, fees: 0, href: "/offers/hf" },// { lender: "Summit Lending", apr: 6.99, term: 60, fees: 600, tag: "Pre-approved" },// { lender: "Bluewater Bank", apr: 8.25, term: 72, fees: 150, monthly: 353.42 }]}/>//// amount: what is borrowed, for every offer (a number or a decimal). offers: a list// of { lender, apr, term, fees?, rate?, monthly?, amount?, tag?, note?, href? }: apr// the yearly rate in percent shown; rate the one to work the payment out with (the// APR by default); term in months; fees paid on top (0); monthly the payment, when// the lender gives it; amount another amount for this offer; tag a badge// ("Pre-approved"); note a line at the bottom; href a link to take it (choose_label,// "Choose"). The total cost is the monthly payment times the term, plus the fees.// currency: ISO code ("USD"); locale: how amounts are written (the program's;// en-US). label: what screen readers call the list ("Loan offers"). id, class.//// loan_offers_cost(offer, amount) gives an offer's { monthly, interest, fees, total }.import ui/theme.qwe use ui_clslet loan_offers_css = css`.ui-loan-offers { margin: 0; padding: 0; list-style: none; display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 12px; min-width: 0; color: var(--ui-fg); font: 14px/1.45 var(--ui-font); }.ui-loan-offers-offer { position: relative; display: grid; grid-template-rows: auto auto 1fr auto; gap: 12px; height: 100%; box-sizing: border-box; padding: 18px 18px 16px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius); background: var(--ui-surface); }.ui-loan-offers-best { border-color: var(--ui-success); box-shadow: 0 0 0 1px var(--ui-success); }.ui-loan-offers-flag { position: absolute; top: -11px; left: 14px; padding: 2px 10px; border-radius: 999px; background: var(--ui-success); color: #fff; font-size: 11px; font-weight: 700; letter-spacing: .03em; }.ui-loan-offers-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }.ui-loan-offers-lender { margin: 0; font-size: 15px; font-weight: 650; line-height: 1.3; }.ui-loan-offers-tag { flex: none; padding: 1px 8px; border-radius: 999px; background: var(--ui-accent-soft); color: var(--ui-accent); font-size: 11px; font-weight: 600; }.ui-loan-offers-apr { margin: 0; font-size: 28px; font-weight: 700; letter-spacing: -.01em; line-height: 1.1; font-variant-numeric: tabular-nums; }.ui-loan-offers-apr small { margin-left: 4px; color: var(--ui-muted); font-size: 13px; font-weight: 500; letter-spacing: 0; }.ui-loan-offers-facts { display: grid; grid-template-columns: 1fr auto; gap: 7px 12px; margin: 0; align-content: start; }.ui-loan-offers-facts dt { color: var(--ui-muted); }.ui-loan-offers-facts dd { margin: 0; text-align: right; font-weight: 600; font-variant-numeric: tabular-nums; }.ui-loan-offers-facts .ui-loan-offers-total { padding-top: 8px; border-top: 1px solid var(--ui-border); color: var(--ui-fg); font-weight: 700; }.ui-loan-offers-best .ui-loan-offers-total { color: var(--ui-success); }.ui-loan-offers-foot { display: grid; gap: 8px; }.ui-loan-offers-diff { margin: 0; color: var(--ui-muted); font-size: 12px; font-variant-numeric: tabular-nums; }.ui-loan-offers-note { margin: 0; color: var(--ui-muted); font-size: 12px; }.ui-loan-offers-choose { display: inline-flex; align-items: center; justify-content: center; height: 34px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); background: var(--ui-surface); color: var(--ui-fg); font-weight: 600; text-decoration: none; }.ui-loan-offers-choose:hover { background: var(--ui-subtle); }.ui-loan-offers-best .ui-loan-offers-choose { border-color: transparent; background: var(--ui-accent); color: var(--ui-accent-fg); }.ui-loan-offers-best .ui-loan-offers-choose:hover { background: var(--ui-accent); filter: brightness(1.08); }.ui-loan-offers-choose:focus-visible { outline: none; box-shadow: var(--ui-focus); }`// x to the power n, for a whole n, exactly (pow takes no decimals).fn loan_offers_pow(x: dec, n: int) -> dec { let out = 1m let i = 0 while i < n { out = out * x i += 1 } out}// An offer's { monthly, interest, fees, total }, for amount unless it has its own.fn loan_offers_cost(offer: map, amount) -> map { let principal = dec(offer.amount ?? amount) let months = int(offer.term) let rate = dec(offer.rate ?? offer.apr) let monthly = if offer.monthly != null { dec(offer.monthly) } else if rate <= 0 { round(principal / months, 2, "ceiling") } else { let r = rate / 1200 let f = loan_offers_pow(1m + r, months) round(principal * r * f / (f - 1m), 2, "half_up") } let fees = dec(offer.fees ?? 0) let repaid = monthly * months { monthly, interest: repaid - principal, fees, total: repaid + fees }}fn loan_offers_money(x: dec, currency: str, locale: str?) -> str { let m = money.of(x, currency, "half_up") if is_err(m) { return str(x) } locale != null ? money.format(m, locale) : money.format(m)}// A term in months, as people say it: "60 months (5 years)".fn loan_offers_term(months: int) -> str = months % 12 == 0 && months >= 12 ? `${months} months (${int(months / 12)} ${months == 12 ? "year" : "years"})` : `${months} months`view LoanOffers(offers: list, amount = null, currency: str = "USD", locale: str? = null, choose_label: str = "Choose", label: str = "Loan offers", id: str? = null, class: str? = null) { style global loan_offers_css let costed = for o in offers => merge(o, { cost: loan_offers_cost(o, amount) }) let cheapest = len(costed) > 0 ? min(for o in costed => o.cost.total) : 0m let fmt = (x: dec) => loan_offers_money(x, currency, locale) <ul class={ui_cls(["ui-loan-offers", class])} id={id} aria-label={label}> {for [i, o] in enumerate(costed) { let best = len(costed) > 1 && o.cost.total == cheapest let heading = id != null ? `${id}-${i}` : null <li key={i}> <article class={ui_cls(["ui-loan-offers-offer", best && "ui-loan-offers-best"])} aria-labelledby={heading} data-best={best ? "true" : null}> {best && <span.ui-loan-offers-flag>Lowest total cost</span>} <div.ui-loan-offers-head> <h3.ui-loan-offers-lender id={heading}>{o.lender}</h3> {o.tag != null && <span.ui-loan-offers-tag>{o.tag}</span>} </div> <p.ui-loan-offers-apr data-key="apr">{dec(o.apr)}%<small>APR</small></p> <dl.ui-loan-offers-facts> <dt>Monthly</dt><dd data-key="monthly">{fmt(o.cost.monthly)}</dd> <dt>Term</dt><dd data-key="term">{loan_offers_term(int(o.term))}</dd> <dt>Fees</dt><dd data-key="fees">{o.cost.fees > 0 ? fmt(o.cost.fees) : "None"}</dd> <dt>Interest</dt><dd data-key="interest">{fmt(o.cost.interest)}</dd> <dt.ui-loan-offers-total>Total cost</dt><dd.ui-loan-offers-total data-key="total">{fmt(o.cost.total)}</dd> </dl> <div.ui-loan-offers-foot> {!best && len(costed) > 1 && <p.ui-loan-offers-diff data-key="diff">{fmt(o.cost.total - cheapest)} more than the cheapest</p>} {o.note != null && <p.ui-loan-offers-note>{o.note}</p>} {o.href != null && <a.ui-loan-offers-choose href={o.href}>{choose_label}<span.ui-sr> {o.lender}</span></a>} </div> </article> </li> }} </ul>}On LoanOffers: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.