Travel

CarRentalCard

A hire car offer: class and model or similar, seats, bags, gearbox and air conditioning, mileage and fuel policy, supplier rating, pick-up and the price a day and in all.

#travel#cars#card#commerce#no-js

Demo

Preview
Volkswagen GolfGreat value
Compact

Volkswagen Golf or similar

  • 5 seats
  • 1 large bag
  • 2 small bags
  • 5 doors
  • Manual
  • Air conditioning
Unlimited mileage · Fuel: Full to full
  • Free cancellation
  • Collision damage waiver
  • Theft protection
Pick-up: Lisbon Airport, in the terminal Desk in arrivals, open 06:00 to 00:00
Rentway 8.4
$34.50 / day$241.50 total for 7 daysDeposit $900.00
Tesla Model Y
Electric SUV

Tesla Model Y

  • 5 seats
  • 3 large bags
  • Automatic
  • Electric · 455 km
1,500 km included
Pick-up: Porto, Avenida dos Aliados Shuttle from the airport, 10 min
Volt Hire
89,00 € / day249,00 € total for 3 days
Economy

Fiat Panda or similar

$21.90 / day

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/car_rental_card.qwe
main.qwe
import ui/theme.qwe use theme_cssuse ui/car_rental_card.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>

Example

The code of the preview above.

demos/car_rental_card.qwe
// A drawing of a car in a colour, as a data: address, so the demo needs no network.fn car_rental_card_demo_car(color: str) -> str =    "data:image/svg+xml," + url_encode(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 90"><ellipse cx="100" cy="80" rx="90" ry="6" fill="#0002"/><path d="M14 62v-14l22-6 26-22h62l30 22 30 6v14z" fill="${color}"/><path d="M68 24h24v18H50zM98 24h24l22 18H98z" fill="#cfe3f5"/><circle cx="52" cy="66" r="14" fill="#222"/><circle cx="52" cy="66" r="6" fill="#bbb"/><circle cx="152" cy="66" r="14" fill="#222"/><circle cx="152" cy="66" r="6" fill="#bbb"/><rect x="176" y="50" width="10" height="5" rx="2" fill="#ffd27a"/></svg>`)view CarRentalCardDemo() =    <div.demo-stack>        <CarRentalCard id="car-rental-card-demo-golf" category="Compact" model="Volkswagen Golf" image={car_rental_card_demo_car("#3d7bd9")}                       seats={5} bags={1} small_bags={2} doors={5} transmission="manual" air_con={true} mileage="Unlimited mileage"                       fuel_policy="Full to full" supplier="Rentway" supplier_score={8.4} pickup="Lisbon Airport, in the terminal"                       pickup_note="Desk in arrivals, open 06:00 to 00:00" price={34.50m} days={7} deposit={900}                       included={["Free cancellation", "Collision damage waiver", "Theft protection"]} badge="Great value" href="#rentway-golf"/>        <CarRentalCard id="car-rental-card-demo-ev" category="Electric SUV" model="Tesla Model Y" similar={false} image={car_rental_card_demo_car("#c8323a")}                       seats={5} bags={3} transmission="automatic" electric={true} range="455 km" mileage="1,500 km included" supplier="Volt Hire"                       pickup="Porto, Avenida dos Aliados" pickup_note="Shuttle from the airport, 10 min" price={89} days={3} total={249.00m}                       currency="EUR" locale="de-DE"/>        <CarRentalCard id="car-rental-card-demo-min" category="Economy" model="Fiat Panda" price={21.90m}/>    </div>

Source

