This commit is contained in:
pj committed 2026-06-22 11:45:21 -04:00
1 parent bd44c0ba7e
commit 706db33792
65 files changed
+6844 -1

No files matched your search

+288
View File
@@ -0,0 +1,288 @@
---
// A faithful CSS mock-up of the Margin desktop app: chapter sidebar, the
// writing surface set in Literata, and the live PDF preview dock. The editor
// shows live proofing underlines; the PDF dock shows the clean typeset page.
---
<div class="win" role="img" aria-label="The Margin app: a chapter sidebar, a writing surface with live proofing, and a live PDF page preview.">
<div class="win__bar">
<span class="dot"></span><span class="dot"></span><span class="dot"></span>
<span class="win__title">The Lighthouse Keeper</span>
<span class="win__menu">Export ▾</span>
</div>
<div class="win__body">
<aside class="side">
<p class="side__label">Manuscript</p>
<ul>
<li>Title Page</li>
<li>Dedication</li>
<li class="is-active">1 · The Cliff</li>
<li>2 · A Light at Sea</li>
<li>3 · Salt and Silence</li>
<li>4 · The Long Night</li>
</ul>
<p class="side__label">Book setup</p>
<ul class="side__meta">
<li>Trim · 5 × 8 in</li>
<li>Language · English</li>
</ul>
</aside>
<main class="page">
<div class="page__sheet">
<p class="chapno">Chapter One</p>
<h4 class="chaptitle">The Cliff</h4>
<p>
The lighthouse had stood on the headland for ninety years, and in all that time it had
never once gone dark. Mara intended to keep it that way.
</p>
<p>
She climbed the iron stair each evening at dusk, counting the
<span class="u-spell">hundered</span> and one steps out of habit, and watched the lamp
<span class="u-grammar">turns</span> its slow circle across the water.
</p>
<p>The sea, tonight, was the colour of pewter.</p>
</div>
</main>
<aside class="dock">
<p class="dock__label">Live PDF · pg 7</p>
<div class="dock__pdf">
<span class="rh">THE LIGHTHOUSE KEEPER</span>
<p class="pch">Chapter One</p>
<p class="pdt">The Cliff</p>
<p class="pp pp--first">
The lighthouse had stood on the headland for ninety years, and in all that time it had
never once gone dark. Mara intended to keep it that way.
</p>
<p class="pp">
She climbed the iron stair each evening at dusk, counting the hundred and one steps out
of habit, and watched the lamp turn its slow circle across the water.
</p>
<p class="pp">The sea, tonight, was the colour of pewter.</p>
<span class="pg">7</span>
</div>
</aside>
</div>
</div>
<style>
.win {
width: 100%;
border-radius: var(--r-xl);
background: var(--shell);
border: 1px solid var(--line-strong);
box-shadow: var(--shadow-deep);
overflow: hidden;
}
.win__bar {
display: flex;
align-items: center;
gap: 8px;
height: 40px;
padding-inline: 16px;
border-bottom: 1px solid var(--line);
background: var(--sidebar);
font-size: 0.78rem;
color: var(--ink-faint);
}
.dot {
width: 11px;
height: 11px;
border-radius: 50%;
}
.win__bar .dot:nth-child(1) {
background: #ff5f57;
}
.win__bar .dot:nth-child(2) {
background: #febc2e;
}
.win__bar .dot:nth-child(3) {
background: #28c840;
}
.win__title {
margin-inline: auto;
font-weight: 500;
}
.win__menu {
color: var(--ink-soft);
font-weight: 500;
}
.win__body {
display: grid;
grid-template-columns: 172px 1fr 196px;
min-height: 392px;
}
.side {
background: var(--sidebar);
border-right: 1px solid var(--line);
padding: 16px 12px;
font-size: 0.8rem;
}
.side__label {
font-size: 0.66rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--ink-faint);
font-weight: 650;
margin: 0 0 8px;
}
.side ul {
list-style: none;
margin: 0 0 18px;
padding: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.side li {
padding: 5px 8px;
border-radius: var(--r-sm);
color: var(--ink-soft);
}
.side li.is-active {
background: var(--accent-wash);
color: var(--ink);
font-weight: 600;
}
.side__meta li {
color: var(--ink-faint);
font-size: 0.74rem;
}
.page {
display: flex;
justify-content: center;
padding: 26px 18px;
overflow: hidden;
}
.page__sheet {
width: 100%;
max-width: 360px;
font-family: var(--font-book);
font-size: 0.92rem;
line-height: 1.62;
color: var(--ink);
}
.page__sheet p {
margin: 0 0 0.7em;
text-indent: 1.1em;
}
.chapno {
text-indent: 0 !important;
text-align: center;
font-family: var(--font-ui);
font-size: 0.62rem;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--ink-faint);
margin-bottom: 0.4em !important;
}
.chaptitle {
text-align: center;
font-size: 1.5rem;
font-weight: 560;
margin: 0 0 1.1em;
}
.u-spell {
text-decoration: wavy underline var(--danger);
text-decoration-thickness: 1.5px;
text-underline-offset: 3px;
}
.u-grammar {
text-decoration: wavy underline var(--good);
text-decoration-thickness: 1.5px;
text-underline-offset: 3px;
}
.dock {
background: var(--shell);
border-left: 1px solid var(--line);
padding: 16px 14px;
}
.dock__label {
font-size: 0.66rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--ink-faint);
font-weight: 650;
margin: 0 0 12px;
}
.dock__pdf {
position: relative;
aspect-ratio: 5 / 8;
background: #fcfbf7;
border-radius: 3px;
box-shadow: var(--shadow-page);
padding: 18px 16px 22px;
overflow: hidden;
color: #2a2622;
}
.dock__pdf .rh {
display: block;
font-family: var(--font-ui);
font-size: 4.4px;
letter-spacing: 0.16em;
color: #b3ab9c;
text-align: center;
margin-bottom: 14px;
}
.dock__pdf .pch {
font-family: var(--font-ui);
font-size: 4px;
letter-spacing: 0.22em;
text-transform: uppercase;
color: #9b9484;
text-align: center;
margin: 0 0 4px;
}
.dock__pdf .pdt {
font-family: var(--font-book);
font-size: 9px;
font-weight: 560;
color: #23201b;
text-align: center;
margin: 0 0 11px;
}
.dock__pdf .pp {
font-family: var(--font-book);
font-size: 5.6px;
line-height: 1.62;
text-align: justify;
text-indent: 1.3em;
margin: 0 0 0.5em;
color: #2a2622;
}
.dock__pdf .pp--first {
text-indent: 0;
}
.dock__pdf .pg {
position: absolute;
bottom: 9px;
left: 0;
right: 0;
text-align: center;
font-family: var(--font-book);
font-size: 5px;
color: #b3ab9c;
}
@media (max-width: 720px) {
.win__body {
grid-template-columns: 1fr 168px;
}
.side {
display: none;
}
}
@media (max-width: 480px) {
.win__body {
grid-template-columns: 1fr;
}
.dock {
display: none;
}
}
</style>
+31
View File
@@ -0,0 +1,31 @@
---
const icons = import.meta.glob("../icons/*.svg", {
query: "?raw",
import: "default",
eager: true,
});
interface Props {
name: string;
label?: string;
class?: string;
}
const { name, label, class: cls } = Astro.props;
const svg = (icons[`../icons/${name}.svg`] as string | undefined) ?? "";
---
<span class:list={["brand-icon", cls]} role="img" aria-label={label} set:html={svg}></span>
<style is:global>
.brand-icon {
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 0;
}
.brand-icon svg {
width: 100%;
height: 100%;
fill: currentColor;
}
</style>
+52
View File
@@ -0,0 +1,52 @@
---
import DownloadButtons from "./DownloadButtons.astro";
import OfferBadge from "./OfferBadge.astro";
---
<section class="section cta">
<div class="container cta__inner">
<h2>Start your book today.</h2>
<p class="lede">
Download Margin, open a blank page, and write the first line. When it's done, export it and
publish. All of it on your own machine, all of it yours.
</p>
<div class="cta__btns">
<DownloadButtons large />
</div>
<div class="cta__offer">
<OfferBadge />
</div>
</div>
</section>
<style>
.cta {
background:
radial-gradient(120% 120% at 50% 120%, var(--paper) 0%, transparent 62%),
var(--shell);
}
.cta__inner {
max-width: 640px;
margin-inline: auto;
text-align: center;
}
.cta .lede {
margin: 18px auto 0;
}
.cta__btns {
display: flex;
justify-content: center;
margin-top: 32px;
}
.cta__btns :global(.dl) {
align-items: center;
}
.cta__btns :global(.dl__os) {
justify-content: center;
}
.cta__offer {
display: flex;
justify-content: center;
margin-top: 20px;
}
</style>
+169
View File
@@ -0,0 +1,169 @@
---
import { compare, site } from "../data/site";
const keyFor: Record<string, "margin" | "vellum" | "atticus" | "lacuna"> = {
Margin: "margin",
Vellum: "vellum",
Atticus: "atticus",
Lacuna: "lacuna",
};
function cell(value: boolean | "partial") {
if (value === true) return { cls: "yes", label: "Yes" };
if (value === "partial") return { cls: "partial", label: "Varies" };
return { cls: "no", label: "No" };
}
---
<section class="section compare" id="compare">
<div class="container">
<header class="block-head block-head--center">
<p class="eyebrow">How Margin compares</p>
<h2>A better way to make a book.</h2>
<p class="lede">
Atticus, Vellum, and Lacuna are capable tools. Here's where Margin is simply different:
offline, on every platform, and yours from $0.
</p>
</header>
<div class="table-wrap card">
<table>
<thead>
<tr>
<th class="th-feature"></th>
{
compare.cols.map((c) => (
<th class:list={["th-col", { "th-col--us": c === "Margin" }]}>{c}</th>
))
}
</tr>
</thead>
<tbody>
{
compare.rows.map((row) => (
<tr>
<td class="td-feature">{row.feature}</td>
{
compare.cols.map((c) => {
const v = row[keyFor[c]];
const m = cell(v);
const isUs = c === "Margin";
return (
<td class:list={["td-val", { "td-val--us": isUs }]}>
<span class:list={["mark", m.cls]} title={m.label}>
{m.cls === "yes" ? (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg>
) : (
<span class="bar" aria-hidden="true"></span>
)}
<span class="sr">{m.label}</span>
</span>
</td>
);
})
}
</tr>
))
}
</tbody>
</table>
</div>
<p class="compare__foot">
Vellum is Mac-only. Lacuna runs on Mac and Windows. Atticus is cloud-based and asks you to
sign in. {site.name} runs offline on all three platforms, with no account at all.
</p>
</div>
</section>
<style>
.block-head--center {
margin-inline: auto;
text-align: center;
}
.block-head--center .lede {
margin-inline: auto;
}
.table-wrap {
overflow-x: auto;
padding: 6px;
}
table {
width: 100%;
border-collapse: collapse;
min-width: 640px;
}
th,
td {
padding: 15px 18px;
text-align: center;
}
.th-feature,
.td-feature {
text-align: left;
width: 42%;
}
thead th {
font-family: var(--font-ui);
font-size: 0.95rem;
font-weight: 680;
color: var(--ink-soft);
border-bottom: 1px solid var(--line);
padding-top: 18px;
padding-bottom: 18px;
}
.th-col--us {
color: var(--ink);
background: var(--accent-wash);
border-radius: var(--r-md) var(--r-md) 0 0;
}
.td-feature {
font-weight: 550;
color: var(--ink);
font-size: 0.95rem;
}
tbody tr + tr td {
border-top: 1px solid var(--line);
}
.td-val--us {
background: var(--accent-wash);
}
tbody tr:last-child .td-val--us {
border-radius: 0 0 var(--r-md) var(--r-md);
}
.mark {
display: inline-flex;
align-items: center;
justify-content: center;
width: 26px;
height: 26px;
border-radius: 50%;
}
.mark.yes {
background: color-mix(in srgb, var(--good) 16%, transparent);
color: var(--good);
}
.mark.yes svg {
width: 15px;
height: 15px;
}
.bar {
width: 12px;
height: 2px;
border-radius: 2px;
background: var(--line-strong);
}
.sr {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
}
.compare__foot {
margin-top: 18px;
font-size: 0.82rem;
color: var(--ink-faint);
max-width: 70ch;
}
</style>
@@ -0,0 +1,82 @@
---
import { site, platforms } from "../data/site";
interface Props {
large?: boolean;
}
const { large = false } = Astro.props;
---
<div class="dl">
<a
class:list={["btn", { "btn--lg": large }]}
href={site.releasesUrl}
data-download
rel="noopener"
download
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
<path d="M12 3v12m0 0 4-4m-4 4-4-4M5 21h14" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
<span data-dl-label>Download {site.name}</span>
</a>
<div class="dl__os">
<span class="dl__also">Also for</span>
{
platforms.map((p) => (
<a
class="dl__chip"
href={site.releasesUrl}
data-download-os={p.id}
rel="noopener"
download
aria-label={`Download for ${p.label}`}
title={p.label}
>
<img class="dl__logo" src={`/logos/${p.logo}`} alt={p.label} />
</a>
))
}
</div>
</div>
<style>
.dl {
display: flex;
flex-direction: column;
gap: 14px;
align-items: flex-start;
}
.dl__os {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.dl__also {
font-size: 0.82rem;
color: var(--ink-faint);
font-weight: 500;
}
.dl__chip {
display: inline-flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
background: #ffffff;
border: 1px solid var(--line-strong);
border-radius: var(--r-md);
transition: box-shadow 0.18s var(--ease), transform 0.18s var(--ease);
}
.dl__chip:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-pop);
}
.dl__logo {
width: 20px;
height: 20px;
object-fit: contain;
}
</style>
@@ -0,0 +1,66 @@
---
import { site } from "../data/site";
const { repo, releasesUrl } = site;
---
<script define:vars={{ repo, releasesUrl }}>
const SUFFIXES = {
mac: [".dmg"],
windows: [".msi", ".exe"],
linux: [".AppImage", ".deb", ".rpm"],
};
const LABELS = { mac: "macOS", windows: "Windows", linux: "Linux" };
function detectOS() {
const ua = navigator.userAgent;
const plat = ((navigator.userAgentData && navigator.userAgentData.platform) || navigator.platform || "").toLowerCase();
if (/mac|iphone|ipad|ipod/i.test(ua) || plat.includes("mac")) return "mac";
if (/win/i.test(ua) || plat.includes("win")) return "windows";
if (/linux|x11|cros/i.test(ua) || plat.includes("linux")) return "linux";
return "mac";
}
function pickAsset(assets, os) {
for (const sfx of SUFFIXES[os] || []) {
const hit = assets.find((a) => a.name.toLowerCase().endsWith(sfx.toLowerCase()));
if (hit) return hit.browser_download_url;
}
return null;
}
const os = detectOS();
document.querySelectorAll("[data-download]").forEach((el) => {
el.dataset.detectedOs = os;
const label = el.querySelector("[data-dl-label]");
if (label) label.textContent = "Download for " + LABELS[os];
});
(async () => {
try {
const res = await fetch(`https://api.github.com/repos/${repo}/releases/latest`, {
headers: { Accept: "application/vnd.github+json" },
});
if (!res.ok) return;
const data = await res.json();
const assets = data.assets || [];
const version = data.tag_name || "";
document.querySelectorAll("[data-download]").forEach((el) => {
const url = pickAsset(assets, el.dataset.detectedOs || os);
if (url) el.href = url;
});
document.querySelectorAll("[data-download-os]").forEach((el) => {
const url = pickAsset(assets, el.dataset.downloadOs);
if (url) el.href = url;
});
if (version) {
document.querySelectorAll("[data-version]").forEach((el) => {
el.textContent = version;
});
}
} catch (e) {
/* keep the releases-page fallback links */
}
})();
</script>
+88
View File
@@ -0,0 +1,88 @@
---
import { faqs, site } from "../data/site";
---
<section class="section faq" id="faq">
<div class="container faq__inner">
<header class="block-head block-head--center">
<p class="eyebrow">Questions</p>
<h2>Questions, answered.</h2>
<p class="lede">
Don't see yours? <a class="faq__mail" href={`mailto:${site.supportEmail}`}>Email us</a>.
</p>
</header>
<div class="faq__list">
{
faqs.map((f) => (
<details class="faq__item">
<summary>
<span>{f.q}</span>
<svg class="faq__chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M12 5v14M5 12h14" />
</svg>
</summary>
<div class="faq__answer">
<p>{f.a}</p>
</div>
</details>
))
}
</div>
</div>
</section>
<style>
.faq__inner {
max-width: 800px;
}
.faq__mail {
color: var(--ink);
text-decoration: underline;
text-underline-offset: 2px;
}
.faq__list {
margin-top: clamp(32px, 4vw, 44px);
border-top: 1px solid var(--line);
}
.faq__item {
border-bottom: 1px solid var(--line);
}
summary {
list-style: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
gap: 18px;
padding: 22px 4px;
font-family: var(--font-ui);
font-weight: 620;
font-size: 1.08rem;
color: var(--ink);
}
summary::-webkit-details-marker {
display: none;
}
summary:hover {
color: var(--ink);
}
.faq__chev {
flex: none;
width: 20px;
height: 20px;
color: var(--ink-faint);
transition: transform 0.22s var(--ease);
}
.faq__item[open] .faq__chev {
transform: rotate(45deg);
}
.faq__answer {
padding: 0 4px 24px;
max-width: 68ch;
}
.faq__answer p {
color: var(--ink-soft);
line-height: 1.62;
}
</style>
+123
View File
@@ -0,0 +1,123 @@
---
const features = [
{
title: "A writing surface that gets out of the way",
body: "A clean editor built for long-form prose. Headings, scenes, block quotes, and chapters: the structure of a book, with none of the clutter of a word processor.",
icon: "pen",
},
{
title: "Book setup in a single panel",
body: "Title, author, ISBN, trim size, and language live in one place. Click the title bar, fill it in once, and every export inherits it.",
icon: "book",
},
{
title: "Images, placed like a typesetter would",
body: "Drop in figures and choose how they sit on the page: inline, full width, full page, or floated. The page preview shows exactly where they land.",
icon: "image",
},
{
title: "A live page preview as you write",
body: "A real, paginated preview docks beside your text. Watch your words fall into a properly typeset book the moment you type them.",
icon: "preview",
},
{
title: "One source, every format",
body: "Your writing flows into the editor, the printed page, and the ebook from one place, all set in real book typography. Change something once and it updates everywhere.",
icon: "layers",
},
{
title: "Your book is one simple file",
body: "Your whole book is a single file you can copy, rename, email, or tuck into a folder. No libraries to manage, nothing locked away.",
icon: "file",
},
];
const paths: Record<string, string> = {
pen: "M12 19l7-7 3 3-7 7-3-3zM18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5zM2 2l7.586 7.586",
book: "M4 19.5A2.5 2.5 0 0 1 6.5 17H20M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z",
image: "M3 5h18v14H3zM3 16l5-5 4 4 4-4 5 5M9 9.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z",
preview: "M3 3h18v18H3zM3 9h18M9 9v12",
layers: "M12 2 2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5",
file: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8zM14 2v6h6M9 13h6M9 17h6",
};
---
<section class="section features" id="features">
<div class="container">
<header class="block-head">
<p class="eyebrow">The exact tools, nothing more</p>
<h2>Everything you need to make a book.</h2>
<p class="lede">
Page-layout suites bury the few tools a book actually needs under a thousand you never
will. Margin keeps the handful that matter, and makes them effortless.
</p>
</header>
<div class="grid">
{
features.map((f) => (
<article class="card feat">
<span class="feat__icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d={paths[f.icon]} />
</svg>
</span>
<h3>{f.title}</h3>
<p>{f.body}</p>
</article>
))
}
</div>
</div>
</section>
<style>
.block-head {
max-width: 640px;
margin-bottom: clamp(36px, 5vw, 56px);
}
.block-head .lede {
margin-top: 18px;
max-width: 58ch;
}
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 18px;
}
.feat {
padding: 26px 24px 28px;
}
.feat__icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 42px;
height: 42px;
border-radius: var(--r-md);
background: var(--accent-wash);
color: var(--ink);
margin-bottom: 18px;
}
.feat__icon svg {
width: 21px;
height: 21px;
}
.feat h3 {
margin-bottom: 9px;
}
.feat p {
color: var(--ink-soft);
font-size: 0.96rem;
}
@media (max-width: 880px) {
.grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 560px) {
.grid {
grid-template-columns: 1fr;
}
}
</style>
+154
View File
@@ -0,0 +1,154 @@
---
import { site } from "../data/site";
import Logo from "./Logo.astro";
const cols = [
{
title: "Get Margin",
links: [
{ label: "Download for macOS", href: site.releasesUrl, os: "mac" },
{ label: "Download for Windows", href: site.releasesUrl, os: "windows" },
{ label: "Download for Linux", href: site.releasesUrl, os: "linux" },
],
},
{
title: "Product",
links: [
{ label: "Features", href: "#features" },
{ label: "Publishing", href: "#publish" },
{ label: "Compare", href: "#compare" },
{ label: "Pricing", href: "#pricing" },
],
},
{
title: "Help",
links: [
{ label: "Questions", href: "#faq" },
{ label: "Email us", href: `mailto:${site.supportEmail}` },
],
},
];
---
<footer class="footer">
<div class="container footer__inner">
<div class="footer__brand">
<a class="brand" href="/">
<Logo size={30} />
<span>{site.name}</span>
</a>
<p>The offline writing studio for authors. Write your book and publish it anywhere.</p>
</div>
<div class="footer__cols">
{
cols.map((col) => (
<nav class="footer__col">
<p class="footer__title">{col.title}</p>
<ul>
{col.links.map((l) => {
const isAnchor = l.href.startsWith("#") || l.href.startsWith("mailto:");
return (
<li>
<a
href={l.href}
data-download-os={(l as { os?: string }).os}
target={isAnchor ? undefined : "_blank"}
rel={isAnchor ? undefined : "noopener"}
>
{l.label}
</a>
</li>
);
})}
</ul>
</nav>
))
}
</div>
</div>
<div class="container footer__base">
<span>© {site.name}. Built with <span class="heart">♥</span> in NYC</span>
</div>
</footer>
<style>
.footer {
background: var(--sidebar);
border-top: 1px solid var(--line);
padding-top: clamp(48px, 6vw, 72px);
padding-bottom: 28px;
}
.footer__inner {
display: grid;
grid-template-columns: 1.3fr 2fr;
gap: 40px;
padding-bottom: 40px;
}
.footer__brand .brand {
display: inline-flex;
align-items: center;
gap: 9px;
font-weight: 680;
font-size: 1.12rem;
}
.footer__brand img {
border-radius: 7px;
}
.footer__brand p {
margin-top: 12px;
color: var(--ink-soft);
font-size: 0.92rem;
max-width: 34ch;
}
.footer__cols {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
.footer__title {
font-size: 0.72rem;
letter-spacing: 0.12em;
text-transform: uppercase;
font-weight: 650;
color: var(--ink-faint);
margin-bottom: 14px;
}
.footer__col ul {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 10px;
}
.footer__col a {
color: var(--ink-soft);
font-size: 0.92rem;
}
.footer__col a:hover {
color: var(--ink);
}
.footer__base {
padding-top: 22px;
border-top: 1px solid var(--line);
text-align: center;
font-size: 0.82rem;
color: var(--ink-faint);
}
.heart {
color: var(--danger);
}
@media (max-width: 760px) {
.footer__inner {
grid-template-columns: 1fr;
gap: 32px;
}
}
@media (max-width: 480px) {
.footer__cols {
grid-template-columns: 1fr 1fr;
}
}
</style>
+73
View File
@@ -0,0 +1,73 @@
---
import AppWindow from "./AppWindow.astro";
import DownloadButtons from "./DownloadButtons.astro";
import OfferBadge from "./OfferBadge.astro";
---
<section class="hero">
<div class="container hero__head">
<h1>
Write your book.<br />
<span class="book-accent">Own</span> every word.
</h1>
<p class="lede hero__lede">
Margin is a calm, offline writing studio for authors. Write your book, format it
beautifully, and send it to every store and to print, all from your own machine. No
subscription, no account. Buy once, own it forever.
</p>
<div class="hero__cta">
<DownloadButtons large />
</div>
<div class="hero__offer">
<OfferBadge />
</div>
</div>
<div class="container hero__art">
<AppWindow />
</div>
</section>
<style>
.hero {
padding-top: clamp(48px, 7vw, 92px);
padding-bottom: clamp(40px, 6vw, 72px);
background:
radial-gradient(120% 90% at 50% -10%, var(--paper) 0%, transparent 60%),
var(--shell);
}
.hero__head {
text-align: center;
max-width: 760px;
}
h1 {
font-size: clamp(2.6rem, 1.6rem + 4.4vw, 4.5rem);
letter-spacing: -0.02em;
margin-bottom: 22px;
}
.hero__lede {
margin-inline: auto;
max-width: 56ch;
}
.hero__cta {
display: flex;
justify-content: center;
margin-top: 34px;
}
.hero__cta :global(.dl) {
align-items: center;
}
.hero__cta :global(.dl__os) {
justify-content: center;
}
.hero__offer {
display: flex;
justify-content: center;
margin-top: 30px;
}
.hero__art {
margin-top: clamp(44px, 6vw, 72px);
max-width: 1000px;
}
</style>
+11
View File
@@ -0,0 +1,11 @@
---
interface Props {
size?: number;
}
const { size = 28 } = Astro.props;
---
<span class="logo-mark" style={`width:${size}px;height:${size}px`}>
<img class="light" src="/logo-light.svg" alt="" width={size} height={size} />
<img class="dark" src="/logo-dark.svg" alt="" width={size} height={size} />
</span>
+132
View File
@@ -0,0 +1,132 @@
---
import { site } from "../data/site";
import Logo from "./Logo.astro";
---
<header class="nav">
<div class="container nav__inner">
<a class="brand" href="/" aria-label={`${site.name} home`}>
<Logo size={28} />
<span>{site.name}</span>
</a>
<nav class="nav__links" aria-label="Primary">
<a href="#features">Features</a>
<a href="#publish">Publishing</a>
<a href="#compare">Compare</a>
<a href="#pricing">Pricing</a>
<a href="#faq">Questions</a>
</nav>
<div class="nav__actions">
<button class="iconlink" id="theme-toggle" aria-label="Toggle theme" type="button">
<svg class="i-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" aria-hidden="true">
<circle cx="12" cy="12" r="4"></circle>
<path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"></path>
</svg>
<svg class="i-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" aria-hidden="true">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79Z"></path>
</svg>
</button>
</div>
</div>
</header>
<script is:inline>
(function () {
var btn = document.getElementById("theme-toggle");
if (!btn) return;
btn.addEventListener("click", function () {
var cur = document.documentElement.getAttribute("data-theme") === "dark" ? "dark" : "light";
var next = cur === "dark" ? "light" : "dark";
document.documentElement.setAttribute("data-theme", next);
try {
localStorage.setItem("margin-theme", next);
} catch (e) {}
});
})();
</script>
<style>
.nav {
position: sticky;
top: 0;
z-index: 50;
background: var(--glass);
backdrop-filter: saturate(140%) blur(14px);
border-bottom: 1px solid var(--line);
}
.nav__inner {
display: flex;
align-items: center;
gap: 24px;
height: 64px;
}
.brand {
display: flex;
align-items: center;
gap: 9px;
font-weight: 680;
font-size: 1.12rem;
letter-spacing: -0.01em;
margin-right: auto;
}
.brand img {
border-radius: 7px;
}
.nav__links {
display: flex;
gap: 26px;
font-size: 0.95rem;
color: var(--ink-soft);
font-weight: 500;
}
.nav__links a:hover {
color: var(--ink);
}
.nav__actions {
display: flex;
align-items: center;
gap: 10px;
}
.iconlink {
display: inline-flex;
align-items: center;
justify-content: center;
width: 38px;
height: 38px;
border-radius: 999px;
color: var(--ink-soft);
border: 1px solid transparent;
cursor: pointer;
background: transparent;
transition: background 0.18s var(--ease), color 0.18s var(--ease);
}
.iconlink:hover {
background: var(--accent-wash);
color: var(--ink);
}
.iconlink svg {
width: 18px;
height: 18px;
}
.i-moon {
display: none;
}
:root[data-theme="dark"] .i-sun {
display: none;
}
:root[data-theme="dark"] .i-moon {
display: block;
}
@media (max-width: 860px) {
.nav__links {
display: none;
}
}
@media (max-width: 560px) {
.nav__cta {
display: none;
}
}
</style>
+67
View File
@@ -0,0 +1,67 @@
---
import { site } from "../data/site";
const { cap, left } = site.launch;
---
<span class="offer">
<span class="offer__dot"></span>
<span class="offer__price"><s>{site.price}</s> $0</span>
<span class="offer__txt">for the first {cap} writers</span>
<span class="offer__sep">·</span>
<span class="offer__left">only {left} left</span>
</span>
<style>
.offer {
display: inline-flex;
align-items: center;
gap: 9px;
padding: 8px 16px;
border-radius: 999px;
background: color-mix(in srgb, var(--good) 12%, var(--paper));
border: 1px solid color-mix(in srgb, var(--good) 32%, transparent);
box-shadow: var(--shadow-page);
font-size: 0.9rem;
font-weight: 600;
color: var(--ink);
white-space: nowrap;
}
.offer__dot {
width: 9px;
height: 9px;
border-radius: 50%;
background: var(--good);
animation: offer-pulse 2s var(--ease) infinite;
}
@keyframes offer-pulse {
0% {
box-shadow: 0 0 0 0 color-mix(in srgb, var(--good) 55%, transparent);
}
70% {
box-shadow: 0 0 0 7px transparent;
}
100% {
box-shadow: 0 0 0 0 transparent;
}
}
@media (prefers-reduced-motion: reduce) {
.offer__dot {
animation: none;
}
}
.offer__price s {
color: var(--ink-faint);
margin-right: 4px;
}
.offer__price {
color: var(--good);
font-weight: 800;
}
.offer__sep {
color: var(--ink-faint);
}
.offer__left {
color: var(--good);
font-weight: 700;
}
</style>
+202
View File
@@ -0,0 +1,202 @@
---
import { site } from "../data/site";
import DownloadButtons from "./DownloadButtons.astro";
const { cap, left } = site.launch;
const claimed = cap - left;
const pct = Math.round((claimed / cap) * 100);
const included = [
"Every feature, nothing held back",
"macOS, Windows & Linux",
"Unlimited books, unlimited exports",
"Updates included, no renewals",
"No account, ever",
"Your book stays yours, forever",
];
---
<section class="section pricing" id="pricing">
<div class="container">
<header class="block-head block-head--center">
<p class="eyebrow">One price, once</p>
<h2>Buy once. Own it forever.</h2>
<p class="lede">
Software you buy and keep, the way it used to be. No subscription, no per-book fees, no
login that can expire. Pay once, and Margin is yours.
</p>
</header>
<div class="price-card card">
<div class="price-card__badge">
<span class="price-card__dot"></span>
Launch offer · only {left} of {cap} spots left
</div>
<div class="price-card__amount">
<s class="price-card__was">{site.price}</s>
<span class="price-card__num">$0</span>
</div>
<p class="price-card__sub">
$0 for the first {cap} writers. After that, {site.price}, once.
</p>
<div class="price-card__meter" role="img" aria-label={`${claimed} of ${cap} spots claimed`}>
<span style={`width:${pct}%`}></span>
</div>
<p class="price-card__meterlabel">{claimed} claimed · {left} left</p>
<ul class="price-card__list">
{included.map((i) => <li>{i}</li>)}
</ul>
<div class="price-card__cta">
<DownloadButtons large />
</div>
<p class="price-card__note">
The price is a small thank-you for the craft. It funds the time that keeps Margin sharp.
</p>
</div>
</div>
</section>
<style>
.price-card {
max-width: 560px;
margin: clamp(36px, 5vw, 52px) auto 0;
padding: clamp(30px, 4vw, 46px);
text-align: center;
background: var(--paper);
box-shadow: var(--shadow-deep);
}
.price-card__badge {
display: inline-flex;
align-items: center;
gap: 9px;
padding: 8px 18px;
border-radius: 999px;
background: color-mix(in srgb, var(--good) 16%, transparent);
color: var(--good);
font-size: 0.82rem;
font-weight: 680;
letter-spacing: 0.01em;
margin-bottom: 24px;
}
.price-card__dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--good);
animation: price-pulse 2s var(--ease) infinite;
}
@keyframes price-pulse {
0% {
box-shadow: 0 0 0 0 color-mix(in srgb, var(--good) 55%, transparent);
}
70% {
box-shadow: 0 0 0 7px transparent;
}
100% {
box-shadow: 0 0 0 0 transparent;
}
}
@media (prefers-reduced-motion: reduce) {
.price-card__dot {
animation: none;
}
}
.price-card__amount {
display: flex;
align-items: baseline;
justify-content: center;
gap: 14px;
}
.price-card__was {
font-family: var(--font-book);
font-size: 2rem;
color: var(--ink-faint);
}
.price-card__num {
font-family: var(--font-book);
font-size: clamp(3.4rem, 2rem + 6vw, 5rem);
font-weight: 580;
letter-spacing: -0.02em;
line-height: 1;
}
.price-card__sub {
margin-top: 12px;
color: var(--ink-soft);
font-weight: 500;
}
.price-card__meter {
margin: 24px auto 8px;
max-width: 320px;
height: 8px;
border-radius: 999px;
background: var(--accent-wash);
overflow: hidden;
}
.price-card__meter span {
display: block;
height: 100%;
border-radius: 999px;
background: var(--good);
}
.price-card__meterlabel {
font-size: 0.8rem;
color: var(--ink-faint);
font-weight: 550;
}
.price-card__list {
list-style: none;
margin: 28px 0;
padding: 28px 0;
border-block: 1px solid var(--line);
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px;
text-align: left;
}
.price-card__list li {
position: relative;
padding-left: 28px;
font-weight: 550;
font-size: 0.95rem;
}
.price-card__list li::before {
content: "";
position: absolute;
left: 0;
top: 1px;
width: 19px;
height: 19px;
border-radius: 50%;
background: color-mix(in srgb, var(--good) 16%, transparent);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%234f7a52' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center;
background-size: 12px;
}
.price-card__cta {
display: flex;
justify-content: center;
}
.price-card__cta :global(.dl) {
align-items: center;
}
.price-card__cta :global(.dl__os) {
justify-content: center;
}
.price-card__note {
margin-top: 26px;
font-size: 0.84rem;
color: var(--ink-faint);
line-height: 1.55;
}
@media (max-width: 520px) {
.price-card__list {
grid-template-columns: 1fr;
}
}
</style>
+150
View File
@@ -0,0 +1,150 @@
---
---
<section class="section privacy" id="privacy">
<div class="container privacy__grid">
<div class="privacy__copy">
<p class="eyebrow">Yours, not ours</p>
<h2>Your words belong to you.</h2>
<p class="lede">
Margin keeps your book where it belongs: with you. We never hold a copy on our servers,
and nothing you write is sent away to be read. Want a backup? Margin can save one to your
own Google Drive, in your account and under your control. Wherever your book lives, it is
yours.
</p>
<ul class="privacy__points">
<li>No copies kept by us, ever</li>
<li>Optional backup to your own Google Drive</li>
<li>Works the same with or without internet</li>
</ul>
</div>
<div class="own">
<div class="own__card own__card--device">
<span class="own__tag">On your device</span>
<ul>
<li>Your manuscript</li>
<li>Spelling &amp; grammar</li>
<li>Print &amp; ebook export</li>
<li>Fonts &amp; book setup</li>
</ul>
<p class="own__foot">Everything Margin does, it does right here.</p>
</div>
<div class="own__card own__card--drive">
<span class="own__tag">
<img class="own__drive" src="/logos/google-drive.svg" alt="" width="16" height="16" />
Your Google Drive
</span>
<p class="own__drivecopy">
A private backup in your own account, switched on only if you want it.
</p>
<p class="own__foot">Your copy. Your account. Your control.</p>
</div>
</div>
</div>
</section>
<style>
.privacy__grid {
display: grid;
grid-template-columns: 1.05fr 0.95fr;
gap: clamp(32px, 5vw, 72px);
align-items: center;
}
.privacy__points {
margin: 26px 0 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: 12px;
}
.privacy__points li {
position: relative;
padding-left: 30px;
color: var(--ink-soft);
font-weight: 500;
}
.privacy__points li::before {
content: "";
position: absolute;
left: 0;
top: 2px;
width: 19px;
height: 19px;
border-radius: 50%;
background: var(--accent-wash);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236b6458' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center;
background-size: 12px;
}
.own {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
.own__card {
border-radius: var(--r-lg);
padding: 22px 20px;
border: 1px solid var(--line);
min-height: 240px;
display: flex;
flex-direction: column;
background: var(--paper);
box-shadow: var(--shadow-page);
}
.own__card--drive {
background: transparent;
box-shadow: none;
border-style: dashed;
}
.own__tag {
display: inline-flex;
align-items: center;
gap: 8px;
font-size: 0.72rem;
letter-spacing: 0.1em;
text-transform: uppercase;
font-weight: 650;
color: var(--ink-faint);
}
.own__tag :global(.brand-icon) {
width: 15px;
height: 15px;
color: var(--ink-soft);
}
.own__card--device ul {
list-style: none;
margin: 16px 0 auto;
padding: 0;
display: flex;
flex-direction: column;
gap: 9px;
font-weight: 550;
}
.own__card--device li {
padding: 8px 12px;
background: var(--accent-wash);
border-radius: var(--r-sm);
font-size: 0.92rem;
}
.own__drivecopy {
margin: 18px 0 auto;
color: var(--ink-soft);
font-size: 0.95rem;
line-height: 1.55;
}
.own__foot {
font-size: 0.82rem;
color: var(--ink-faint);
margin-top: 14px;
}
@media (max-width: 860px) {
.privacy__grid {
grid-template-columns: 1fr;
}
}
</style>
+144
View File
@@ -0,0 +1,144 @@
---
---
<section class="section proof">
<div class="container proof__grid">
<div class="proof__demo">
<div class="card proof__editor">
<p class="proof__line">
She climbed the <span class="u-spell">hundered</span> and one steps and watched the
lamp <span class="u-grammar">turns</span> its slow circle.
</p>
<div class="pop">
<span class="pop__kind">Spelling</span>
<span class="pop__from">hundered</span>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span class="pop__to">hundred</span>
</div>
</div>
<p class="proof__badge">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" stroke-linejoin="round"/></svg>
Checked entirely on your device
</p>
</div>
<div class="proof__copy">
<p class="eyebrow">Built-in proofing</p>
<h2>Grammar and spelling, caught as you write.</h2>
<p class="lede">
Margin underlines the mistakes that matter: misspellings, capitalization slips, doubled
words, and broken grammar, with the same inline corrections you'd expect from the best
checkers. The difference is that it all runs on your machine, so not a single word is sent
away to be read.
</p>
<ul class="proof__points">
<li>Real-time underlines, right in the text</li>
<li>One-tap corrections and suggestions</li>
<li>Runs on your machine, nothing sent away</li>
</ul>
</div>
</div>
</section>
<style>
.proof__grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: clamp(32px, 5vw, 72px);
align-items: center;
}
.proof__editor {
padding: 34px 30px 44px;
position: relative;
font-family: var(--font-book);
font-size: 1.25rem;
line-height: 1.7;
}
.u-spell {
text-decoration: wavy underline var(--danger);
text-decoration-thickness: 2px;
text-underline-offset: 4px;
}
.u-grammar {
text-decoration: wavy underline var(--good);
text-decoration-thickness: 2px;
text-underline-offset: 4px;
}
.pop {
position: absolute;
left: 30px;
bottom: -18px;
display: inline-flex;
align-items: center;
gap: 9px;
background: var(--accent);
color: var(--accent-contrast);
padding: 9px 14px;
border-radius: 999px;
box-shadow: var(--shadow-pop);
font-family: var(--font-ui);
font-size: 0.9rem;
font-weight: 600;
}
.pop__kind {
font-size: 0.66rem;
letter-spacing: 0.1em;
text-transform: uppercase;
opacity: 0.65;
}
.pop__from {
text-decoration: line-through;
opacity: 0.7;
}
.pop svg {
width: 15px;
height: 15px;
opacity: 0.7;
}
.proof__badge {
display: inline-flex;
align-items: center;
gap: 8px;
margin-top: 26px;
font-size: 0.86rem;
font-weight: 600;
color: var(--ink-soft);
}
.proof__badge svg {
width: 17px;
height: 17px;
color: var(--good);
}
.proof__points {
margin: 24px 0 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: 11px;
font-weight: 550;
color: var(--ink-soft);
}
.proof__points li {
position: relative;
padding-left: 26px;
}
.proof__points li::before {
content: "";
position: absolute;
left: 0;
top: 7px;
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--good);
}
@media (max-width: 860px) {
.proof__grid {
grid-template-columns: 1fr;
}
.proof__demo {
order: 2;
}
}
</style>
+138
View File
@@ -0,0 +1,138 @@
---
import { stores } from "../data/site";
---
<section class="section publish" id="publish">
<div class="container">
<header class="block-head block-head--center">
<p class="eyebrow">Sell it anywhere</p>
<h2>Your book works with every store and platform.</h2>
<p class="lede">
Margin hands you the two files publishing runs on: a print-ready file for paperback and a
clean ebook for every reader. Upload them anywhere you like. No conversion services, no
middlemen.
</p>
</header>
<div class="stores">
{
stores.map((s) => (
<div class="store">
<span class="store__logo">
<img src={`/logos/${s.logo}`} alt={`${s.name} logo`} />
</span>
<span class="store__name">{s.name}</span>
</div>
))
}
</div>
<div class="formats">
<div class="format">
<span class="format__tag">Paperback</span>
<span>A print-ready file, set in real book typography.</span>
</div>
<div class="format">
<span class="format__tag">Ebook</span>
<span>A reflowable file that looks right on every reader.</span>
</div>
</div>
</div>
</section>
<style>
.block-head--center {
margin-inline: auto;
text-align: center;
}
.block-head--center .lede {
margin-inline: auto;
}
.stores {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
margin-top: clamp(36px, 5vw, 52px);
}
.store {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16px;
padding: 28px 18px 22px;
background: #ffffff;
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--shadow-page);
text-align: center;
transition: transform 0.18s var(--ease), box-shadow 0.18s var(--ease);
}
.store:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-pop);
}
.store__logo {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 48px;
padding-inline: 6px;
}
.store__logo img {
width: 100%;
height: 100%;
object-fit: contain;
}
.store__name {
font-weight: 600;
font-size: 0.86rem;
color: #6b6458;
}
.formats {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
max-width: 760px;
margin: 40px auto 0;
}
.format {
display: flex;
align-items: center;
gap: 14px;
padding: 18px 22px;
border: 1px solid var(--line);
border-radius: var(--r-md);
background: var(--raised);
font-size: 0.95rem;
color: var(--ink-soft);
}
.format__tag {
flex: none;
font-size: 0.7rem;
letter-spacing: 0.1em;
text-transform: uppercase;
font-weight: 700;
color: var(--ink);
padding: 5px 10px;
background: var(--accent-wash);
border-radius: 999px;
}
@media (max-width: 880px) {
.stores {
grid-template-columns: repeat(3, 1fr);
}
}
@media (max-width: 620px) {
.stores {
grid-template-columns: repeat(2, 1fr);
}
.formats {
grid-template-columns: 1fr;
}
}
</style>
+125
View File
@@ -0,0 +1,125 @@
// Single source of truth for the site. Change the price, offer, or content here.
export const site = {
name: "Margin",
tagline: "Write your book. Own every word.",
description:
"Margin is a calm, offline writing studio for authors. Write your book and send it to print and to every ebook store, all from your own machine. Buy once, own it forever.",
domain: "https://margin.studio",
// The regular one-time price, shown struck through during the launch offer.
price: "$10",
// Launch offer: first N writers get it for $0. Update `left` as spots go.
launch: {
cap: 100,
left: 12,
},
supportEmail: "[email protected]",
// Repo that hosts the release files. Used only to resolve the right download
// per OS at runtime. It is never shown to the reader.
repo: "priyanshujain/margin",
get repoUrl() {
return `https://github.com/${this.repo}`;
},
get releasesUrl() {
return `https://github.com/${this.repo}/releases/latest`;
},
};
export type Platform = {
id: "mac" | "windows" | "linux";
label: string;
logo: string;
// Filename suffix used to pick the right file from a release.
match: string[];
};
export const platforms: Platform[] = [
{ id: "mac", label: "macOS", logo: "macos.svg", match: [".dmg"] },
{ id: "windows", label: "Windows", logo: "windows.svg", match: [".msi", ".exe"] },
{ id: "linux", label: "Linux", logo: "linux.svg", match: [".AppImage", ".deb", ".rpm"] },
];
// Full-color store logos, served from /public/logos. Shown on white tiles so
// every logo stays legible in both light and dark themes.
export const stores = [
{ name: "Amazon Kindle", logo: "kindle.svg" },
{ name: "Apple Books", logo: "apple-books.svg" },
{ name: "Google Play Books", logo: "google-play-books.svg" },
{ name: "Kobo", logo: "kobo.svg" },
{ name: "Barnes & Noble", logo: "barnes-noble.svg" },
{ name: "Scribd", logo: "scribd.svg" },
{ name: "OverDrive", logo: "overdrive.svg" },
{ name: "tolino", logo: "tolino.svg" },
{ name: "Draft2Digital", logo: "draft2digital.png" },
{ name: "Smashwords", logo: "smashwords.png" },
{ name: "IngramSpark", logo: "ingramspark.png" },
];
export const faqs = [
{
q: "Is this a subscription?",
a: "No. Margin is a one-time purchase, and the first 100 writers get it for $0. Pay once and it is yours for good, with no monthly fees, no renewals, and no seats to manage.",
},
{
q: "Does Margin really work offline?",
a: "Yes, completely. Writing, the live page preview, the spelling and grammar checker, and every export all happen on your machine. You can write and publish an entire book with the internet switched off.",
},
{
q: "Is my book private?",
a: "Your book belongs to you. Margin never keeps a copy on our servers, and nothing you write is sent away to be read. If you would like a backup, Margin can save one to your own Google Drive, in your account and under your control.",
},
{
q: "Which platforms are supported?",
a: "macOS, Windows, and Linux. The same app, the same book, the same results on every one. (Vellum is Mac-only, and Lacuna runs on Mac and Windows.)",
},
{
q: "Can I sell on Amazon, Apple Books, and Kobo?",
a: "Yes. Margin gives you a clean ebook file you upload straight to Amazon, Apple Books, Kobo, Google Play, Barnes & Noble, and the rest. For paperback, you get a print-ready file for services like Amazon and IngramSpark.",
},
{
q: "Is the print file really ready to publish?",
a: "Yes. Margin sets your book in real book typography, with proper margins, page numbers, running heads, and your chosen trim size. The file is ready to send to a printer or upload to a print-on-demand service.",
},
{
q: "How is the grammar and spelling different from Grammarly?",
a: "It catches the same kinds of mistakes, misspellings, capitalization, doubled words, and grammar slips, right as you write. The difference is that it works on your machine, so your words are never sent anywhere to be checked.",
},
{
q: "Where is my book kept?",
a: "Your whole book is a single file on your computer, easy to copy, rename, back up, or move to another machine. Nothing is locked inside an account or a library you cannot get out of.",
},
{
q: "Do I need an account?",
a: "No. No sign-up, no login, no email needed to write. Download Margin, open it, and start your book.",
},
{
q: "Do updates cost extra?",
a: "No. Updates are included, and the app keeps itself up to date. You bought the tool, so you keep getting better versions of it.",
},
{
q: "What if my question isn't here?",
a: "Write to us and a real person will answer. We read every message.",
},
];
// Capability comparison. Values are conservative and publicly verifiable.
// Vellum : macOS-only, native, offline, one-time (~$250), no grammar checker.
// Atticus : cloud-based, requires an account, cross-platform, one-time (~$147).
// Lacuna : Mac + Windows, offline, one-time (~$139), no grammar checker.
export const compare = {
cols: ["Margin", "Vellum", "Atticus", "Lacuna"] as const,
rows: [
{ feature: "Works fully offline", margin: true, vellum: true, atticus: false, lacuna: true },
{ feature: "macOS, Windows & Linux", margin: true, vellum: false, atticus: true, lacuna: false },
{ feature: "Print-ready paperback file", margin: true, vellum: true, atticus: true, lacuna: true },
{ feature: "Ebook for Kindle, Apple & Kobo", margin: true, vellum: true, atticus: true, lacuna: true },
{ feature: "Built-in grammar & spelling", margin: true, vellum: false, atticus: false, lacuna: false },
{ feature: "No account needed", margin: true, vellum: true, atticus: false, lacuna: true },
{ feature: "One-time price, no subscription", margin: true, vellum: true, atticus: true, lacuna: true },
{ feature: "Your book stays yours", margin: true, vellum: true, atticus: false, lacuna: true },
],
};
+1
View File
@@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Amazon</title><path d="M.045 18.02c.072-.116.187-.124.348-.022 3.636 2.11 7.594 3.166 11.87 3.166 2.852 0 5.668-.533 8.447-1.595l.315-.14c.138-.06.234-.1.293-.13.226-.088.39-.046.525.13.12.174.09.336-.12.48-.256.19-.6.41-1.006.654-1.244.743-2.64 1.316-4.185 1.726a17.617 17.617 0 01-10.951-.577 17.88 17.88 0 01-5.43-3.35c-.1-.074-.151-.15-.151-.22 0-.047.021-.09.051-.13zm6.565-6.218c0-1.005.247-1.863.743-2.577.495-.71 1.17-1.25 2.04-1.615.796-.335 1.756-.575 2.912-.72.39-.046 1.033-.103 1.92-.174v-.37c0-.93-.105-1.558-.3-1.875-.302-.43-.78-.65-1.44-.65h-.182c-.48.046-.896.196-1.246.46-.35.27-.575.63-.675 1.096-.06.3-.206.465-.435.51l-2.52-.315c-.248-.06-.372-.18-.372-.39 0-.046.007-.09.022-.15.247-1.29.855-2.25 1.82-2.88.976-.616 2.1-.975 3.39-1.05h.54c1.65 0 2.957.434 3.888 1.29.135.15.27.3.405.48.12.165.224.314.283.45.075.134.15.33.195.57.06.254.105.42.135.51.03.104.062.3.076.615.01.313.02.493.02.553v5.28c0 .376.06.72.165 1.036.105.313.21.54.315.674l.51.674c.09.136.136.256.136.36 0 .12-.06.226-.18.314-1.2 1.05-1.86 1.62-1.963 1.71-.165.135-.375.15-.63.045a6.062 6.062 0 01-.526-.496l-.31-.347a9.391 9.391 0 01-.317-.42l-.3-.435c-.81.886-1.603 1.44-2.4 1.665-.494.15-1.093.227-1.83.227-1.11 0-2.04-.343-2.76-1.034-.72-.69-1.08-1.665-1.08-2.94l-.05-.076zm3.753-.438c0 .566.14 1.02.425 1.364.285.34.675.512 1.155.512.045 0 .106-.007.195-.02.09-.016.134-.023.166-.023.614-.16 1.08-.553 1.424-1.178.165-.28.285-.58.36-.91.09-.32.12-.59.135-.8.015-.195.015-.54.015-1.005v-.54c-.84 0-1.484.06-1.92.18-1.275.36-1.92 1.17-1.92 2.43l-.035-.02zm9.162 7.027c.03-.06.075-.11.132-.17.362-.243.714-.41 1.05-.5a8.094 8.094 0 011.612-.24c.14-.012.28 0 .41.03.65.06 1.05.168 1.172.33.063.09.099.228.099.39v.15c0 .51-.149 1.11-.424 1.8-.278.69-.664 1.248-1.156 1.68-.073.06-.14.09-.197.09-.03 0-.06 0-.09-.012-.09-.044-.107-.12-.064-.24.54-1.26.806-2.143.806-2.64 0-.15-.03-.27-.087-.344-.145-.166-.55-.257-1.224-.257-.243 0-.533.016-.87.046-.363.045-.7.09-1 .135-.09 0-.148-.014-.18-.044-.03-.03-.036-.047-.02-.077 0-.017.006-.03.02-.063v-.06z"/></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

