Version 2.0

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 .brand anchor near the top of every page.
  • Swap assets/images/favicon.svg for 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

FileDemoAccentLayout
home-grid.htmlGridredClassic news grid
home-newspaper.htmlNewspaperinkBroadsheet front page
home-magazine.htmlMagazinemagentaMosaic feature grid
home-tech.htmlTechvioletGadgets and reviews
home-sports.htmlSportsgreenScores and standings
home-business.htmlBusinessblueMarkets and finance
home-video.htmlVideotealVideo and gallery
home-lifestyle.htmlLifestyleamberFashion and culture
home-minimal.htmlMinimalinkText-first blog
home-slider.htmlSlidertealFull-width carousel
home-list.htmlEditorialinkRanked story list
home-review.htmlReviewamberScored 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:

  1. .utility-bar — date, secondary links, social icons, theme toggle.
  2. .marquee-bar — the breaking-news ticker.
  3. header.masthead or header.masthead-center — wordmark and actions.
  4. 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= on category-grid.html and category-list.html
  • ?tag= on tag.html
  • ?q= on search.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-on to <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-motion switches 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

ClassUse
.postBase vertical card
.post-listHorizontal card with a side thumbnail
.post-miniCompact sidebar card, square thumbnail
.overlay-cardHeadline on top of the image; add .overlay-tall or .overlay-xl
.hero-main / .hero-smallLead 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

FeatureMethod
The briefExtractive 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 aloudThe 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 storiesTF-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 youEach 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 articleFinds 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/:

PartialContains
_ai.scssThe brief, read-aloud bar, ask panel, related grid and personalised rail
_tokens.scssFonts, palettes, accents, spacing scale, radii
_base.scssReset, typography, layout helpers, buttons
_navigation.scssUtility bar, marquee, masthead, nav, footer
_cards.scssStory cards and hero grids
_widgets.scssSidebar widgets, forms, newsletter, ad slots
_blocks.scssSection layouts specific to each demo
_pages.scssArticle, about, contact, 404, tools, docs
_landing.scssThe demo index page
_responsive.scssAll 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, delete assets/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.js and 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 alt text 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 to home-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.