From ea35d0c8ad6006da214e0bc8a0718ded5e2f606d Mon Sep 17 00:00:00 2001 From: Marco Lecheler Date: Sun, 1 Nov 2026 16:41:52 +0100 Subject: [PATCH] feat: add landing page to the docs site The docs index moves to /overview/ so the site root can introduce the product: what it is, how it works in three steps and where to start. --- docs/README.md | 4 + website/scripts/sync-brand.mjs | 2 + website/src/pages/index.js | 178 +++++++++++++++++++++ website/src/pages/index.module.css | 249 +++++++++++++++++++++++++++++ 4 files changed, 433 insertions(+) create mode 100644 website/src/pages/index.js create mode 100644 website/src/pages/index.module.css diff --git a/docs/README.md b/docs/README.md index 59e7347..1279f55 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,3 +1,7 @@ +--- +slug: /overview +--- + # Documentation ## Using and running it diff --git a/website/scripts/sync-brand.mjs b/website/scripts/sync-brand.mjs index a8e8325..674f94e 100644 --- a/website/scripts/sync-brand.mjs +++ b/website/scripts/sync-brand.mjs @@ -12,6 +12,8 @@ const out = join(dirname(fileURLToPath(import.meta.url)), '..', 'static', 'brand const files = [ 'svg/outprint-lockup.svg', 'svg/outprint-lockup-dark.svg', + 'svg/outprint-banner.svg', + 'svg/outprint-banner-dark.svg', 'favicon/favicon.svg', 'favicon/favicon.ico', 'favicon/apple-touch-icon.png', diff --git a/website/src/pages/index.js b/website/src/pages/index.js new file mode 100644 index 0000000..ec694aa --- /dev/null +++ b/website/src/pages/index.js @@ -0,0 +1,178 @@ +import React from 'react'; +import Link from '@docusaurus/Link'; +import Layout from '@theme/Layout'; +import ThemedImage from '@theme/ThemedImage'; +import useBaseUrl from '@docusaurus/useBaseUrl'; + +import styles from './index.module.css'; + +const METADATA_EXAMPLE = [ + ['type', 'letter'], + ['subject', 'Spring plant sale'], + ['reference', 'PS-2026-03'], + ['recipient', null], + ['', ' - Ada Example'], + ['', ' - 1 Sample Lane'], + ['', ' - 12345 Exampleton'], + ['closing', 'With best wishes'], +]; + +const STEPS = [ + { + title: 'Pick a Template', + text: 'A Template provides the look and structure: a letter, a report, or your own design in LaTeX.', + }, + { + title: 'Bind it to a Collection', + text: 'A Collection admin pairs the Template with a Configuration that holds the organisation name, logo and colours.', + }, + { + title: 'Write and download', + text: 'Authors fill a few fields at the top of a Document and click Download PDF.', + }, +]; + +const FEATURES = [ + { + title: 'No Outline fork', + text: 'A small reverse proxy adds the Download PDF button. Outline itself stays untouched and upgradable.', + }, + { + title: 'Real typesetting', + text: 'Pandoc and LuaLaTeX turn the Document into a PDF, so letters and reports look like they were set, not exported.', + }, + { + title: 'Content apart from branding', + text: 'Names, logos and colours live in the Configuration. Authors write content and cannot override them.', + }, + { + title: 'Renders in a sandbox', + text: 'The Worker that runs LaTeX has no network and no secrets. It only turns a bundle into a PDF.', + }, +]; + +const PATHS = [ + { + to: '/metadata/', + title: 'I write Documents', + text: 'The fields at the top of a Document, which values win, and what the errors mean.', + }, + { + to: '/deploy-compose/', + title: 'I run Outline', + text: 'Deploy the Gateway, the Worker and the injector next to your Outline with Docker Compose.', + }, + { + to: '/template-author-guide/', + title: 'I design Templates', + text: 'Package format, fields, validation and a worked example, from first file to golden test.', + }, +]; + +function DocumentCard() { + return ( +
+
Top of an Outline document
+
+        
+          ```yaml
+          {'\n'}
+          {METADATA_EXAMPLE.map(([key, value], index) => (
+            
+              {key ? {key}: : null}
+              {value ? {key ? ' ' : ''}{value} : null}
+              {'\n'}
+            
+          ))}
+          ```
+          {'\n'}
+          Thank you for your enquiry ...
+        
+      
+
+ ); +} + +export default function Home() { + return ( + +
+
+
+

+ +

+

+ Turn Outline documents into typeset PDFs: letters, + reports or your own LaTeX design. Authors write in Outline and click{' '} + Download PDF. You host it yourself, and Outline is not forked. +

+
+ + Read the documentation + + + View on GitHub + +
+
+ +
+
+ +
+
+

+ From Outline document to PDF in three steps +

+
    + {STEPS.map((step) => ( +
  1. +

    {step.title}

    +

    {step.text}

    +
  2. + ))} +
+
+ +
+

+ Built to be run by a small team +

+
+ {FEATURES.map((feature) => ( +
+
{feature.title}
+
{feature.text}
+
+ ))} +
+
+ +
+

+ Where to start +

+
    + {PATHS.map((path) => ( +
  • + + {path.title} + {path.text} + +
  • + ))} +
+
+
+
+ ); +} diff --git a/website/src/pages/index.module.css b/website/src/pages/index.module.css new file mode 100644 index 0000000..bdc9293 --- /dev/null +++ b/website/src/pages/index.module.css @@ -0,0 +1,249 @@ +/* Landing page. Uses the --op-* tokens from custom.css, so light and dark + follow the site theme. The folded corner on the document card repeats the + mark in the logo. */ + +.hero { + border-bottom: 1px solid var(--op-border); + padding: clamp(2.5rem, 6vw, 5.5rem) 1rem; +} + +.heroInner { + display: grid; + grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr); + gap: clamp(2rem, 5vw, 4.5rem); + align-items: center; + max-width: 1160px; + margin: 0 auto; +} + +.banner { + margin: 0 0 1.5rem; + line-height: 0; +} +.banner img { + display: block; + width: min(100%, 440px); + height: auto; +} + +.lead { + max-width: 34rem; + margin: 0 0 2rem; + font-size: clamp(1.1rem, 1rem + 0.4vw, 1.3rem); + line-height: 1.6; + color: var(--op-muted-text); +} +.lead strong { + color: var(--op-text); + font-weight: 500; +} + +.actions { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; +} + +.primary, +.secondary { + display: inline-block; + padding: 0.7rem 1.25rem; + border-radius: 6px; + font-weight: 500; + text-decoration: none; + transition: background-color 150ms ease, border-color 150ms ease; +} +.primary { + background: var(--op-accent); + border: 1px solid var(--op-accent); + color: #0b2616; +} +.primary:hover { + background: #52d78c; + border-color: #52d78c; + color: #0b2616; + text-decoration: none; +} +.secondary { + border: 1px solid var(--op-border); + background: var(--op-card); + color: var(--op-text); +} +.secondary:hover { + border-color: var(--op-muted); + color: var(--op-text); + text-decoration: none; +} + +/* The document card */ +.document { + position: relative; + margin: 0; + padding: 1.25rem 1.5rem 1.5rem; + background: var(--op-card); + border: 1px solid var(--op-border); + border-radius: 10px; + clip-path: polygon(0 0, calc(100% - 56px) 0, 100% 56px, 100% 100%, 0 100%); +} +.document::after { + content: ''; + position: absolute; + top: 0; + right: 0; + width: 56px; + height: 56px; + background: var(--op-accent); + clip-path: polygon(0 0, 0 100%, 100% 100%); +} + +.documentCaption { + margin-bottom: 0.75rem; + font-size: 0.875rem; + color: var(--op-muted-text); +} + +.documentCode { + margin: 0; + padding: 0; + border: 0; + background: transparent; + font-size: 0.9rem; + line-height: 1.7; + overflow-x: auto; +} +.documentCode code { + border: 0; + background: transparent; + padding: 0; +} + +.fence { + color: var(--op-muted-text); +} +.key { + color: var(--op-link); + font-weight: 500; +} +.value { + color: var(--op-text); +} +.body { + display: inline-block; + margin-top: 0.5rem; + font-family: var(--ifm-font-family-base); + color: var(--op-text); +} + +/* Content sections */ +.section { + max-width: 1160px; + margin: 0 auto; + padding: clamp(2.5rem, 5vw, 4rem) 1rem 0; +} +.section:last-child { + padding-bottom: clamp(3rem, 6vw, 5rem); +} + +.sectionTitle { + max-width: 28rem; + margin: 0 0 1.75rem; + font-size: clamp(1.5rem, 1.2rem + 1vw, 2rem); + line-height: 1.25; +} + +.steps { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 1.5rem; + margin: 0; + padding: 0; + list-style: none; + counter-reset: step; +} +.step { + position: relative; + padding-top: 1rem; + border-top: 2px solid var(--op-text); + counter-increment: step; +} +.step::before { + content: counter(step); + display: block; + margin-bottom: 0.5rem; + font-size: 2.25rem; + font-weight: 700; + line-height: 1; + color: var(--op-link); +} +.step h3 { + margin: 0 0 0.35rem; + font-size: 1.15rem; +} +.step p { + margin: 0; + max-width: 24rem; + color: var(--op-muted-text); +} + +.features { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 2rem 3rem; + margin: 0; +} +.feature dt { + font-size: 1.1rem; + font-weight: 700; +} +.feature dd { + margin: 0.3rem 0 0; + max-width: 30rem; + color: var(--op-muted-text); +} + +.paths { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 1rem; + margin: 0; + padding: 0; + list-style: none; +} +.path { + display: flex; + flex-direction: column; + gap: 0.35rem; + height: 100%; + padding: 1.25rem; + background: var(--op-card); + border: 1px solid var(--op-border); + border-radius: 6px; + color: var(--op-text); + text-decoration: none; + transition: border-color 150ms ease; +} +.path:hover { + border-color: var(--op-accent); + color: var(--op-text); + text-decoration: none; +} +.pathTitle { + font-size: 1.1rem; + font-weight: 700; +} +.pathText { + color: var(--op-muted-text); +} + +@media (max-width: 996px) { + .heroInner { + grid-template-columns: minmax(0, 1fr); + } + .steps, + .paths { + grid-template-columns: minmax(0, 1fr); + } + .features { + grid-template-columns: minmax(0, 1fr); + } +}