+1
View File
@@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Apple</title><path d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701"/></svg>

After

Width:  |  Height:  |  Size: 650 B

+1
View File
@@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Google Drive</title><path d="M12.01 1.485c-2.082 0-3.754.02-3.743.047.01.02 1.708 3.001 3.774 6.62l3.76 6.574h3.76c2.081 0 3.753-.02 3.742-.047-.005-.02-1.708-3.001-3.775-6.62l-3.76-6.574zm-4.76 1.73a789.828 789.861 0 0 0-3.63 6.319L0 15.868l1.89 3.298 1.885 3.297 3.62-6.335 3.618-6.33-1.88-3.287C8.1 4.704 7.255 3.22 7.25 3.214zm2.259 12.653-.203.348c-.114.198-.96 1.672-1.88 3.287a423.93 423.948 0 0 1-1.698 2.97c-.01.026 3.24.042 7.222.042h7.244l1.796-3.157c.992-1.734 1.85-3.23 1.906-3.323l.104-.167h-7.249z"/></svg>

After

Width:  |  Height:  |  Size: 599 B

+1
View File
@@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Google Play</title><path d="M22.018 13.298l-3.919 2.218-3.515-3.493 3.543-3.521 3.891 2.202a1.49 1.49 0 0 1 0 2.594zM1.337.924a1.486 1.486 0 0 0-.112.568v21.017c0 .217.045.419.124.6l11.155-11.087L1.337.924zm12.207 10.065l3.258-3.238L3.45.195a1.466 1.466 0 0 0-.946-.179l11.04 10.973zm0 2.067l-11 10.933c.298.036.612-.016.906-.183l13.324-7.54-3.23-3.21z"/></svg>

