Without a front page of its own, a site opens on a generated overview: the
newest notes, the top-level tags, and the views. This guide turns one note
into the front page instead, with a banner, a short introduction, and links
into the site, and keeps it an ordinary note you edit like any other. It
continues the harbor theme of
Writing a site theme, so do that first or use a theme of your
own made with ntropy site theme init.
Step 1: The front page note
Write the note that becomes the front page. Its body is the introduction,
in ordinary Markdown. ntropy new --empty -p creates an empty note and
prints its path, and ntropy write puts the whole note into it,
frontmatter first:
note=$(ntropy new --empty -p "Welcome")
ntropy write "$note" <<'NOTE'
---
title: Welcome
tags: [harbor]
---
This site keeps the harbor office's notes: who is moored where, what the
tide does this week, and what happened on the water.
NOTE
Name the note in .ntropy/config.toml by its id, the 26 characters before
the first - of its file name, which ntropy search -n title:Welcome also shows
in its ID column:
[site]
theme = "harbor"
title = "Harbor"
index = "01M6CBRQM0Z3ZPK7BGZKWZ29QN"
Export and open the site as in the theme guide: the front page is the
Welcome note, with its title, date, and tags, the breadcrumbs above it,
and the related notes below. title is the site's name in the header.
Step 2: A banner from the frontmatter
The note picks a template of its own with template in the site table of
its frontmatter, and the template can read any other field of it. Add both
to the note's frontmatter, which ntropy search <id> opens in your
editor:
---
title: Welcome
tags: [harbor]
tagline: Tide tables, moorings and the log of a small harbor
site:
template: front
---
Create templates/front.html in the theme:
{% extends "page.html" %}
{% block body_class %}{{ super() }} front{% endblock %}
{% block before_content %}<section class="hero">
<h1 class="hero-title">{{ site_title }}</h1>
{% if note and note.frontmatter.tagline %}<p class="hero-tagline">{{ note.frontmatter.tagline }}</p>{% endif %}
</section>
{% endblock %}
It extends your theme's page.html, so the header links and the footer of
the theme guide come along, and fills its before_content block with the
banner. {{ super() }} front adds the class front to body and keeps the
site or document the stylesheet's layout depends on. The banner shows
the site's title, so the note's own header goes. Add the CSS at the end of
style.css:
.front .note-header {
display: none;
}
.hero {
background: var(--accent);
border-radius: var(--radius-panel);
color: var(--bg);
margin-bottom: var(--section-gap);
padding: 3rem 2rem;
}
.hero-title {
color: inherit;
font-family: var(--display);
font-size: 3rem;
margin: 0;
}
.hero-tagline {
font-size: 1.15rem;
margin: 0.5rem 0 1.5rem;
}
Export and reload: the front page opens with a teal banner holding the site's name and the tagline, and the introduction follows.
Step 3: Links into the site
A front page sends the reader on. Harbor does it twice: buttons in the banner, to pages of the site, and cards below the introduction, to notes. The buttons come from a list in the frontmatter:
---
title: Welcome
tags: [harbor]
tagline: Tide tables, moorings and the log of a small harbor
actions:
- { label: All notes by tag, href: tags/index.html }
- { label: The harbor log, href: tags/harbor/log/index.html }
site:
template: front
---
A button's href is a path in the site: a tag's page is
tags/<tag>/index.html, a note's page notes/<slug>.html, and after an
export the output directory shows the path of any page. Replace
templates/front.html with the version that draws them:
{% extends "page.html" %}
{% block body_class %}{{ super() }} front{% endblock %}
{% block before_content %}<section class="hero">
<h1 class="hero-title">{{ site_title }}</h1>
{% if note and note.frontmatter.tagline %}<p class="hero-tagline">{{ note.frontmatter.tagline }}</p>{% endif %}
{% if kind != "document" and note and note.frontmatter.actions %}<p class="hero-actions">
{% for action in note.frontmatter.actions %}<a href="{{ prefix }}{{ action.href }}">{{ action.label }}</a>
{% endfor %}</p>{% endif %}
</section>
{% endblock %}
prefix reaches the site's root from the page, and kind != "document"
leaves the buttons out of a render --to html page of the note, which has
no site around it. The cards are a list of note links at the end of the
note's body; pick two notes from ntropy search -n and link them by file
name:
## Start here
- [Harbor log](01M6CBNRGEPMEQ70EV0XR1KNBC-harbor-log.md): the week on the water, with tide tables.
- [Moorings](01M6CBNRJ39E2MPBRHBBCNT1M7-moorings.md): twelve berths on two piers, and who has them.
Then the CSS for both:
.hero-actions {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin: 0;
}
.hero-actions a {
border: 1px solid currentColor;
border-radius: var(--radius-control);
color: inherit;
font-family: var(--sans);
padding: 0.5rem 1rem;
text-decoration: none;
}
.hero-actions a:hover {
background: var(--bg);
color: var(--accent);
}
.front .note-body > ul {
display: grid;
gap: 1rem;
grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
list-style: none;
padding: 0;
}
.front .note-body > ul > li {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-block);
padding: 1rem 1.25rem;
}
Export and reload: two buttons in the banner, and the two notes as cards side by side, one above the other on a narrow screen.
Note
ntropy reconcile keeps the cards' links pointing at their notes when a
title changes, and a link to a note the export leaves out is a warning.
A link to a note that no longer exists becomes plain text without one,
and the buttons' paths are never checked: click every button and card
after an export.
Step 4: Room for the page
The front page needs no breadcrumbs and no outline, and its banner wants the width the outline took. This CSS hides both and lets the content take the rest of the row on a wide screen:
.site.front .breadcrumbs,
.site.front .side {
display: none;
}
.site.front .layout > main {
max-width: none;
}
@media (width > 60rem) {
.site.front .layout {
grid-template-columns: var(--sidebar-width) minmax(0, 1fr);
grid-template-areas:
"header header"
"sidebar main";
}
}
Export and reload: the banner and the cards reach the right edge of the page, and on a phone the page is one column as before.
Warning
The two-column grid sits inside @media (width > 60rem) on purpose.
.site.front .layout is more specific than the built-in rule that makes
the page one column on a narrow screen, and outside the query it would
win there too. .site keeps it off a render --to html page of the note,
which keeps the built-in document layout.
Step 5: Out of the navigation
The front page is reached through the site's name in the header, so the
note needs no entry in the sidebar, no place in the previous and next
links, and no related notes under it. Add to its site table:
site:
template: front
hidden: true
related: false
Export and reload: the front page ends with the cards and the footer, and Welcome is gone from the sidebar, its counts, and the previous and next links of the other pages.


The note stays a note. It keeps its own page, drawn with the same
template, at notes/welcome.html, or with a short tail after welcome
when another note's slug is welcome too. ntropy search finds it, and
you change the introduction, the tagline, or the cards by editing it.
Note
The ntropy website goes the other way: its front page template holds all of the page's markup, an install switcher and a video among it, and the index note only selects that template. That suits markup Markdown cannot express, at the cost of writing the header and its scripts into the template yourself; Site themes lists what they need.
Check the front page
Export with --strict and look at the front page at 390 pixels and in a
full window, with the sun and with the moon. A note that names a template
the theme does not have is a warning, and the page uses page.html, so
--theme default warns about the front page note: that warning is
expected.