ui/car_rental_card.qwe
// CarRentalCard: a hire car in a list of offers: its class and an example model ("or// similar"), a picture, what it takes (seats, large and small bags, doors), the// gearbox, air conditioning, fuel or electric range, the mileage and fuel policy, the// supplier with its rating, where to pick it up, what is included, and the price a day// with the total for the hire.////   <CarRentalCard category="Compact" model="Volkswagen Golf" image="/cars/golf.png" seats={5} bags={2} small_bags={2}//                  doors={5} transmission="manual" air_con={true} mileage="Unlimited mileage" fuel_policy="Full to full"//                  supplier="Rentway" supplier_score={8.4} pickup="Lisbon Airport, in the terminal"//                  price={34.50m} days={7} included={["Free cancellation", "Theft protection"]} href="/cars/golf"/>//// category: the car's class ("Compact", "SUV", "Economy"). model: an example of it,// shown "Volkswagen Golf or similar" (similar: false for that exact car). image: a// picture of it (on a light ground; alt says the model). seats, doors: numbers; bags,// small_bags: suitcases and small bags that fit. transmission: "manual" or// "automatic". air_con: true or false (null says nothing). electric: true for an// electric car; range: its range ("420 km"). mileage: "Unlimited mileage" or "1,500 km// included". fuel_policy: "Full to full". supplier: who rents it; supplier_logo: its// logo; supplier_score: its rating out of 10. pickup: where it is picked up;// pickup_note: how ("Shuttle bus, 5 min"). included: what the price includes, a word// or two each (shown with ticks). price: a day, decimals best (numbers are read as// decimals); days: the hire's length (shows the total; total overrides it when the// total is not days times the daily price). deposit: the deposit held on a card.// currency: ISO code ("USD"); locale: how amounts are written ("en-US"). badge: a word// at the top ("Great value"). href: where View deal goes (the whole card can be// clicked). action_label ("View deal"). id, class.import ui/theme.qwe use ui_clslet car_rental_card_css = css`.ui-carrent { position: relative; display: grid; grid-template-columns: minmax(150px, 220px) minmax(0, 1fr) auto; min-width: 0; overflow: hidden;  border: 1px solid var(--ui-border); border-radius: var(--ui-radius); background: var(--ui-surface); color: var(--ui-fg); font: 14px/1.4 var(--ui-font);  transition: border-color .15s, box-shadow .15s; }.ui-carrent:hover { border-color: var(--ui-strong); box-shadow: var(--ui-shadow); }.ui-carrent:has(.ui-carrent-title a:focus-visible) { border-color: var(--ui-accent); box-shadow: var(--ui-focus); }.ui-carrent-media { position: relative; display: grid; place-items: center; padding: 16px; background: var(--ui-subtle); }.ui-carrent-media img { display: block; width: 100%; max-height: 130px; object-fit: contain; }.ui-carrent-media svg { width: 70%; color: var(--ui-strong); }.ui-carrent-badge { position: absolute; top: 10px; left: 10px; height: 22px; padding: 0 9px; border-radius: 999px; background: var(--ui-success);  color: var(--ui-surface); font: 600 11px/22px var(--ui-font); letter-spacing: .03em; text-transform: uppercase; }.ui-carrent-main { display: grid; gap: 10px; align-content: start; min-width: 0; padding: 14px 16px; }.ui-carrent-cat { color: var(--ui-muted); font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; }.ui-carrent .ui-carrent-title { margin: 0; font-size: 17px; font-weight: 650; line-height: 1.25; }.ui-carrent-title a { color: inherit; text-decoration: none; }.ui-carrent-title a::after { content: ""; position: absolute; inset: 0; }.ui-carrent-title a:hover { color: var(--ui-accent); }.ui-carrent-title a:focus-visible { outline: none; }.ui-carrent-similar { color: var(--ui-muted); font-size: 13px; font-weight: 400; }.ui-carrent-specs { display: flex; flex-wrap: wrap; gap: 6px 14px; margin: 0; padding: 0; list-style: none; font-size: 13px; }.ui-carrent-specs li { display: inline-flex; align-items: center; gap: 5px; }.ui-carrent-specs svg { flex: none; width: 16px; height: 16px; color: var(--ui-muted); }.ui-carrent-terms { color: var(--ui-muted); font-size: 12px; }.ui-carrent-incl { display: flex; flex-wrap: wrap; gap: 4px 12px; margin: 0; padding: 0; list-style: none; color: var(--ui-success); font-size: 12px; font-weight: 600; }.ui-carrent-incl li::before { content: "✓ "; }.ui-carrent-pickup { display: flex; gap: 6px; padding-top: 10px; border-top: 1px solid var(--ui-border); font-size: 13px; }.ui-carrent-pickup svg { flex: none; width: 16px; height: 16px; margin-top: 1px; color: var(--ui-muted); }.ui-carrent-pickup small { display: block; color: var(--ui-muted); font-size: 12px; }.ui-carrent-side { display: grid; align-content: space-between; justify-items: end; gap: 12px; min-width: 150px; padding: 14px 16px;  border-left: 1px solid var(--ui-border); text-align: right; font-variant-numeric: tabular-nums; }.ui-carrent-supplier { display: grid; justify-items: end; gap: 4px; font-size: 13px; font-weight: 600; }.ui-carrent-supplier img { max-width: 90px; max-height: 24px; object-fit: contain; }.ui-carrent-score { padding: 1px 6px; border-radius: 5px; background: var(--ui-accent); color: var(--ui-accent-fg); font-size: 12px; font-weight: 700; }.ui-carrent-price { display: grid; justify-items: end; gap: 1px; }.ui-carrent-day { font-size: 22px; font-weight: 700; line-height: 1.15; letter-spacing: -.01em; white-space: nowrap; }.ui-carrent-day small { color: var(--ui-muted); font-size: 12px; font-weight: 500; letter-spacing: 0; }.ui-carrent-total { font-size: 13px; font-weight: 600; }.ui-carrent-deposit { color: var(--ui-muted); font-size: 12px; }.ui-carrent-btn { position: relative; z-index: 1; display: inline-flex; align-items: center; height: 34px; margin-top: 6px; padding: 0 14px;  border-radius: var(--ui-radius-sm); background: var(--ui-accent); color: var(--ui-accent-fg); font-size: 13px; font-weight: 600; text-decoration: none; }.ui-carrent-btn:hover { filter: brightness(1.08); }@media (max-width: 680px) {  .ui-carrent { grid-template-columns: 1fr; }  .ui-carrent-side { grid-template-columns: 1fr auto; align-items: end; border-left: 0; border-top: 1px solid var(--ui-border); justify-items: start; text-align: left; }  .ui-carrent-supplier, .ui-carrent-price { justify-items: start; }}@media (prefers-reduced-motion: reduce) { .ui-carrent { transition: none; } }`let car_rental_card_icons = {    seats: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="7" r="3.5"/><path d="M5 21v-2a7 7 0 0 1 14 0v2"/></svg>,    bags: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="6" width="14" height="15" rx="2"/><path d="M9 6V3h6v3M9 10v7M15 10v7"/></svg>,    small: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="6" y="9" width="12" height="11" rx="2"/><path d="M9 9V6h6v3"/></svg>,    doors: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 21V9l6-6h10v18H4zM4 11h16M15 15h2"/></svg>,    gears: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="6" cy="5" r="2"/><circle cx="12" cy="5" r="2"/><circle cx="18" cy="5" r="2"/><circle cx="6" cy="19" r="2"/><circle cx="12" cy="19" r="2"/><path d="M6 7v10M12 7v10M18 7v5H6"/></svg>,    air: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 2v20M4.9 6.5l14.2 11M4.9 17.5l14.2-11M9 3.5l3 2.5 3-2.5M9 20.5l3-2.5 3 2.5"/></svg>,    bolt: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M13 2 4 14h7l-1 8 9-12h-7z"/></svg>,    pin: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 21s-7-6.2-7-12a7 7 0 0 1 14 0c0 5.8-7 12-7 12z"/><circle cx="12" cy="9" r="2.5"/></svg>,    car: <svg viewBox="0 0 64 32" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round" aria-hidden="true"><path d="M4 22v-5l6-2 8-8h20l10 8 10 2v5h-4M12 22H4M24 22h20"/><circle cx="18" cy="23" r="5"/><circle cx="50" cy="23" r="5"/></svg>,}// An amount in its currency ("$34.50", "241,50 €").fn car_rental_card_money(x: dec, currency: str, locale: str) -> str {    let m = money.of(x, currency, "half_up")    type_of(m) == "error" ? `${format_number(round(x, 2, "half_up"), locale, 2)} ${currency}` : money.format(m, locale)}// "5 seats", "1 seat": a count with its word.fn car_rental_card_count(n: int, one: str, many: str) -> str = `${n} ${n == 1 ? one : many}`view CarRentalCard(category: str, model: str, price, image: str? = null, similar: bool = true, seats: int? = null, bags: int? = null,                   small_bags: int? = null, doors: int? = null, transmission: str? = null, air_con: bool? = null, electric: bool = false,                   range: str? = null, mileage: str? = null, fuel_policy: str? = null, supplier: str? = null, supplier_logo: str? = null,                   supplier_score = null, pickup: str? = null, pickup_note: str? = null, included: list = [], days: int? = null,                   total = null, deposit = null, currency: str = "USD", locale: str = "en-US", badge: str? = null, href: str? = null,                   action_label: str = "View deal", id: str? = null, class: str? = null) {    style global car_rental_card_css    let day = dec(price)    let whole = total != null ? dec(total) : (days != null ? day * days : null)    let specs = [        seats != null && { icon: "seats", text: car_rental_card_count(seats, "seat", "seats"), key: "seats" },        bags != null && { icon: "bags", text: car_rental_card_count(bags, "large bag", "large bags"), key: "bags" },        small_bags != null && { icon: "small", text: car_rental_card_count(small_bags, "small bag", "small bags"), key: "small_bags" },        doors != null && { icon: "doors", text: car_rental_card_count(doors, "door", "doors"), key: "doors" },        transmission != null && { icon: "gears", text: transmission == "automatic" ? "Automatic" : "Manual", key: "transmission" },        air_con == true && { icon: "air", text: "Air conditioning", key: "air_con" },        electric && { icon: "bolt", text: range != null ? `Electric · ${range}` : "Electric", key: "electric" },    ] | filter((s) => s != false)    let name = similar ? `${model} or similar` : model    <article class={ui_cls(["ui-carrent", class])} id={id} aria-label={`${category}: ${name}`}>        <div.ui-carrent-media>            {image != null ? <img src={image} alt={model} loading="lazy"/> : car_rental_card_icons.car}            {badge != null && <span.ui-carrent-badge>{badge}</span>}        </div>        <div.ui-carrent-main>            <div>                <span.ui-carrent-cat>{category}</span>                <h3.ui-carrent-title>                    {href != null ? <a href={href}>{model}</a> : model}                    {similar && [" ", <span.ui-carrent-similar>or similar</span>]}                </h3>            </div>            {len(specs) > 0 && <ul.ui-carrent-specs>{for s in specs => <li data-spec={s.key}>{car_rental_card_icons[s.icon]}{s.text}</li>}</ul>}            {(mileage != null || fuel_policy != null) && <div.ui-carrent-terms>                {mileage != null && <span>{mileage}</span>}                {mileage != null && fuel_policy != null && " · "}                {fuel_policy != null && <span>Fuel: {fuel_policy}</span>}            </div>}            {len(included) > 0 && <ul.ui-carrent-incl aria-label="Included">{for x in included => <li>{x}</li>}</ul>}            {pickup != null && <div.ui-carrent-pickup>                {car_rental_card_icons.pin}                <span><span.ui-sr>Pick-up: </span>{pickup}{pickup_note != null && [" ", <small>{pickup_note}</small>]}</span>            </div>}        </div>        <div.ui-carrent-side>            {supplier != null ? <div.ui-carrent-supplier>                {supplier_logo != null ? <img src={supplier_logo} alt={supplier}/> : <span>{supplier}</span>}                {supplier_score != null && [" ", <span.ui-carrent-score aria-label={`Rated ${format_number(round(float(supplier_score), 1), locale, 1)} out of 10`}>                    {format_number(round(float(supplier_score), 1), locale, 1)}                </span>]}            </div> : <span></span>}            <div.ui-carrent-price>                <span.ui-carrent-day data-amount={str(day)}>{car_rental_card_money(day, currency, locale)} <small>/ day</small></span>                {whole != null && <span.ui-carrent-total data-amount={str(whole)}>                    {car_rental_card_money(whole, currency, locale)} total{days != null && ` for ${car_rental_card_count(days, "day", "days")}`}                </span>}                {deposit != null && <span.ui-carrent-deposit>Deposit {car_rental_card_money(dec(deposit), currency, locale)}</span>}                {href != null && <a.ui-carrent-btn href={href} tabindex="-1" aria-hidden="true">{action_label}</a>}            </div>        </div>    </article>}
Thread
1011Useful? Like it, or add to its thread below.

Thread 0 posts

On CarRentalCard: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.