After

Width:  |  Height:  |  Size: 439 B

+1
View File
@@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Linux</title><path d="M12.504 0c-.155 0-.315.008-.48.021-4.226.333-3.105 4.807-3.17 6.298-.076 1.092-.3 1.953-1.05 3.02-.885 1.051-2.127 2.75-2.716 4.521-.278.832-.41 1.684-.287 2.489a.424.424 0 00-.11.135c-.26.268-.45.6-.663.839-.199.199-.485.267-.797.4-.313.136-.658.269-.864.68-.09.189-.136.394-.132.602 0 .199.027.4.055.536.058.399.116.728.04.97-.249.68-.28 1.145-.106 1.484.174.334.535.47.94.601.81.2 1.91.135 2.774.6.926.466 1.866.67 2.616.47.526-.116.97-.464 1.208-.946.587-.003 1.23-.269 2.26-.334.699-.058 1.574.267 2.577.2.025.134.063.198.114.333l.003.003c.391.778 1.113 1.132 1.884 1.071.771-.06 1.592-.536 2.257-1.306.631-.765 1.683-1.084 2.378-1.503.348-.199.629-.469.649-.853.023-.4-.2-.811-.714-1.376v-.097l-.003-.003c-.17-.2-.25-.535-.338-.926-.085-.401-.182-.786-.492-1.046h-.003c-.059-.054-.123-.067-.188-.135a.357.357 0 00-.19-.064c.431-1.278.264-2.55-.173-3.694-.533-1.41-1.465-2.638-2.175-3.483-.796-1.005-1.576-1.957-1.56-3.368.026-2.152.236-6.133-3.544-6.139zm.529 3.405h.013c.213 0 .396.062.584.198.19.135.33.332.438.533.105.259.158.459.166.724 0-.02.006-.04.006-.06v.105a.086.086 0 01-.004-.021l-.004-.024a1.807 1.807 0 01-.15.706.953.953 0 01-.213.335.71.71 0 00-.088-.042c-.104-.045-.198-.064-.284-.133a1.312 1.312 0 00-.22-.066c.05-.06.146-.133.183-.198.053-.128.082-.264.088-.402v-.02a1.21 1.21 0 00-.061-.4c-.045-.134-.101-.2-.183-.333-.084-.066-.167-.132-.267-.132h-.016c-.093 0-.176.03-.262.132a.8.8 0 00-.205.334 1.18 1.18 0 00-.09.4v.019c.002.089.008.179.02.267-.193-.067-.438-.135-.607-.202a1.635 1.635 0 01-.018-.2v-.02a1.772 1.772 0 01.15-.768c.082-.22.232-.406.43-.533a.985.985 0 01.594-.2zm-2.962.059h.036c.142 0 .27.048.399.135.146.129.264.288.344.465.09.199.14.4.153.667v.004c.007.134.006.2-.002.266v.08c-.03.007-.056.018-.083.024-.152.055-.274.135-.393.2.012-.09.013-.18.003-.267v-.015c-.012-.133-.04-.2-.082-.333a.613.613 0 00-.166-.267.248.248 0 00-.183-.064h-.021c-.071.006-.13.04-.186.132a.552.552 0 00-.12.27.944.944 0 00-.023.33v.015c.012.135.037.2.08.334.046.134.098.2.166.268.01.009.02.018.034.024-.07.057-.117.07-.176.136a.304.304 0 01-.131.068 2.62 2.62 0 01-.275-.402 1.772 1.772 0 01-.155-.667 1.759 1.759 0 01.08-.668 1.43 1.43 0 01.283-.535c.128-.133.26-.2.418-.2zm1.37 1.706c.332 0 .733.065 1.216.399.293.2.523.269 1.052.468h.003c.255.136.405.266.478.399v-.131a.571.571 0 01.016.47c-.123.31-.516.643-1.063.842v.002c-.268.135-.501.333-.775.465-.276.135-.588.292-1.012.267a1.139 1.139 0 01-.448-.067 3.566 3.566 0 01-.322-.198c-.195-.135-.363-.332-.612-.465v-.005h-.005c-.4-.246-.616-.512-.686-.71-.07-.268-.005-.47.193-.6.224-.135.38-.271.483-.336.104-.074.143-.102.176-.131h.002v-.003c.169-.202.436-.47.839-.601.139-.036.294-.065.466-.065zm2.8 2.142c.358 1.417 1.196 3.475 1.735 4.473.286.534.855 1.659 1.102 3.024.156-.005.33.018.513.064.646-1.671-.546-3.467-1.089-3.966-.22-.2-.232-.335-.123-.335.59.534 1.365 1.572 1.646 2.757.13.535.16 1.104.021 1.67.067.028.135.06.205.067 1.032.534 1.413.938 1.23 1.537v-.043c-.06-.003-.12 0-.18 0h-.016c.151-.467-.182-.825-1.065-1.224-.915-.4-1.646-.336-1.77.465-.008.043-.013.066-.018.135-.068.023-.139.053-.209.064-.43.268-.662.669-.793 1.187-.13.533-.17 1.156-.205 1.869v.003c-.02.334-.17.838-.319 1.35-1.5 1.072-3.58 1.538-5.348.334a2.645 2.645 0 00-.402-.533 1.45 1.45 0 00-.275-.333c.182 0 .338-.03.465-.067a.615.615 0 00.314-.334c.108-.267 0-.697-.345-1.163-.345-.467-.931-.995-1.788-1.521-.63-.4-.986-.87-1.15-1.396-.165-.534-.143-1.085-.015-1.645.245-1.07.873-2.11 1.274-2.763.107-.065.037.135-.408.974-.396.751-1.14 2.497-.122 3.854a8.123 8.123 0 01.647-2.876c.564-1.278 1.743-3.504 1.836-5.268.048.036.217.135.289.202.218.133.38.333.59.465.21.201.477.335.876.335.039.003.075.006.11.006.412 0 .73-.134.997-.268.29-.134.52-.334.74-.4h.005c.467-.135.835-.402 1.044-.7zm2.185 8.958c.037.6.343 1.245.882 1.377.588.134 1.434-.333 1.791-.765l.211-.01c.315-.007.577.01.847.268l.003.003c.208.199.305.53.391.876.085.4.154.78.409 1.066.486.527.645.906.636 1.14l.003-.007v.018l-.003-.012c-.015.262-.185.396-.498.595-.63.401-1.746.712-2.457 1.57-.618.737-1.37 1.14-2.036 1.191-.664.053-1.237-.2-1.574-.898l-.005-.003c-.21-.4-.12-1.025.056-1.69.176-.668.428-1.344.463-1.897.037-.714.076-1.335.195-1.814.12-.465.308-.797.641-.984l.045-.022zm-10.814.049h.01c.053 0 .105.005.157.014.376.055.706.333 1.023.752l.91 1.664.003.003c.243.533.754 1.064 1.189 1.637.434.598.77 1.131.729 1.57v.006c-.057.744-.48 1.148-1.125 1.294-.645.135-1.52.002-2.395-.464-.968-.536-2.118-.469-2.857-.602-.369-.066-.61-.2-.723-.4-.11-.2-.113-.602.123-1.23v-.004l.002-.003c.117-.334.03-.752-.027-1.118-.055-.401-.083-.71.043-.94.16-.334.396-.4.69-.533.294-.135.64-.202.915-.47h.002v-.002c.256-.268.445-.601.668-.838.19-.201.38-.336.663-.336zm7.159-9.074c-.435.201-.945.535-1.488.535-.542 0-.97-.267-1.28-.466-.154-.134-.28-.268-.373-.335-.164-.134-.144-.333-.074-.333.109.016.129.134.199.2.096.066.215.2.36.333.292.2.68.467 1.167.467.485 0 1.053-.267 1.3Line truncated

