Files
TREK/shared/src/book/book-lockups.data.ts
T
Maurice 75d0593198 feat(studio): text effects, fit to frame and 24 lockups in a Text styles shelf
- A text can be cased (capitals, lower case, small caps), placed at the top,
  middle or foot of its frame, outlined, given a hard shadow or a marker band,
  bent on an arc or round a circle, filled with a gallery photo or left
  hollow, and shrunk to fit its frame. Every effect prints as vector: copies
  and strokes for HTML type, textPath and a clip over an <image> for SVG.
- Fitting is solved when the text is edited, in the Studio and in the edit
  engine, and stored; the chosen size is kept as the ceiling.
- Text sizes reach 400 pt and tracking 1.5 em.
- The Elements panel opens on a Text tab: the plain styles and 24 lockups
  (titles, badges and stamps, chapter marks, quotes, captions, places), each
  drawn with the book's own title and placed as one group.
- The assistant sets every effect through style and add, places a lockup with
  add_lockup, reads the effects in describe_book_page and gets two new
  findings (curve_overflow, effect_heavy) beside photo-fill and band checks.
- Book format revision 6.
2026-10-23 22:08:13 +00:00

590 lines
15 KiB
TypeScript

import type {
BookLockup,
BookLockupRole,
BookLockupShapePart,
BookLockupTextPart,
BookLockupTone,
} from './book-lockups';
/**
* The 24 lockups, in the units of a box 100 wide (book-lockups.ts).
*
* Type sizes are chosen against the share of the page a lockup takes, so the
* printed sizes land where the type scale wants them on a 210 mm page: a cover
* title between 70 and 180 pt, spaced capitals at 8 to 11 pt with a quarter to
* half an em of tracking, a title at least three times its subtitle. Every
* frame is a little taller than its line, and the parts never touch unless
* they are meant to layer, so a lockup placed as it comes has no severe finding.
*
* The faces are the book's own (book-fonts.data.ts): condensed and heavy
* poster faces for titles, Montserrat for spaced capitals, a thin serif and its
* italic for quiet titles and quotes, a script for the word laid over a title,
* DM Mono for coordinates.
*/
/** A line of spaced capitals: a date line, a kicker, a year. */
function spaced(
role: BookLockupRole,
y: number,
size: number,
tone: BookLockupTone = 'muted',
more: Partial<BookLockupTextPart> = {},
): BookLockupTextPart {
return {
kind: 'text',
role,
x: 0,
y,
w: 100,
h: size * 2,
font: 'montserrat',
size,
weight: 600,
tracking: 0.4,
transform: 'upper',
tone,
...more,
};
}
/** A filled bar: a rule when it is thin, a band when it is not. */
function bar(x: number, y: number, w: number, h: number, fill: BookLockupTone = 'ink'): BookLockupShapePart {
return { kind: 'shape', shape: 'rect', x, y, w, h, fill };
}
/** A circle drawn as a line only. */
function ring(inset: number, stroke: BookLockupTone, strokeWidth: number, size = 100): BookLockupShapePart {
const side = size - inset * 2;
return { kind: 'shape', shape: 'ellipse', x: inset, y: inset, w: side, h: side, fill: null, stroke, strokeWidth };
}
/** A condensed poster face, Bebas unless the part says another: capitals, tight lines. */
function poster(
role: BookLockupRole,
y: number,
size: number,
more: Partial<BookLockupTextPart> = {},
): BookLockupTextPart {
return {
kind: 'text',
role,
x: 0,
y,
w: 100,
h: size * 1.05,
font: 'bebas',
size,
leading: 0.9,
tracking: 0.02,
transform: 'upper',
tone: 'ink',
...more,
};
}
export const BOOK_LOCKUPS: readonly BookLockup[] = [
{
id: 'poster-stack',
descr: 'Heavy poster title with a hard accent shadow, a subtitle on an accent band, the year under it',
height: 62,
share: 0.8,
parts: [
poster('title', 0, 34, { font: 'anton', shadow: { dx: 0.9, dy: 0.9, tone: 'accent' } }),
poster('subtitle', 38, 7.5, {
h: 11,
tracking: 0.3,
tone: 'paper',
highlight: { tone: 'accent', padX: 2.4, padY: 0.5 },
}),
spaced('year', 54, 2.4, 'ink', { h: 6 }),
],
},
{
id: 'spaced-date',
descr: 'A spaced date line over a condensed title, a short accent rule and the place',
height: 50,
share: 0.75,
parts: [
spaced('dates', 0, 2.4, 'muted', { h: 6 }),
poster('title', 7, 30, { font: 'league', tracking: 0.04 }),
bar(42, 40.5, 16, 0.5, 'accent'),
spaced('place', 43.5, 2.4, 'ink', { h: 6, tracking: 0.3 }),
],
},
{
id: 'serif-sun',
descr: 'A large thin serif title, an italic subtitle, a sun between two hairlines and the year',
height: 64,
share: 0.8,
parts: [
{
kind: 'text',
role: 'title',
x: 0,
y: 0,
w: 100,
h: 28,
font: 'cormorant',
size: 26,
weight: 300,
leading: 1,
tracking: -0.01,
tone: 'ink',
},
{
kind: 'text',
role: 'subtitle',
x: 0,
y: 29,
w: 100,
h: 10,
font: 'cormorant',
size: 7.5,
italic: true,
tone: 'ink',
},
bar(18, 46.8, 26, 0.3),
{ kind: 'shape', shape: 'sun', x: 46, y: 43, w: 8, h: 8, fill: 'accent' },
bar(56, 46.8, 26, 0.3),
spaced('year', 55, 2.2, 'muted', { h: 6, tracking: 0.5 }),
],
},
{
id: 'title-script-year',
descr: 'A bold serif title, an accent script subtitle turned across its foot, the year in spaced capitals',
height: 50,
share: 0.75,
parts: [
{
kind: 'text',
role: 'title',
x: 0,
y: 0,
w: 100,
h: 25,
font: 'playfair',
size: 22,
weight: 700,
leading: 0.95,
tone: 'ink',
},
{
kind: 'text',
role: 'subtitle',
x: 12,
y: 24,
w: 76,
h: 13,
rotation: -4,
font: 'vibes',
size: 12,
italic: false,
tone: 'accent',
},
spaced('year', 42, 2.4, 'muted', { h: 6, tracking: 0.45 }),
],
},
{
id: 'circle-badge',
descr: 'A round accent badge: a kicker run round its rim inside a paper ring, the title and the year in the middle',
height: 100,
share: 0.45,
parts: [
{ kind: 'shape', shape: 'ellipse', x: 0, y: 0, w: 100, h: 100, fill: 'accent' },
ring(5, 'paper', 0.7),
spaced('kicker', 0, 6, 'paper', {
x: 7,
y: 7,
w: 86,
h: 86,
tracking: 0.25,
curve: { type: 'circle', bend: 0, start: 0 },
}),
poster('title', 36, 22, { x: 16, w: 68, h: 24, tone: 'paper', tracking: 0.04 }),
spaced('year', 62, 4.4, 'paper', { x: 25, w: 50, h: 8, tracking: 0.3 }),
],
},
{
id: 'postmark',
descr: 'A round postmark: the place round its rim between two rings, the date in the middle over a cancel wave',
height: 100,
share: 0.42,
parts: [
ring(0.5, 'ink', 1),
ring(14, 'ink', 0.5),
poster('place', 0, 12, {
x: 1,
y: 1,
w: 98,
h: 98,
tracking: 0.16,
curve: { type: 'circle', bend: 0, start: 0 },
}),
poster('dates', 38, 10, { x: 20, w: 60, h: 11, tracking: 0.08 }),
{ kind: 'shape', shape: 'wave', x: 22, y: 53, w: 56, h: 8, fill: 'ink' },
spaced('year', 63, 4.4, 'ink', { x: 25, w: 50, h: 8, tracking: 0.3 }),
],
},
{
id: 'script-overlay',
descr: 'A spaced poster title with a large accent script word turned over it, outlined in paper',
height: 58,
share: 0.8,
parts: [
poster('title', 22, 32, { tracking: 0.1 }),
{
kind: 'text',
role: 'overlay',
x: 8,
y: 0,
w: 84,
h: 30,
rotation: -6,
font: 'yellowtail',
size: 24,
weight: 400,
italic: false,
tone: 'accent',
outline: { tone: 'paper', width: 0.6 },
},
],
},
{
id: 'photo-word',
descr: 'A huge poster word filled with a photo, a spaced kicker over it',
height: 50,
share: 0.85,
parts: [
spaced('kicker', 0, 2.4, 'ink', { h: 6, tracking: 0.45 }),
poster('title', 7, 40, { h: 42, font: 'anton', fill: 'photo' }),
],
},
{
id: 'fabric-band',
descr: 'A poster title in paper on a full accent band, a script subtitle under it',
height: 44,
share: 0.8,
parts: [
bar(0, 0, 100, 26, 'accent'),
poster('title', 4, 18, { h: 18, tracking: 0.14, tone: 'paper' }),
{
kind: 'text',
role: 'subtitle',
x: 0,
y: 29,
w: 100,
h: 13,
font: 'satisfy',
size: 7,
italic: false,
tone: 'ink',
},
],
},
{
id: 'quote-classic',
descr:
'A large accent quotation mark, the quote in a serif italic, an accent rule and the source in spaced capitals',
height: 56,
share: 0.6,
parts: [
{
kind: 'text',
role: 'quote',
literal: '“',
x: 0,
y: 0,
w: 22,
h: 22,
font: 'playfair',
size: 26,
weight: 700,
leading: 0.8,
align: 'left',
tone: 'accent',
},
{
kind: 'text',
role: 'quote',
x: 0,
y: 23,
w: 100,
h: 24,
font: 'cormorant',
size: 6.5,
italic: true,
leading: 1.3,
align: 'left',
tone: 'ink',
},
bar(0, 50.8, 9, 0.4, 'accent'),
spaced('caption', 48, 2.8, 'muted', { x: 12, w: 88, h: 6, align: 'left', tracking: 0.3 }),
],
},
{
id: 'quote-band',
descr: 'A quote set in paper on ink bands behind each line, the source under it',
height: 42,
share: 0.7,
parts: [
{
kind: 'text',
role: 'quote',
x: 0,
y: 0,
w: 100,
h: 32,
font: 'sans',
size: 5,
weight: 600,
leading: 1.6,
align: 'left',
tone: 'paper',
highlight: { tone: 'ink', padX: 1.4, padY: 0.4 },
},
spaced('caption', 35, 2.6, 'muted', { h: 6, align: 'left', tracking: 0.3 }),
],
},
{
id: 'caption-rule',
descr: 'A caption in spaced capitals after a short rule',
height: 8,
share: 0.5,
parts: [
bar(0, 3.85, 8, 0.3),
spaced('caption', 0, 3.1, 'ink', { x: 11, w: 89, h: 8, align: 'left', tracking: 0.3 }),
],
},
{
id: 'caption-number',
descr: 'An accent figure beside a caption in a serif italic',
height: 16,
share: 0.5,
parts: [
poster('number', 0, 14, { w: 16, h: 16, align: 'left', tone: 'accent' }),
{
kind: 'text',
role: 'caption',
x: 18,
y: 1,
w: 82,
h: 14,
font: 'cormorant',
size: 4.8,
italic: true,
leading: 1.3,
align: 'left',
tone: 'ink',
},
],
},
{
id: 'ticket',
descr: 'A ticket outline holding a kicker, the place in poster capitals and the dates',
height: 40,
share: 0.55,
parts: [
{ kind: 'shape', shape: 'ticket', x: 0, y: 0, w: 100, h: 40, fill: null, stroke: 'ink', strokeWidth: 0.5 },
spaced('kicker', 7, 2.8, 'muted', { x: 12, w: 76, h: 5 }),
poster('place', 12, 15, { x: 12, w: 76, h: 16, tracking: 0.08 }),
spaced('dates', 28.5, 2.8, 'ink', { x: 12, w: 76, h: 5, font: 'mono', weight: 500, tracking: 0.12 }),
],
},
{
id: 'coords-pin',
descr: 'Coordinates in spaced figures after an accent map pin',
height: 10,
share: 0.5,
parts: [
{ kind: 'shape', shape: 'pin', x: 0, y: 1, w: 6, h: 8, fill: 'accent' },
spaced('coords', 0, 3.2, 'ink', {
x: 9,
w: 91,
h: 10,
align: 'left',
font: 'mono',
weight: 400,
tracking: 0.12,
transform: 'none',
}),
],
},
{
id: 'coords-stack',
descr: 'The place in poster capitals, an accent rule and the coordinates under it',
height: 30,
share: 0.5,
parts: [
poster('place', 0, 15, { h: 16, tracking: 0.1 }),
bar(42, 17.5, 16, 0.4, 'accent'),
spaced('coords', 21, 3, 'muted', { h: 7, font: 'mono', weight: 400, tracking: 0.08, transform: 'none' }),
],
},
{
id: 'chapter-outline',
descr: 'A huge hollow outlined figure, a spaced accent label under it and the title in a serif italic',
height: 62,
share: 0.45,
parts: [
poster('number', 0, 42, { h: 42, fill: { tone: 'ink', alpha: 0 }, outline: { tone: 'accent', width: 0.5 } }),
spaced('label', 43, 3.6, 'accent', { h: 7, tracking: 0.5 }),
{
kind: 'text',
role: 'title',
x: 0,
y: 51,
w: 100,
h: 11,
font: 'cormorant',
size: 8,
italic: true,
tone: 'ink',
},
],
},
{
id: 'chapter-rules',
descr: 'A serif figure between two hairlines, the title in spaced capitals under it',
height: 34,
share: 0.5,
parts: [
bar(0, 12.8, 32, 0.35),
{ kind: 'text', role: 'number', x: 34, y: 2, w: 32, h: 22, font: 'bodoni', size: 17, leading: 1, tone: 'ink' },
bar(68, 12.8, 32, 0.35),
spaced('title', 26, 3.4, 'ink', { h: 7 }),
],
},
{
id: 'day-opener',
descr: 'A spaced label, a huge accent serif figure and the place in poster capitals, set flush left',
height: 58,
share: 0.5,
parts: [
spaced('label', 0, 3.4, 'muted', { h: 7, align: 'left', tracking: 0.5 }),
{
kind: 'text',
role: 'number',
x: 0,
y: 7,
w: 100,
h: 37,
font: 'fraunces',
size: 40,
weight: 800,
leading: 0.85,
align: 'left',
tone: 'accent',
},
poster('place', 46, 10, { h: 11, align: 'left', tracking: 0.14 }),
],
},
{
id: 'stamp',
descr: 'A rubber stamp set at an angle: a double accent frame round the place and the dates',
height: 44,
share: 0.45,
rotation: -7,
parts: [
{
kind: 'shape',
shape: 'rect',
x: 0,
y: 0,
w: 100,
h: 44,
fill: null,
stroke: 'accent',
strokeWidth: 1,
radius: 2,
},
{
kind: 'shape',
shape: 'rect',
x: 3.5,
y: 3.5,
w: 93,
h: 37,
fill: null,
stroke: 'accent',
strokeWidth: 0.35,
radius: 1,
},
poster('place', 8, 17, { x: 7, w: 86, h: 18, tracking: 0.12, tone: 'accent' }),
spaced('dates', 28, 3.4, 'accent', { x: 7, w: 86, h: 7, tracking: 0.35 }),
],
},
{
id: 'arched-title',
descr: 'A poster title arched over a script subtitle, a sparkle at each side',
height: 58,
share: 0.75,
parts: [
poster('title', 0, 20, { h: 42, tracking: 0.08, curve: { type: 'arc', bend: 42, start: 0 } }),
{ kind: 'shape', shape: 'sparkle', x: 6, y: 43, w: 7, h: 7, fill: 'accent' },
{
kind: 'text',
role: 'subtitle',
x: 16,
y: 41,
w: 68,
h: 12,
font: 'dancing',
size: 7,
italic: false,
weight: 500,
tone: 'ink',
},
{ kind: 'shape', shape: 'sparkle', x: 87, y: 43, w: 7, h: 7, fill: 'accent' },
],
},
{
id: 'retro-shadow',
descr: 'A heavy retro title in the accent, outlined in ink with a hard ink shadow, a subtitle and the year',
height: 48,
share: 0.75,
parts: [
{
kind: 'text',
role: 'title',
x: 0,
y: 0,
w: 100,
h: 28,
font: 'shrikhand',
size: 21,
weight: 400,
leading: 0.95,
tracking: -0.01,
tone: 'accent',
outline: { tone: 'ink', width: 0.5 },
shadow: { dx: 1.3, dy: 1.3, tone: 'ink' },
},
spaced('subtitle', 32, 2.8, 'ink', { h: 7, tracking: 0.35 }),
spaced('year', 40, 2.4, 'muted', { h: 7, tracking: 0.35 }),
],
},
{
id: 'outline-echo',
descr: 'The title three times in poster capitals: two hollow outlines over the solid word, the year under it',
height: 66,
share: 0.7,
parts: [
poster('title', 0, 17, { h: 18, fill: { tone: 'ink', alpha: 0 }, outline: { tone: 'ink', width: 0.3 } }),
poster('title', 19, 17, { h: 18, fill: { tone: 'ink', alpha: 0 }, outline: { tone: 'ink', width: 0.3 } }),
poster('title', 38, 17, { h: 18 }),
spaced('year', 59, 2.6, 'muted', { h: 6, tracking: 0.45 }),
],
},
{
id: 'minimal-caps',
descr: 'A title in widely spaced light capitals, a hairline and the year: quiet and elegant',
height: 30,
share: 0.7,
parts: [
spaced('title', 0, 8, 'ink', { h: 13, weight: 300, tracking: 0.55 }),
bar(44, 16.5, 12, 0.3),
spaced('year', 21, 2.6, 'muted', { h: 7, weight: 400, tracking: 0.5 }),
],
},
];