Economy
Central bank holds rates as inflation cools for a fourth month
Policymakers kept the benchmark at 4.25% and signalled that cuts could come early next year if prices keep easing.
A section's front: the lead story large with its picture and dek, the next stories beside it, and a column of more headlines; one column where narrow.
Economy
Policymakers kept the benchmark at 4.25% and signalled that cuts could come early next year if prices keep easing.
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/section_front.qweimport ui/theme.qwe use theme_cssuse ui/section_front.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>The code of the preview above.
// A picture drawn here, so the demo needs no network: a sky of `a` over hills of `b`.fn section_front_demo_pic(a: str, b: str) -> str = "data:image/svg+xml," + url_encode(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 200"><defs><linearGradient id="g" x2="0" y2="1"><stop offset="0" stop-color="${a}"/><stop offset="1" stop-color="#fff"/></linearGradient></defs><rect width="300" height="200" fill="url(#g)"/><circle cx="220" cy="62" r="24" fill="#fff" opacity=".8"/><path d="M0 150 Q70 100 140 140 T300 120 V200 H0z" fill="${b}"/><path d="M0 175 Q90 140 180 170 T300 160 V200 H0z" fill="${b}" opacity=".7"/></svg>`)view SectionFrontDemo() = <SectionFront id="section-front-demo" title="Business" href="#business" more_href="#business-all" lead={{ headline: "Central bank holds rates as inflation cools for a fourth month", href: "#rates", kicker: "Economy", dek: "Policymakers kept the benchmark at 4.25% and signalled that cuts could come early next year if prices keep easing.", src: section_front_demo_pic("#7aa7d9", "#2f5d50"), alt: "The central bank's building at dusk", credit: "Photo: Lena Ortiz for The Ledger", time: "1 h ago", datetime: "2026-10-30T13:05:00Z", author: "Ada Lovelace" }} stories={[ { headline: "Ports clear the last of the summer backlog", href: "#ports", kicker: "Trade", label: "Analysis", dek: "Container waits are back under two days.", src: section_front_demo_pic("#e0b07a", "#4a5a7a"), alt: "Cranes at the container port", time: "3 h ago" }, { headline: "Markets: shares edge up as oil slips", href: "#markets", label: "Live", time: "Updated 4 min ago" }, { headline: "The four-day week, a year on", href: "#four-day", kicker: "Work", time: "Yesterday" }, ]} more={[ { headline: "Shipping costs fall for a sixth week", href: "#shipping", time: "5 h ago" }, { headline: "Chipmaker to build second plant in the north", href: "#chips", time: "6 h ago" }, { headline: "What the new pension rules mean for you", href: "#pensions", label: "Explainer" }, { headline: "Airline strike called off after late deal", href: "#strike", time: "Yesterday" }, ]}/>// SectionFront: the front of a section of a news site: its name (a link to all of// it), the lead story large with its picture, headline and dek, the next stories in// a grid under or beside it, and a column of more headlines. It narrows to one// column where there is little room (it measures its own width, not the window's).//// <SectionFront title="Business" href="/business"// lead={{ headline: "Rates hold as inflation cools", href: "/b/rates", kicker: "Economy",// dek: "The central bank kept its rate at 4.25% for a third month.", src: "/img/bank.jpg", alt: "The bank's facade",// time: "1 h ago", datetime: "2026-10-30T13:00:00Z", author: "Ada Lovelace" }}// stories={[{ headline: "Ports clear the backlog", href: "/b/ports", src: "/img/port.jpg", alt: "", time: "3 h ago" }, ...]}// more={[{ headline: "Shipping costs fall again", href: "/b/ship", time: "5 h ago" }, ...]}/>//// title: the section's name, an <h2>, a link to href when given. lead: the top story;// stories: the ones after it (two to four read best); each is a map of { headline,// href, kicker?, dek?, src?, alt?, credit?, time?, datetime?, author?, label? }.// headline links to href, and the whole story can be clicked; kicker: its topic, over// the headline; dek: a line under it (the lead's always shows; a story's only with// show_deks); src and alt: its picture (credit, "Photo: AP", under the lead's);// time: when, as it should read ("2 h ago"), with datetime its ISO form; author:// "By ..." after it; label: a tag before the kicker, "Live" (red, with a pulsing dot),// "Analysis", "Opinion", "Exclusive". more: headlines only, maps of { headline, href,// time?, datetime?, label? }, under more_title ("More in <title>"); more_href and// more_link ("See all") end the column. show_deks: stories show their deks too.// id: the section's id. Story headlines are <h3>, so the page's outline reads right.import ui/theme.qwe use ui_clslet section_front_css = css`.ui-secfr { container-type: inline-size; min-width: 0; color: var(--ui-fg); font: 15px/1.45 var(--ui-font); }.ui-secfr-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin: 0 0 16px; padding-top: 10px; border-top: 3px solid var(--ui-fg); }.ui-secfr .ui-secfr-title { margin: 0; font-size: 22px; font-weight: 800; letter-spacing: -.01em; }.ui-secfr-title a { color: inherit; text-decoration: none; }.ui-secfr-title a::after { content: " ›"; color: var(--ui-accent); }.ui-secfr-title a:hover { color: var(--ui-accent); }.ui-secfr a:focus-visible { outline: none; box-shadow: var(--ui-focus); border-radius: 3px; }.ui-secfr-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 20px; }.ui-secfr-story { position: relative; display: grid; gap: 6px; align-content: start; min-width: 0; margin: 0; }.ui-secfr-pic { margin: 0 0 4px; }.ui-secfr-pic img { display: block; width: 100%; aspect-ratio: 3 / 2; object-fit: cover; border-radius: var(--ui-radius-sm); background: var(--ui-subtle); }.ui-secfr-credit { display: block; margin-top: 4px; color: var(--ui-muted); font-size: 11px; text-align: right; }.ui-secfr-top { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; margin: 0; font-size: 12px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--ui-accent); }.ui-secfr-label { color: var(--ui-muted); }.ui-secfr-live { display: inline-flex; align-items: center; gap: 5px; color: var(--ui-danger); }.ui-secfr-live::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor; animation: ui-secfr-pulse 1.6s ease-in-out infinite; }@keyframes ui-secfr-pulse { 50% { opacity: .25; } }.ui-secfr .ui-secfr-hl { margin: 0; font-size: 17px; line-height: 1.25; font-weight: 700; text-wrap: balance; }.ui-secfr-hl a { color: inherit; text-decoration: none; }.ui-secfr-hl a::after { content: ""; position: absolute; inset: 0; }.ui-secfr-story:hover .ui-secfr-hl a { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px; }.ui-secfr-dek { margin: 0; color: var(--ui-muted); font-size: 14px; }.ui-secfr-meta { margin: 0; color: var(--ui-muted); font-size: 12px; }.ui-secfr-meta > * + *::before { content: "·"; margin: 0 6px; }.ui-secfr-lead .ui-secfr-hl { font-size: clamp(22px, 3.4cqi, 30px); line-height: 1.15; font-weight: 800; }.ui-secfr-lead .ui-secfr-dek { font-size: 16px; }.ui-secfr-rest { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 20px; }.ui-secfr-rest .ui-secfr-story + .ui-secfr-story { border: 0; }.ui-secfr-more { min-width: 0; }.ui-secfr-more h3 { margin: 0 0 4px; font-size: 13px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--ui-muted); }.ui-secfr-list { margin: 0; padding: 0; list-style: none; }.ui-secfr-list li { position: relative; padding: 10px 0; border-bottom: 1px solid var(--ui-border); }.ui-secfr-list a { color: var(--ui-fg); font-weight: 600; text-decoration: none; line-height: 1.3; }.ui-secfr-list a::after { content: ""; position: absolute; inset: 0; }.ui-secfr-list li:hover a { color: var(--ui-accent); }.ui-secfr-list .ui-secfr-meta { margin-top: 2px; }.ui-secfr-all { display: inline-block; margin-top: 10px; color: var(--ui-accent); font-size: 14px; font-weight: 600; text-decoration: none; }.ui-secfr-all:hover { text-decoration: underline; }@container (min-width: 640px) { .ui-secfr-grid { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); } .ui-secfr-lead { grid-row: span 2; } .ui-secfr-grid:not(:has(.ui-secfr-more)) { grid-template-columns: minmax(0, 1fr); } .ui-secfr-rest { grid-column: 1; } .ui-secfr-more { grid-column: 2; grid-row: 1 / span 2; padding-left: 20px; border-left: 1px solid var(--ui-border); }}@container (min-width: 900px) { .ui-secfr-grid:has(.ui-secfr-more) { grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr) minmax(0, .9fr); } .ui-secfr-grid:has(.ui-secfr-more) .ui-secfr-lead { grid-row: auto; } .ui-secfr-grid:has(.ui-secfr-more) .ui-secfr-rest { grid-column: 2; grid-template-columns: minmax(0, 1fr); align-content: start; } .ui-secfr-grid:has(.ui-secfr-more) .ui-secfr-rest .ui-secfr-story + .ui-secfr-story { padding-top: 16px; border-top: 1px solid var(--ui-border); } .ui-secfr-grid:has(.ui-secfr-more) .ui-secfr-rest .ui-secfr-pic { display: none; } .ui-secfr-grid:has(.ui-secfr-more) .ui-secfr-rest .ui-secfr-story:first-child .ui-secfr-pic { display: block; } .ui-secfr-more { grid-column: 3; grid-row: 1; }}@media (prefers-reduced-motion: reduce) { .ui-secfr-live::before { animation: none; } }`// A story's label: "Live" in red with a dot, others muted.fn section_front_label(label: str?) = label == null ? null : (contains(["live", "breaking"], lower(label)) ? <span.ui-secfr-live>{label}</span> : <span.ui-secfr-label>{label}</span>)// The line of when and who: "1 h ago · By Ada Lovelace".fn section_front_meta(s) = s.time == null && s.author == null ? null : <p.ui-secfr-meta> {s.time != null && <time datetime={s.datetime}>{s.time}</time>} {s.author != null && <span>By {s.author}</span>} </p>view SectionFront(title: str, lead, stories: list = [], more: list = [], href: str? = null, more_title: str? = null, more_href: str? = null, more_link: str = "See all", show_deks: bool = false, id: str? = null, class: str? = null) { style global section_front_css let story = (s, big: bool) => <article class={ui_cls(["ui-secfr-story", big && "ui-secfr-lead"])}> {s.src != null && <figure.ui-secfr-pic> <img src={s.src} alt={s.alt ?? ""} loading={big ? "eager" : "lazy"}/> {big && s.credit != null && <figcaption.ui-secfr-credit>{s.credit}</figcaption>} </figure>} {(s.kicker != null || s.label != null) && <p.ui-secfr-top>{section_front_label(s.label)}{s.kicker != null && <span>{s.kicker}</span>}</p>} <h3.ui-secfr-hl><a href={s.href}>{s.headline}</a></h3> {s.dek != null && (big || show_deks) && <p.ui-secfr-dek>{s.dek}</p>} {section_front_meta(s)} </article> <section class={ui_cls(["ui-secfr", class])} id={id} aria-labelledby={id != null ? `${id}-title` : null} aria-label={id == null ? title : null}> <div.ui-secfr-head> <h2.ui-secfr-title id={id != null ? `${id}-title` : null}>{href != null ? <a href={href}>{title}</a> : title}</h2> </div> <div.ui-secfr-grid> {story(lead, true)} {len(stories) > 0 && <div.ui-secfr-rest>{for s in stories => story(s, false)}</div>} {len(more) > 0 && <div.ui-secfr-more> <h3>{more_title ?? `More in ${title}`}</h3> <ul.ui-secfr-list> {for m in more => <li> {m.label != null && <p.ui-secfr-top>{section_front_label(m.label)}</p>} <a href={m.href}>{m.headline}</a> {section_front_meta(m)} </li>} </ul> {more_href != null && <a.ui-secfr-all href={more_href}>{more_link} →</a>} </div>} </div> </section>}On SectionFront: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.