After

Width:  |  Height:  |  Size: 5.3 KiB

+1
View File
@@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Rakuten Kobo</title><path d="M0 13.192h.874v3.115l1.21-1.432h1.047l-1.385 1.619 1.51 2.125H2.21L.889 16.732H.874v1.887H0zm3.177 3.555c0-1.227.575-1.957 1.653-1.957 1.077 0 1.655.73 1.655 1.957s-.575 1.957-1.651 1.957-1.652-.731-1.652-1.957zm1.653 1.341c.627 0 .722-.643.722-1.344 0-.7-.095-1.343-.722-1.343-.628 0-.722.646-.722 1.346s.091 1.341.722 1.341m2.999-2.764h.016a1.09 1.09 0 0 1 .967-.535c.898 0 1.29.763 1.29 1.957 0 1.045-.362 1.957-1.581 1.957-.978 0-1.564-.559-1.564-1.678v-3.83h.872zm.699 2.767c.56 0 .653-.73.653-1.344 0-.676-.07-1.398-.653-1.398-.527 0-.699.771-.699 1.398 0 .935.14 1.344.699 1.344m2.011-1.344c0-1.227.573-1.957 1.65-1.957 1.078 0 1.653.731 1.653 1.957s-.575 1.957-1.653 1.957c-1.077 0-1.65-.73-1.65-1.957m1.655 1.341c.629 0 .724-.643.724-1.344 0-.7-.095-1.343-.724-1.343s-.723.643-.723 1.343c0 .701.094 1.344.723 1.344m7.126-6.807L5.956 12.439 4.8 11.281zM6.141 6.604h.903v3.935h-.903v-.17c-.245.174-.538.27-.839.272-1.018 0-1.791-.928-1.791-2.067s.773-2.068 1.791-2.068c.301.003.594.098.839.273zm-.839 3.088c.502 0 .857-.493.857-1.118s-.355-1.118-.857-1.118-.868.494-.868 1.118c0 .623.366 1.118.868 1.118m8.006-3.088h.894v3.935h-.894v-.14a1.37 1.37 0 0 1-.759.242c-.951 0-1.599-.773-1.599-1.723V6.604h.893v2.314c0 .434.297.801.732.801s.733-.367.733-.801zm8.332 3.935h-.897V6.61h.897v.14c.224-.155.489-.239.761-.244.95 0 1.599.773 1.599 1.723v2.31h-.893v-2.31c0-.434-.298-.8-.733-.8-.434 0-.734.366-.734.8zm-20.705 0H.013V5.627H1.55c.927.006 1.686.77 1.686 1.697 0 .546-.264 1.06-.707 1.378l1.384 1.837H2.741L1.592 9.017H.935zm0-3.982v1.525h.642a.767.767 0 0 0 .764-.763.77.77 0 0 0-.764-.763l-.032.001zm15.679 3.054v.003l.588.785a1.8 1.8 0 0 1-.875.245 1.353 1.353 0 0 1-1.331-1.346l.002-.059v-1.7h-.473v-.935h.473v-.978h.894v.978h.768v.935h-.765v1.686l-.002.043c0 .234.187.43.422.441a.54.54 0 0 0 .299-.098m-7.352-1.16 1.78 2.092H9.789L8.47 8.925v1.618h-.922V5.296h.922v2.68l1.073-1.372h1.253zm9.416-1.947c1.014 0 1.934.931 1.713 2.345h-2.577c.118.9 1.137 1.358 1.748.457l.79.456c-.508.72-1.113.882-1.584.882-.935 0-1.866-.85-1.866-2.07 0-1.16.742-2.07 1.776-2.07m.782 1.561c-.175-.979-1.423-.923-1.612 0z"/></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

