The licence
The licence page shows the terms Loom is released under, set to be read rather than scrolled past. It opens on a plain-language account of what the licence lets a reader do, then gives the licence itself in full, centred on the page in a single column.
The page holds no copy of the licence. It reads the repository's own LICENSE file when the site is built, so the page and the file a checkout carries can never say different things. Nothing on the page answers a click with a message, so it builds with Foldkit's inert builder, ih.
What the licence says, plainly
The head is the Loom mark beside the page's name, as on the other pages past the landing, without the two link cards those pages carry. Under it, a few sentences say what the MIT licence means in practice. The licence's own words are exact but dense, and a reader deciding whether they may use Loom deserves the answer first.
const licenseHead = (): Html =>
ih.div(
[ih.Class('section section-hero page-hero'), ih.Id('license-top')],
[
ih.div(
[ih.Class('license-column')],
[
ih.div([ih.Class('page-title')], [loomMark('paper', 0.8), ih.h1([], ['License'])]),
ih.div(
[ih.Class('license-plain')],
Array.map(plainly, (paragraph) => ih.p([ih.Class('prose')], [paragraph])),
),
],
),
],
)
const plainly: ReadonlyArray<string> = [
'Loom is free software under the MIT licence. You may use it, copy it, change it and share it, in any project, open or closed, paid or free.',
'The one condition is that the copyright notice and the permission below travel with every copy. Loom comes with no warranty: if it breaks, the licence holds nobody liable.',
'The licence covers Loom: this repository and every package it publishes.',
]The licence in full
The licence file is plain text, hard-wrapped to fit a terminal. Its first line is its name, the next block is the copyright notice, and blank lines divide the rest into paragraphs. licenseParts reads it that way. It splits the file at blank lines, rejoins each paragraph's wrapped lines into one, and takes the first paragraph as the title and the second as the notice. Rejoined, the paragraphs reflow to the page's measure, so they read as prose rather than as a terminal's column.
type LicenseParts = {
readonly title: string
readonly notice: string
readonly terms: ReadonlyArray<string>
}
const licenseParts = (text: string): LicenseParts => {
const paragraphs = pipe(
text.split(/\n\s*\n/),
Array.map((paragraph) =>
pipe(
paragraph.split('\n'),
Array.map((line) => line.trim()),
Array.join(' '),
).trim(),
),
Array.filter((paragraph) => paragraph !== ''),
)
return {
title: Option.getOrElse(Array.get(paragraphs, 0), () => 'License'),
notice: Option.getOrElse(Array.get(paragraphs, 1), () => ''),
terms: Array.drop(paragraphs, 2),
}
}Both sections hold their content in one centred column, the width of a line of running text, so the page reads like a printed document rather than a page laid out for scanning. The licence sits in a section of the landing's kind, under a mark woven like a page of text: cream lines on the navy ground, with amber rules where the writing runs. The licence's name is the heading and the copyright notice sits under it in muted ink. The terms follow at the site's one size for running text. The warranty paragraph is in capitals, as the licence writes it. A reader gets the licence word for word, so the page changes nothing but the line breaks.
const licenseCarpet: Carpet = {
rows: ['.ppppp.', '.paaap.', '.ppppp.', '.paaap.', '.ppppp.', '.paap..', '.ppp...'],
threads: { '.': NAVY, p: '#F2EFE6', a: '#FFC53D' },
}
const licenseText = (): Html => {
const parts = licenseParts(licenseFile)
return ih.div(
[ih.Class('section'), ih.Id('license-text')],
[
ih.div(
[ih.Class('license-column')],
[
ih.div(
[ih.Class('section-head')],
[
carpetMark(licenseCarpet, '42px', broadSeam),
ih.div(
[ih.Class('lines')],
[ih.span([ih.Class('eyebrow')], ['The terms']), ih.h2([], [parts.title])],
),
],
),
ih.div(
[ih.Class('license-body')],
[
ih.p([ih.Class('prose license-notice')], [parts.notice]),
...Array.map(parts.terms, (term) => ih.p([ih.Class('prose')], [term])),
],
),
],
),
],
)
}
export const license = (): ReadonlyArray<Html> => [licenseHead(), licenseText()]What this module draws on
Effect lends Array, Option and pipe to split the file and read its parts. Foldkit lends the inert builder and the Html type. The components lend the carpet machinery and the Loom mark. The licence itself comes from the repository's LICENSE file, imported as text, which both Vite and Bun read with the ?raw suffix.
import { Array, Option, pipe } from 'effect'
import { type Html, inertHtml as ih } from 'foldkit/html'
import { broadSeam, type Carpet, carpetMark, loomMark } from './components'
import licenseFile from '../../LICENSE?raw'
const NAVY = '#14204A'