Nexbrief documentation
Everything in this template is plain HTML, one stylesheet and a little vanilla JavaScript. There is no framework to learn and no build step you are required to run.
01 — Quick start
Unzip the package and open index.html in a browser. That file is the demo index; the actual site homepages are the home-*.html files.
Most pages work when opened directly from disk, but two features need a real HTTP origin: the archive filters and the article pages that read ?post= from the address bar. Run any static server from the template folder while you work:
python -m http.server 4321
# then open http://localhost:4321
To publish, upload the whole folder to any static host. There is nothing to compile and no server-side language involved.
What to change first
- Replace the wordmark. It is the
.brandanchor near the top of every page. - Swap
assets/images/favicon.svgfor your own icon. - Edit
assets/js/posts.js— that one file supplies every demo story. - Point the forms at your own endpoint (see section 07).
02 — Files and pages
nexbrief/
├── index.html demo index (the page you are selling from)
├── home-*.html 12 homepage demos
├── single-*.html 3 article layouts
├── category-*.html 2 archive layouts
├── tag.html search.html author.html
├── about.html contact.html newsletter.html tools.html 404.html
├── documentation/ this file, licences and image credits
└── assets/
├── css/style.css readable build
├── css/style.min.css the one the pages load
├── scss/ Sass sources (optional)
├── js/ dark-mode, main, posts, sliders
├── fonts/ self-hosted variable fonts
├── images/ demo photographs (CC0)
└── vendor/ Swiper (slider demo only)
The twelve homepages
| File | Demo | Accent | Layout |
|---|---|---|---|
| home-grid.html | Grid | red | Classic news grid |
| home-newspaper.html | Newspaper | ink | Broadsheet front page |
| home-magazine.html | Magazine | magenta | Mosaic feature grid |
| home-tech.html | Tech | violet | Gadgets and reviews |
| home-sports.html | Sports | green | Scores and standings |
| home-business.html | Business | blue | Markets and finance |
| home-video.html | Video | teal | Video and gallery |
| home-lifestyle.html | Lifestyle | amber | Fashion and culture |
| home-minimal.html | Minimal | ink | Text-first blog |
| home-slider.html | Slider | teal | Full-width carousel |
| home-list.html | Editorial | ink | Ranked story list |
| home-review.html | Review | amber | Scored reviews |
Shared page furniture
Every news page uses the same four blocks in the same order, so you can copy them between files without adjusting anything:
.utility-bar— date, secondary links, social icons, theme toggle..marquee-bar— the breaking-news ticker.header.mastheadorheader.masthead-center— wordmark and actions.nav.main-nav— categories plus the Demos and Pages dropdowns.
Add class="nav-solid" to the nav for a dark navigation band, as used by the newspaper, sports and video demos.
03 — Colours, accents and dark mode
All colour lives in CSS custom properties at the top of style.css. You never need to search the markup for a hex value.
Switching a demo's accent
Set data-accent on the <html> element. Every button, chip, rule, link hover and focus ring follows:
<html lang="en" data-bs-theme="light" data-accent="blue">
Included accents: red, ink, violet, blue, green, amber, magenta, teal. Each one is defined twice — once for light and once for dark — because a colour that is legible on white is rarely legible on near-black.
Adding your own accent
[data-accent="brand"] {
--primary: #7a2ff2; /* buttons, active states */
--primary-hover: #6725cf;
--primary-ink: #ffffff; /* text on top of primary */
--primary-soft: #7a2ff214; /* tinted backgrounds */
--accent: #0e7c66; /* secondary highlight */
--chip: #6725cf; /* category labels */
}
[data-bs-theme="dark"][data-accent="brand"] {
--primary: #a678ff; --primary-ink: #1b0733; --chip: #c9aaff;
}
Dark mode
The default theme is light. assets/js/dark-mode.js runs before the stylesheet paints, so there is no flash of the wrong theme. Precedence is: the visitor's saved choice, then the site default.
To follow the visitor's operating system setting instead, add one attribute to your pages:
<html lang="en" data-bs-theme="light" data-theme-follow-os="true">
To ship a dark-only site, set data-bs-theme="dark" on every page and delete the #theme-toggle button.
Note on dark panels. Blocks that are dark in both themes — the solid nav, .band-ink, .newsletter-box and the landing call to action — use --ink-panel rather than --ink. Using --ink there would invert them to white in dark mode.
04 — Typography
Two self-hosted variable fonts cover the whole template:
- Fraunces — headlines, the wordmark, pull quotes, numerals in data widgets.
- Inter Tight — body copy, metadata, labels and all user-interface text.
Because they are variable fonts, every weight from 100 to 900 comes from a single file. The four bundled files total roughly 240 KB, and the italics are only downloaded by pages that actually render italic text.
Sizes come from a fluid scale (--step--2 through --step-5) that interpolates between mobile and desktop, so there are no font-size media queries to maintain. To change the whole hierarchy at once, edit those tokens.
One size sits deliberately outside that scale. The navigation bar has to fit ten items between the drawer breakpoint and the container width, so it reads --nav-fs instead. Enlarging the editorial scale therefore never pushes the menu into overflow — adjust --nav-fs separately if you want larger menu labels, and check the bar at 1024px afterwards.
To use different families, replace the @font-face blocks and update two tokens:
--font-display: "Your Serif", Georgia, serif;
--font-sans: "Your Sans", system-ui, sans-serif;
05 — Stories, archives and search
assets/js/posts.js holds an array of plain objects. The three article layouts read from it, so one record drives every presentation of a story.
{
id: "small-models", // unique slug, used as ?post=<id>
title: "The next big thing in AI might be thinking smaller",
category: "Artificial Intelligence",
image: "ai-brain", // assets/images/ai-brain.webp
excerpt: "One-sentence standfirst.",
author: "Maya Chen",
minutes: 8,
tag: "AI models",
section: "Heading for the article's second block",
body: "Opening paragraph.",
detail: "Supporting paragraph.",
takeaway: "The highlighted takeaway box.",
date: "September 11, 2026",
url: "single-standard.html?post=small-models"
}
Point url at single-standard.html, single-fullwidth.html or single-review.html to choose the layout. The image field is a basename — the .webp extension is added for you.
How filtering works
Archive, tag and search pages render every card in the HTML and then hide the ones that do not match. That means the content is still readable with JavaScript disabled and is visible to crawlers. Filtering reads:
?category=oncategory-grid.htmlandcategory-list.html?tag=ontag.html?q=onsearch.html
Page size is set per page with data-page-size on #results.
If you move to a CMS, delete posts.js, render the cards server-side with the same class names, and every layout keeps working.
06 — Components
Marquee
The ticker holds its items twice: once normally and once marked data-clone aria-hidden="true" tabindex="-1". The animation translates the track by -50%, so the duplicate makes the loop seamless.
Spacing is applied as a margin-left on each item, never as a flex gap. A gap would make the two halves unequal and produce a visible jump at the seam.
It pauses on hover and on keyboard focus. Under prefers-reduced-motion the animation stops, the clone is hidden and the strip becomes horizontally scrollable.
Variants: .marquee-vert for the sidebar live feed, and .ticker-tape for the market prices on the business demo.
Demo switcher
The Demos dropdown is a native <details> element containing a grid of screenshots. When you remove demos you do not need, delete the matching .demo-tile anchors and their images in assets/images/demos/. Add class="is-current" to mark the page you are on.
Scroll reveal
Cards, tiles and section blocks fade up as they enter the viewport. It is
about 25 lines in main.js built on IntersectionObserver
— there is no AOS or other animation library to load.
Three rules keep it safe:
- Nothing is hidden by CSS alone. The script adds
.reveal-onto<html>before anything fades, so a visitor with JavaScript disabled — or a search engine — sees the page fully rendered. - Above-the-fold content never animates. Only elements that start below the first screen are observed; a front page should not fade its own headlines in.
prefers-reduced-motionswitches it off entirely, including if the visitor changes the setting mid-session.
Add data-reveal to any element to opt it in. To change what
animates by default, edit the REVEAL_DEFAULTS selector list at the
top of the block. Siblings are staggered by 70ms up to a maximum of four steps;
change --reveal-delay to adjust.
To remove the effect completely, delete that block from main.js.
No CSS change is needed — the rules only apply under .reveal-on.
Dropdown menus
Both dropdowns are native <details class="has-menu"> elements.
The panel inside is .mega, positioned against
.main-nav so it spans the full width of the bar.
.has-menu must stay position: static. Giving it
position: relative makes it the containing block, and the panel
collapses to the width of the summary button.
Below 860px the panel becomes part of the mobile drawer: it switches to
position: static and the demo picker drops to two columns.
Cards
| Class | Use |
|---|---|
.post | Base vertical card |
.post-list | Horizontal card with a side thumbnail |
.post-mini | Compact sidebar card, square thumbnail |
.overlay-card | Headline on top of the image; add .overlay-tall or .overlay-xl |
.hero-main / .hero-small | Lead and secondary stories in .hero-grid |
Documentation layout
This page uses .doc-shell: a sticky section index
(.doc-nav) beside the prose (.doc-body). The index
never covers the text because it sits in its own column, and it scrolls
internally if you add more sections than fit the screen.
A small script in main.js marks the section you are reading with
.is-current, driven by an IntersectionObserver. Add or
remove links freely — the script reads whatever is in the index and matches it
to the matching heading id.
Below 1024px the index becomes a static two-column box above the content, so nothing is pinned on a small screen.
Sticky sidebar
In a two-column section the <aside class="sidebar"> is a grid
item, so it stretches to the height of the whole row. The last widget in
it is position: sticky, which means it scrolls normally until it
reaches the top of the viewport and then follows the reader down the page.
Pinning the last widget rather than the whole column is deliberate: a full sidebar is usually taller than the viewport, and pinning all of it would put its lower half permanently out of reach.
For this to work the main column must be taller than the sidebar — that is what gives the pinned widget room to travel. If you delete stories and the main column becomes the shorter of the two, the sticky effect simply stops and the layout still reads correctly.
The offset is --stick-top (the navigation height plus a gap).
Sticky is switched off below 1200px, where the sidebar sits under the article.
To pin a group of widgets instead, wrap them in .sticky-side — only
do that when the group is shorter than the viewport.
Sparkline
The trend widget is a plain inline <svg> with two paths —
a filled area and a stroked line — coloured by --accent. To plot
your own numbers, replace the d attributes. No script runs.
Section blocks
.paper-front (ruled newspaper columns), .mosaic and .mosaic-5 (bento feature grids), .cat-tiles, .gallery-grid, .score-strip, .standings, .market-table, .timeline, .quote-band, .stat-band, .opinion-row.
Advertising slots
.ad-leader (728×90), .ad-side (300×250), .ad-side-tall (300×600), .ad-billboard and .ad-inline. They are dashed placeholders — drop your ad code inside and remove the placeholder text.
Utility classes
A short list of spacing and flex helpers lives at the end of
_base.scss: .mt-1–.mt-5,
.mb-0/3/4, .pt-4, .d-flex,
.d-grid, .d-block, .gap-2/3,
.align-items-start, .align-items-center,
.justify-between, .flex-wrap, .fs-4,
.text-center and .w-100. They exist so the markup
stays readable without pulling in a CSS framework.
07 — Forms and integration
Every form carries class="demo-form" and a data-form-kind of newsletter, contact or comment. The demo handler validates the fields and prints a confirmation. Nothing is transmitted or stored.
To connect a form, remove demo-form and add a real action:
<form action="https://your-provider.example/subscribe" method="post">
<label class="form-label" for="email">Email address</label>
<input class="form-input" id="email" type="email" name="email"
autocomplete="email" required>
<button class="btn-n btn-primary-n" type="submit">Subscribe</button>
</form>
Keep the .form-label, .form-input and .btn-n classes and the styling carries over. Remember to delete the "demo form" notes once a form is live.
08 — Reader intelligence
assets/js/ai.js adds the features shown on the AI Newsroom demo and on every article page: a three-line brief, read-aloud, related stories matched by content, and a feed that reorders itself around what the reader opens.
All of it runs in the browser. There is no API key, no account, no subscription and no network request. The summariser, the similarity ranking and the personalised feed are computed from posts.js on the reader's own machine, and the read-aloud uses the speech engine already built into the browser.
Why there is no API key field
A key placed in client-side JavaScript is public. Anyone can open the developer tools, read it, and spend your quota. Any template that tells you to paste a provider key into a .js file is handing your key to every visitor. This one does not ask you to do that, and you should be wary of any that does.
What each feature actually does
| Feature | Method |
|---|---|
| The brief | Extractive summarisation. Sentences are scored on the frequency of their meaningful words, normalised for length, with a bonus for the opening sentence and for overlap with the headline. The best three are returned in their original order. Because every line is one of the author's own sentences, the summary cannot invent a fact that is not in the article. |
| Read aloud | The browser's speechSynthesis API, one sentence per utterance. Chunking is what keeps it reliable: a single long utterance stops after about fifteen seconds in some browsers, and per-sentence playback is also what lets the spoken line be highlighted. |
| Related stories | TF-IDF vectors over every record in posts.js, compared by cosine similarity. "Related" therefore means "shares unusual vocabulary with this story" rather than "sits in the same category", and each card shows the words the two have in common. |
| Picked for you | Each opened story is recorded in localStorage with its category and tag; the feed is then ranked by affinity, weighted so a recent read counts for more. Nothing is transmitted, and clearing site data clears it. |
| Ask this article | Finds the sentences that best answer the question. If nothing matches, it returns the summary rather than claiming the subject is absent. Suggested questions are generated from the article's own distinctive vocabulary, so a suggestion always has an answer. |
Using it on your own pages
Load ai.js before main.js; the wiring in main.js checks for each block of markup before touching it, so nothing breaks on pages that have neither.
<script src="assets/js/ai.js" defer></script>
<script src="assets/js/posts.js" defer></script>
<script src="assets/js/main.js" defer></script>
The library is also usable directly:
NEXBRIEF_AI.summarise(text, { count: 3, title: headline }); // -> [sentence, ...]
NEXBRIEF_AI.related(post, NEXBRIEF_POSTS, 4); // -> [{post, score, strength, terms}, ...]
NEXBRIEF_AI.rank(NEXBRIEF_POSTS, "serach term"); // typo-tolerant, ranked
NEXBRIEF_AI.forYou(NEXBRIEF_POSTS, 4); // -> [post, ...]
NEXBRIEF_AI.topics(text, 5); // distinctive words
const reader = NEXBRIEF_AI.reader(); // read-aloud instance
The article markup uses .ai-brief for the panel, #ai-points for the summary list, #ai-play and #ai-rate for the reader, #ai-ask-form for the question box and #related-grid for the related row. Add #foryou-grid and #foryou-note to any page to get the personalised rail.
By default the prose is taken from the direct <p> children of .article-body, which excludes the share row, the sponsored block and the comment form. On a layout of your own, mark your prose with data-prose and that selection is used instead.
Connecting a real model
"Ask this article" is the one feature a language model would genuinely improve. Point it at an endpoint on your own server, which holds the provider key and never exposes it:
NEXBRIEF_AI.configure({
endpoint: "/api/ask", // your route, not the provider's
headers: { "X-CSRF": token }, // optional
timeout: 20000,
});
The endpoint receives { question, context } as JSON and should reply with { answer }. If it is unreachable or slow, the page falls back to the article text and says so, so a failed request never leaves the reader with a blank box. Keep the rate limiting and the key on the server side.
Turning it off
Remove the ai.js script tag. The brief, the reader and the related row disappear; nothing else changes, because the wiring checks for its markup before running. To remove the markup as well, delete the .ai-brief and #related-grid sections from the three article layouts.
Other languages
The only English-specific thing in ai.js is the stop-word list at the top and the abbreviation list in the sentence splitter. Replace both with your language's equivalents; everything below them is frequency arithmetic. The read-aloud picks a voice from <html lang>, so set that correctly and it follows.
09 — Editing the Sass sources
You can edit assets/css/style.css directly and ignore Sass entirely. If you prefer the sources, they are in assets/scss/:
| Partial | Contains |
|---|---|
_ai.scss | The brief, read-aloud bar, ask panel, related grid and personalised rail |
_tokens.scss | Fonts, palettes, accents, spacing scale, radii |
_base.scss | Reset, typography, layout helpers, buttons |
_navigation.scss | Utility bar, marquee, masthead, nav, footer |
_cards.scss | Story cards and hero grids |
_widgets.scss | Sidebar widgets, forms, newsletter, ad slots |
_blocks.scss | Section layouts specific to each demo |
_pages.scss | Article, about, contact, 404, tools, docs |
_landing.scss | The demo index page |
_responsive.scss | All breakpoints, in one file |
npx sass assets/scss/style.scss assets/css/style.css --style=expanded --no-source-map
npx sass assets/scss/style.scss assets/css/style.min.css --style=compressed --no-source-map
10 — Credits and licensing
Third-party code
One library, loaded by one page:
- Swiper — MIT. Loaded only by
home-slider.html. If you do not use the slider demo, deleteassets/vendor/entirely.
There is no CSS framework, no charting library and no jQuery. The tool
directory's dialog is a native <dialog>, so the browser
supplies the backdrop, focus trapping and Escape-to-close. The sidebar trend
widget is an inline SVG sparkline you can edit by hand.
Full licence texts are in documentation/licenses/.
Fonts
Fraunces and Inter Tight are licensed under the SIL Open Font License 1.1 and may be redistributed with this template. Licence texts are in documentation/licenses/.
Photographs
Every bundled photograph is released under the Creative Commons Zero (CC0 1.0) public domain dedication, so no attribution is required and commercial use is permitted. documentation/image-credits.txt lists the source of each file for transparency.
If you replace them with your own photographs, you may delete that file.
Demo content
All stories, bylines, product names, scores, market prices and sports results in this template are fictional and exist only to demonstrate the layouts. Replace them before publishing.
11 — Launch checklist
- Replace the wordmark, favicon and the
<title>and<meta name="description">of every page. - Replace every story in
posts.jsand delete the demo cards you do not need. - Connect the newsletter, contact and comment forms; remove the demo notes.
- Replace the advertising placeholders or delete the slots.
- Swap the demo photographs and write real
alttext for each one. - Delete the homepage demos you are not using, and their thumbnails in
assets/images/demos/. - Update the copyright line in the footer.
- Re-check colour contrast if you changed the accent.
- Test with a keyboard: the skip link, the navigation drawer and both dropdowns.
Changelog
- 2.0 — Eight new homepage demos (newspaper, magazine, tech, sports, business, video, lifestyle, minimal). New demo index at
index.html; the original grid homepage moved tohome-grid.html. New typography (Fraunces and Inter Tight). Eight accent themes. Breaking-news marquee, market tape and demo switcher. Light theme is now the default. 54 CC0 photographs replace the previous three. - 2.2 — New AI Newsroom demo (thirteen homepages). New
assets/js/ai.js: in-browser article summaries, read-aloud through the browser's speech engine, related stories ranked by content similarity, typo-tolerant ranked search and a personalised feed — no API key and no backend. Every bundled photograph re-sourced from StockSnap. - 2.1 — Larger editorial type scale (17px body, 19px card headlines). The split hero’s stacked column no longer distributes leftover height between its cards, and the sports and tech demos carry enough stories to fill it. Ten more sports articles and ten more photographs; a “Coming up” fixture widget and a “By sport” section row on the sports demo.
- 1.0 — First release: four homepages, three article layouts, archive, tag, search, author and utility pages.
Thanks for choosing Nexbrief. If something here is unclear, that is a documentation bug — please get in touch so it can be fixed.