+1
View File
@@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Scribd</title><path d="M11.635 0C6.633 0 3.139 3.067 3.139 7.459v.06c.008.085.004.169.004.254 0 1.725.438 3.09 1.253 4.057.815.967 1.885 1.451 3.21 1.451 1.108 0 1.98-.336 2.617-1.008a3.384 3.384 0 0 0 .955-2.412c0-.96-.308-1.715-.926-2.263-.618-.549-1.278-.823-1.979-.823a4.47 4.47 0 0 0-.859.112c-.344.086-.573.129-.687.129-.51 0-.764-.308-.764-.924 0-1.035.516-1.925 1.547-2.67.302-.218.612-.406.927-.568.907-.411 1.988-.637 3.198-.637 2.33 0 4.423.61 6.238 1.816l-1.004 1.602 1.893 1.248 2.205-3.303-.799-.644C17.75.987 14.878 0 11.635 0Zm5.683 10.682c-1.108 0-1.98.336-2.617 1.007a3.384 3.384 0 0 0-.955 2.413c0 .96.31 1.715.928 2.263.617.548 1.276.823 1.976.823.217 0 .503-.038.86-.112.344-.086.572-.129.687-.129.51 0 .764.308.764.924 0 .222-.025.436-.072.645-.849 2.079-3.022 3.267-6.166 3.267-2.671 0-5.233-.848-7.319-2.408l1.233-1.52-1.785-1.39L2.25 19.63l.783.703C5.673 22.698 9.114 24 12.723 24c.402 0 .791-.018 1.172-.05 2.077-.094 4.15-.87 5.61-2.188 1.497-1.35 2.245-3.215 2.245-5.592 0-1.725-.407-3.07-1.223-4.037-.814-.968-1.884-1.451-3.209-1.451z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+1
View File
@@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Windows</title><path d="M0,0H11.377V11.372H0ZM12.623,0H24V11.372H12.623ZM0,12.623H11.377V24H0Zm12.623,0H24V24H12.623"/></svg>

After

Width:  |  Height:  |  Size: 203 B

+58
View File
@@ -0,0 +1,58 @@
---
import "../styles/global.css";
import { site } from "../data/site";
interface Props {
title?: string;
description?: string;
}
const {
title = `${site.name} · ${site.tagline}`,
description = site.description,
} = Astro.props;
const canonical = new URL(Astro.url.pathname, site.domain).href;
const ogImage = new URL("/margin-logo.png", site.domain).href;
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/png" href="/margin-mark.png" />
<link rel="canonical" href={canonical} />
<title>{title}</title>
<meta name="description" content={description} />
<meta property="og:type" content="website" />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:url" content={canonical} />
<meta property="og:image" content={ogImage} />
<meta property="og:site_name" content={site.name} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={ogImage} />
<link rel="preload" href="/fonts/Literata-VF.ttf" as="font" type="font/ttf" crossorigin />
<link rel="preload" href="/fonts/HankenGrotesk-VF.ttf" as="font" type="font/ttf" crossorigin />
<script is:inline>
(function () {
try {
var t = localStorage.getItem("margin-theme");
if (t !== "light" && t !== "dark")
t = matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
document.documentElement.setAttribute("data-theme", t);
} catch (e) {}
})();
</script>
</head>
<body>
<slot />
</body>
</html>
+32
View File
@@ -0,0 +1,32 @@
---
import Base from "../layouts/Base.astro";
import Nav from "../components/Nav.astro";
import Hero from "../components/Hero.astro";
import Privacy from "../components/Privacy.astro";
import Features from "../components/Features.astro";
import Publish from "../components/Publish.astro";
import Proofing from "../components/Proofing.astro";
import Compare from "../components/Compare.astro";
import Pricing from "../components/Pricing.astro";
import Faq from "../components/Faq.astro";
import ClosingCta from "../components/ClosingCta.astro";
import Footer from "../components/Footer.astro";
import DownloadScript from "../components/DownloadScript.astro";
---
<Base>
<Nav />
<main>
<Hero />
<Privacy />
<Features />
<Publish />
<Proofing />
<Compare />
<Pricing />
<Faq />
<ClosingCta />
</main>
<Footer />
<DownloadScript />
</Base>
+288
View File
@@ -0,0 +1,288 @@
@font-face {
font-family: "Literata";
src: url("/fonts/Literata-VF.ttf") format("truetype-variations");
font-weight: 200 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Literata";
src: url("/fonts/Literata-Italic-VF.ttf") format("truetype-variations");
font-weight: 200 900;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: "Hanken Grotesk";
src: url("/fonts/HankenGrotesk-VF.ttf") format("truetype-variations");
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Hanken Grotesk";
src: url("/fonts/HankenGrotesk-Italic-VF.ttf") format("truetype-variations");
font-weight: 100 900;
font-style: italic;
font-display: swap;
}
:root {
--font-ui: "Hanken Grotesk", ui-sans-serif, system-ui, -apple-system, sans-serif;
--font-book: "Literata", Georgia, "Times New Roman", serif;
--r-sm: 6px;
--r-md: 10px;
--r-lg: 16px;
--r-xl: 24px;
--ease: cubic-bezier(0.22, 0.61, 0.36, 1);
--container: 1120px;
}
:root,
:root[data-theme="light"] {
--paper: #fcfbf7;
--shell: #f1ece2;
--sidebar: #ece6da;
--raised: #fbfaf6;
--ink: #23201b;
--ink-soft: #6b6458;
--ink-faint: #9b9484;
--line: #e3ddce;
--line-strong: #d6cfbd;
--accent: #2a2622;
--accent-ink: #100e0b;
--accent-wash: rgba(35, 32, 27, 0.08);
--accent-contrast: #faf7f0;
--selection: rgba(35, 32, 27, 0.14);
--glass: rgba(252, 251, 247, 0.86);
--good: #4f7a52;
--danger: #b4453a;
--shadow-page: 0 1px 2px rgba(35, 32, 27, 0.06), 0 14px 30px rgba(35, 32, 27, 0.09);
--shadow-pop: 0 8px 24px rgba(35, 32, 27, 0.14);
--shadow-deep: 0 30px 70px -20px rgba(35, 32, 27, 0.28);
}
:root[data-theme="dark"] {
--paper: #1d1a16;
--shell: #151310;
--sidebar: #1a1713;
--raised: #232019;
--ink: #ece6da;
--ink-soft: #a89f8e;
--ink-faint: #756d5e;
--line: #2c2823;
--line-strong: #3a352d;
--accent: #efe9dc;
--accent-ink: #ffffff;
--accent-wash: rgba(239, 233, 220, 0.12);
--accent-contrast: #1a1714;
--selection: rgba(239, 233, 220, 0.18);
--glass: rgba(33, 30, 25, 0.88);
--good: #88b08a;
--danger: #d97a6f;
--shadow-page: 0 1px 2px rgba(0, 0, 0, 0.4), 0 14px 34px rgba(0, 0, 0, 0.5);
--shadow-pop: 0 8px 24px rgba(0, 0, 0, 0.55);
--shadow-deep: 0 30px 70px -20px rgba(0, 0, 0, 0.7);
}
* {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
-webkit-text-size-adjust: 100%;
}
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
}
body {
margin: 0;
background: var(--shell);
color: var(--ink);
font-family: var(--font-ui);
font-size: 17px;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
::selection {
background: var(--selection);
}
h1,
h2,
h3 {
font-family: var(--font-book);
font-weight: 560;
letter-spacing: -0.012em;
line-height: 1.08;
margin: 0;
color: var(--ink);
}
h2 {
font-size: clamp(1.9rem, 1.2rem + 2.6vw, 3rem);
}
h3 {
font-size: 1.2rem;
letter-spacing: -0.006em;
}
p {
margin: 0;
}
a {
color: inherit;
text-decoration: none;
}
img {
max-width: 100%;
display: block;
}
.container {
width: 100%;
max-width: var(--container);
margin-inline: auto;
padding-inline: 24px;
}
.section {
padding-block: clamp(64px, 8vw, 120px);
}
.eyebrow {
font-family: var(--font-ui);
font-size: 0.78rem;
font-weight: 650;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--ink-faint);
margin: 0 0 18px;
}
.lede {
font-size: clamp(1.05rem, 0.98rem + 0.4vw, 1.22rem);
color: var(--ink-soft);
line-height: 1.6;
max-width: 46ch;
}
h2 + .lede,
.block-head .lede,
.block-head--center .lede {
margin-top: 20px;
}
.btn {
--bg: var(--accent);
--fg: var(--accent-contrast);
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.55em;
font-family: var(--font-ui);
font-size: 0.98rem;
font-weight: 600;
line-height: 1;
padding: 0.85em 1.4em;
border-radius: 999px;
border: 1px solid transparent;
background: var(--bg);
color: var(--fg);
cursor: pointer;
transition:
transform 0.18s var(--ease),
box-shadow 0.18s var(--ease),
background 0.18s var(--ease),
border-color 0.18s var(--ease);
white-space: nowrap;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-pop);
}
.btn:active {
transform: translateY(0);
}
.btn--ghost {
--bg: transparent;
--fg: var(--ink);
border-color: var(--line-strong);
}
.btn--ghost:hover {
background: var(--accent-wash);
box-shadow: none;
}
.btn--lg {
font-size: 1.05rem;
padding: 1em 1.7em;
}
.btn svg {
width: 1.1em;
height: 1.1em;
}
.card {
background: var(--paper);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--shadow-page);
}
.muted {
color: var(--ink-soft);
}
.book-accent {
font-family: var(--font-book);
font-style: italic;
}
.logo-mark {
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 0;
}
.logo-mark img {
width: 100%;
height: 100%;
display: block;
}
.logo-mark .dark {
display: none;
}
:root[data-theme="dark"] .logo-mark .light {
display: none;
}
:root[data-theme="dark"] .logo-mark .dark {
display: block;
}