En poques paraules
Un diari local a l'estil dels anys trenta en què cada foto és una pel·lícula muda i curta. En paper són imatges fixes; al visor 3D es mouen totes alhora, una vegada i una altra, com un diari de conte.
Què compondràs
The Wexcombe Lantern, un setmanari costaner inventat de juny de 1933, en una pàgina tabloide de 280 × 430 mm a quatre columnes amb filet: una capçalera estreta, una notícia principal amb la seva foto a tres columnes, una altra a dues, taules de marees, anuncis per paraules i una pàgina d'esports. Cada foto és una pel·lícula de cinc segons en blanc i negre. A l'imprès, i a les pàgines planes sota el visor, cadascuna és el seu primer fotograma. Quan s'obre a Folio com un diari doblegat, sobre paper premsa clar, les fotos de les pàgines obertes es mouen alhora i no s'aturen: l'alcaldessa parla, l'aviadora saluda, el pescador riu, el porter es llança i es torna a aixecar. La pàgina no dibuixa res; la maquetació és la d'un diari corrent i els clips fan la resta.
Aquesta recepta respon a
- Com faig que diversos vídeos curts sense so es reprodueixin alhora en una pàgina i tornin a començar cada cop que s'hi passa?
- Com mostro un diari al visor 3D com a plecs doblegats, amb les pàgines d'economia en paper salmó?
La resposta curta
// A video resource prints its poster (the first frame) on the flat page. In Folio, and in the
// HTML viewer, the poster comes alive: autoplay starts it muted when its page is on show, loop
// keeps it going, and exclusive: false lets it run alongside the other clips instead of
// pausing them, as one exclusive player at a time would.
const videoStyle = {
playMark: { enabled: false }, // a press photograph, not a play button
qr: { enabled: false }, // the clips live on the page, not behind a link
linkPoster: false,
player: { autoplay: true, loop: true, muted: true, exclusive: false, controls: false },
};
const clip = (id, url, [w, h], placement, caption, altText) => ({ id, typeId: 'picture',
kind: 'video', createdAt: 0, updatedAt: 0, caption, altText, placement,
video: { source: 'file', fileId: `${id}-loop.mp4`, url, // url: where the clip plays from
format: 'mp4', width: w, height: h, duration: 5,
poster: { fileId: `${id}-poster.jpg`, format: 'jpeg', width: w, height: h } } });
Ingredients
- Funcions
- Vídeos que es reprodueixen alhoraTres o més columnesVídeos com a recursosPortades de vídeo: marca de reproducció i codi QRFlotants a diverses columnesFormats de diariDiari plegat (Folio)Visor de llibre en 3D (Folio)Paper, enquadernació, taula i llum (Folio)Obertures dissenyadesEstils de títolAtributs de títolTextos, filets i caixes als dissenys de pàginaCapçaleres i folisFilet de columnaRequadresColumnes dins d'un requadreEstils de paràgrafLínies de font i crèdit
- També fa servir
- Requadres flotantsFigures just aquíBanda de capítol d'amplada completaColor del paperTipus de recurs propisFigures i taules com a recursosCapçaleres per seccióRequadres a tota l'amplada
- Tipografia
- Libre Caslon Text, Oswald, Courier Prime (SIL OFL 1.1)
- Recursos
actress-loop.mp4actress-poster.jpgaviator-loop.mp4aviator-poster.jpgclock-loop.mp4clock-poster.jpgfisherman-loop.mp4fisherman-poster.jpgkeeper-loop.mp4keeper-poster.jpgmayor-loop.mp4mayor-poster.jpg- Mrs Edith Rowe speaks at the opening of the Harbour line: a black-and-white loop of 5 s, a still generated from a text prompt and animated with the same first and last frame, encoded with ffmpeg (Generated With Diffusion Models, CC BY 4.0)
- The first frame of the mayor loop, its printed picture (Generated With Diffusion Models, CC BY 4.0)
- The Guildhall clock tower: a black-and-white loop of 5 s, a still generated from a text prompt and animated with the same first and last frame, encoded with ffmpeg (Generated With Diffusion Models, CC BY 4.0)
- The first frame of the clock loop, its printed picture (Generated With Diffusion Models, CC BY 4.0)
- Skipper Jacob Hosking with the cod: a black-and-white loop of 5 s, a still generated from a text prompt and animated with the same first and last frame, encoded with ffmpeg (Generated With Diffusion Models, CC BY 4.0)
- The first frame of the fisherman loop, its printed picture (Generated With Diffusion Models, CC BY 4.0)
- Miss Constance Lacey in the cockpit: a black-and-white loop of 5 s, a still generated from a text prompt and animated with the same first and last frame, encoded with ffmpeg (Generated With Diffusion Models, CC BY 4.0)
- The first frame of the aviator loop, its printed picture (Generated With Diffusion Models, CC BY 4.0)
- Miss Rosalind Avery on the stage: a black-and-white loop of 5 s, a still generated from a text prompt and animated with the same first and last frame, encoded with ffmpeg (Generated With Diffusion Models, CC BY 4.0)
- The first frame of the actress loop, its printed picture (Generated With Diffusion Models, CC BY 4.0)
- Tom Rundle in goal: a black-and-white loop of 5 s, a still generated from a text prompt and animated with the same first and last frame, encoded with ffmpeg (Generated With Diffusion Models, CC BY 4.0)
- The first frame of the keeper loop, its printed picture (Generated With Diffusion Models, CC BY 4.0)
Elaboració
#1 · Fes de cada foto un clip que es repeteix amb els altres
El codi és la resposta curta de dalt. Un recurs de vídeo imprimeix la seva portada com una imatge, així que els clips ocupen el lloc de les fotografies i porten peu com elles. Tres opcions del reproductor donen vida a la pàgina: autoplay engega un clip quan la seva pàgina és a la vista (sempre sense so, com exigeixen els navegadors), loop el torna a reproduir des del principi i exclusive: false deixa que corri al costat dels altres clips. Per defecte un vídeo és exclusiu: quan s'engega, posa en pausa tot el que s'estigui reproduint, cosa que serveix per a una classe però no per a una pàgina de fotos en moviment. La marca de reproducció i el codi QR estan desactivats, de manera que en paper els clips es llegeixen com a fotos de premsa.
#2 · Dona a cada bucle el seu lloc a la retícula
const resources = [
clip('mayor', asset('mayor-loop.mp4'), [640, 480],
{ position: 'top', span: 'column', columns: 3 },
'**Mrs Edith Rowe** speaks from the platform at the foot of Quay Hill before cutting '
+ 'the ribbon.',
'A woman in a dark coat and small hat speaks at a draped podium, one hand raised.'),
clip('clock', asset('clock-loop.mp4'), [480, 640],
{ position: 'top', span: 'column', columns: 2 },
'**Twelve o’clock** on the Guildhall tower, for the first time since 1924.',
'A stone clock tower against clouds, pigeons on its ledge.'),
clip('fisherman', asset('fisherman-loop.mp4'), [640, 480],
{ position: 'auto', span: 'column', columns: 2 },
'**Skipper Jacob Hosking** and the 82-pound cod on the quay.',
'An old fisherman in an oilskin laughs, holding up a huge cod.'),
clip('aviator', asset('aviator-loop.mp4'), [640, 480],
{ position: 'top', span: 'column', columns: 2 },
'**Miss Constance Lacey** waves from the cockpit on Wexcombe Common.',
'A young woman in a flying helmet waves from the open cockpit of a biplane.'),
clip('actress', asset('actress-loop.mp4'), [480, 640],
{ position: 'top', span: 'column', columns: 2 },
'**Miss Rosalind Avery** as Hester Lane, on the garden steps in Act III.',
'An actress in a long gown on a garden stage set, one arm outstretched.'),
clip('keeper', asset('keeper-loop.mp4'), [640, 480],
{ position: 'top', span: 'column', columns: 3 },
'**Tom Rundle** at Gannet Park, before the penalty he saved.',
'A goalkeeper in a flat cap crouches on his line in front of a packed terrace.'),
];
La maquetació és layoutType: 'multiple' amb columnCount: 4. El columns d'un flotant fixa quantes n'ocupa: els clips de l'alcaldessa i del porter, tres; els de l'aviadora, la torre del rellotge, el pescador i l'actriu, dues. Un flotant a diverses columnes pren el capdamunt d'una sèrie de columnes buides que comencen a la mateixa altura, o el peu de la columna on es cita i de les buides que la segueixen, així que el lloc de la línia ::resource decideix el buit. L'alcaldessa es cita després del primer paràgraf, cosa que deixa la columna 1 al text i posa la foto sobre les columnes 2 a 4.
#3 · Compon la capçalera i les pàgines de secció
const nameplate = { enabled: true, slot: { elements: [
depth(10), // the masthead is ten grid lines deep; the rules and the dateline hang from it
{ kind: 'rule', id: 'foot', thickness: pt(2.5), color: col('ink'),
placement: { ...at('#depth', 'align-bottom', 0, -1.5), size: { width: 'fill' } } },
...[['left', '{attr.issue}'], ['center', '{publishDate}'], ['right', '{attr.price}']].map(
([align, content]) => text(`date-${align}`, content, { ...typed(9, { fontWeight: 700 }),
align }, { ...at('#foot', 'above', 0, -1.6), size: { width: 'fill' } })),
rule('thin', '#date-left', 0.6, 1.8),
text('name', '{titleText}', { ...oswald(66, 700, { textTransform: 'uppercase',
letterSpacing: pt(1.5) }), lineHeight: 1, align: 'center' },
{ ...at('#thin', 'above', 0, -2), size: { width: 'fill' } }),
// Two ears over the name: the paper's age on the left, the weather on the right.
text('since', '{attr.since}', { ...caps(8, 600, 'flag'), align: 'left' },
{ ...at('container', 'top-left', 0, 1), size: { width: mm(90) } }),
text('weather', 'Weather: {attr.weather}', { ...typed(8.5, { italic: true }), align: 'right' },
{ ...at('container', 'top-right', 0, 1), size: { width: mm(110) } }),
] } };
El títol de nivell 1 de la portada és el nom del diari, compost en una ranura d'elements: dues orelles a sobre, el nom en majúscules d'Oswald i una línia de data entre un filet fi i un de gruixut. Les pàgines interiors obren amb un rètol vermell de secció sobre una barra de 3 pt (la regió pages), i totes les pàgines menys la primera porten una línia de foli al capdamunt.
#4 · Obre el diari a Folio
const stage = Object.assign(document.createElement('section'),
{ id: 'folio', ariaLabel: 'The newspaper in 3D' });
stage.style.cssText = 'height: min(82vh, 820px); margin: 0 auto; max-width: 1280px';
document.getElementById('pages').before(stage);
createFolioFromDocument(stage, doc, {
// Folio plays each clip from its video.url, muted, while its page is on show.
appearance: { textureBaseUrl: 'https://postext.dev/folio/textures' },
onChange: ({ pages }) => kitStatus(`${doc.pages.length} pages · open at `
+ pages.map((i) => doc.pages[i].pageLabel || i + 1).join('–')),
});
config.folio posa el diari sobre roure amb llum de dia: paper premsa de 45 g/m², que deixa veure per transparència la pàgina següent, i binding.type: 'folded', plecs doblegats una vegada i posats l'un dins de l'altre, sense grapes ni llom, com es munta un diari. Folio dibuixa tots els clips de la doble pàgina oberta en una sola textura, fins a vuit alhora, engega els bucles automàtics cada vegada que un pas de pàgina s'atura a la seva i els atura quan aquesta pàgina es passa. Un clic en un vídeo exclusiu el continua reproduint tot sol, amb el seu so, i els bucles esperen que s'acabi.
La recepta completa
// ═══ Postext Cookbook · Nº 136 · A living newspaper of video loops ══════════════ // https://postext.dev/en/cookbook/living-newspaper-video-loops // Code: MIT · Text: original (CC BY 4.0) · Clips: generated with diffusion models (CC BY 4.0) // Fonts: Oswald, Libre Caslon Text, Courier Prime (SIL OFL 1.1) · Needs postext ≥ 1.18.0 import { buildDocument, renderPageToCanvas, clearMeasurementCache, registerResourceImage } from 'https://esm.sh/postext'; import { createFolioFromDocument } from 'https://esm.sh/postext-folio'; const LANG = 'en'; // @lang: the language of the sample document ('en') const RECIPE = 'living-newspaper-video-loops'; // ─── 1 · Design ───────────────────────────────────────────────────────────── // #region palette: newsprint, a warm black and a dark red for the flags const palette = { ink: '#16130f', // text, rules and the nameplate paper: '#f3efe4', // newsprint, and type reversed out of ink or red flag: '#8e1b14', // the one accent: section flags, kickers, the jump lines tint: '#e6dfcf', // the In brief strip rule: '#8f897c', // hairlines: the column rules muted: '#5b554b', // bylines, captions' credits, the imprint }; const col = (id) => ({ hex: palette[id], model: 'hex', paletteId: id }); const colorPalette = [ // defaults link to 'main-color': point it at the ink, never blue ...Object.entries(palette).map(([id, hex]) => ({ id, name: id, value: { hex, model: 'hex' } })), { id: 'main-color', name: 'ink (defaults)', value: { hex: palette.ink, model: 'hex' } }, ]; // #endregion const MARGIN = { top: 12, bottom: 12, side: 12 }; // mm: a tabloid's narrow margins const GUTTER = 4.5; // mm between the four columns const LEAD = 11.6; // body leading in pt: the baseline grid const oswald = (size, weight, look = {}) => ({ fontFamily: 'Oswald', fontSize: pt(size), fontWeight: weight, color: col('ink'), ...look }); const caps = (size, weight, colour = 'ink') => oswald(size, weight, { color: col(colour), textTransform: 'uppercase', letterSpacing: pt(size * 0.12) }); const typed = (size, look = {}) => ({ fontFamily: 'Courier Prime', fontSize: pt(size), color: col('ink'), ...look }); // the dateline and the captions: a typewriter const at = (to, edge, x = 0, y = 0) => ({ anchor: { to, edge }, offset: { x: mm(x), y: mm(y) } }); const text = (id, content, look, placement) => ({ kind: 'text', id, content, align: 'left', overflow: 'wrap', color: col('ink'), ...look, placement }); // never the ellipsis default const rule = (id, under, weight, gap = 0) => ({ kind: 'rule', id, thickness: pt(weight), color: col('ink'), placement: { ...at(under, 'above', 0, -gap), size: { width: 'fill' } } }); const depth = (lines) => ({ kind: 'box', id: 'depth', style: {}, placement: { ...at('container', 'top-left'), size: { width: 'fill', height: pt(lines * LEAD) } } }); // #region answer: every picture is a clip that loops, autoplays and plays with the others // A video resource prints its poster (the first frame) on the flat page. In Folio, and in the // HTML viewer, the poster comes alive: autoplay starts it muted when its page is on show, loop // keeps it going, and exclusive: false lets it run alongside the other clips instead of // pausing them, as one exclusive player at a time would. const videoStyle = { playMark: { enabled: false }, // a press photograph, not a play button qr: { enabled: false }, // the clips live on the page, not behind a link linkPoster: false, player: { autoplay: true, loop: true, muted: true, exclusive: false, controls: false }, }; const clip = (id, url, [w, h], placement, caption, altText) => ({ id, typeId: 'picture', kind: 'video', createdAt: 0, updatedAt: 0, caption, altText, placement, video: { source: 'file', fileId: `${id}-loop.mp4`, url, // url: where the clip plays from format: 'mp4', width: w, height: h, duration: 5, poster: { fileId: `${id}-poster.jpg`, format: 'jpeg', width: w, height: h } } }); // #endregion // #region clips: where each loop sits on the four-column grid const resources = [ clip('mayor', asset('mayor-loop.mp4'), [640, 480], { position: 'top', span: 'column', columns: 3 }, '**Mrs Edith Rowe** speaks from the platform at the foot of Quay Hill before cutting ' + 'the ribbon.', 'A woman in a dark coat and small hat speaks at a draped podium, one hand raised.'), clip('clock', asset('clock-loop.mp4'), [480, 640], { position: 'top', span: 'column', columns: 2 }, '**Twelve o’clock** on the Guildhall tower, for the first time since 1924.', 'A stone clock tower against clouds, pigeons on its ledge.'), clip('fisherman', asset('fisherman-loop.mp4'), [640, 480], { position: 'auto', span: 'column', columns: 2 }, '**Skipper Jacob Hosking** and the 82-pound cod on the quay.', 'An old fisherman in an oilskin laughs, holding up a huge cod.'), clip('aviator', asset('aviator-loop.mp4'), [640, 480], { position: 'top', span: 'column', columns: 2 }, '**Miss Constance Lacey** waves from the cockpit on Wexcombe Common.', 'A young woman in a flying helmet waves from the open cockpit of a biplane.'), clip('actress', asset('actress-loop.mp4'), [480, 640], { position: 'top', span: 'column', columns: 2 }, '**Miss Rosalind Avery** as Hester Lane, on the garden steps in Act III.', 'An actress in a long gown on a garden stage set, one arm outstretched.'), clip('keeper', asset('keeper-loop.mp4'), [640, 480], { position: 'top', span: 'column', columns: 3 }, '**Tom Rundle** at Gannet Park, before the penalty he saved.', 'A goalkeeper in a flat cap crouches on his line in front of a packed terrace.'), ]; // #endregion // The three small tables: the week's tides, the picture houses and the cricket. const cell = (content, align = 'left') => ({ content, align }); const head = (content, align = 'left') => ({ content, align, isHeader: true }); const table = (id, caption, columnWidths, rows, placement) => ({ id, typeId: 'picture', kind: 'table', createdAt: 0, updatedAt: 0, caption, placement, table: { model: { headerRowCount: 1, columnWidths, rows: [rows[0].map((h, i) => head(h, i ? 'right' : 'left')), ...rows.slice(1).map((r) => r.map((c, i) => cell(c, i ? 'right' : 'left')))] } } }); resources.push( table('tides', '**High water at Wexcombe Quay**', [1.2, 1, 1, 0.9], [ ['Day', 'Morning', 'Evening', 'Feet'], ['Sat 10', '5.12', '5.38', '16.4'], ['Sun 11', '5.58', '6.21', '16.9'], ['Mon 12', '6.41', '7.03', '17.2'], ['Tue 13', '7.23', '7.44', '17.3'], ['Wed 14', '8.04', '8.25', '17.0']], { position: 'here' }), table('pictures', '**This week at the picture houses**', [1, 2.2, 1.3], [ ['House', 'Programme', 'Shows'], ['Regal', 'All Aboard for Lisbon', '2.30, 6, 8.30'], ['Electric', 'The Lone Rider of Red Mesa', '6.15, 8.40'], ['Both', 'Children’s matinée', 'Sat 10.30']], { position: 'here' }), table('wireless', '**The Regional programme**', [0.8, 2.6], [ ['Time', 'Programme'], ['6.0', 'The Children’s Hour'], ['6.40', 'News and weather'], ['7.0', 'Cornish Songs, by the Wexcombe Male Voice Choir'], ['7.45', 'Talk: The Coasting Trade, by a ship’s master'], ['8.15', 'Dance Music from the Assembly Rooms'], ['10.0', 'Second news; shipping']], { position: 'here' }), table('fixtures', '**This week’s fixtures**', [1.1, 2.3, 1.1], [ ['Day', 'Match', 'Ground'], ['Sat 10', 'Cricket: Wexcombe v. Truro', 'The Rec'], ['Wed 14', 'Bowls: Park v. Hayle', 'Park green'], ['Sat 17', 'Cricket: Redruth v. Wexcombe', 'Redruth'], ['Sat 24', 'County Cup final: Athletic v. Rovers', 'Gannet Park']], { position: 'here' }), table('wheelers', '**Fifty miles: the first five**', [0.3, 1.2, 1.9], [ ['', 'Rider', 'Time'], ['1', 'L. Uren', '2 h 11 m 40 s'], ['2', 'T. Bray', '2 h 14 m 05 s'], ['3', 'S. Harvey', '2 h 15 m 52 s'], ['4', 'C. Nance', '2 h 19 m 31 s'], ['5', 'R. Jenkin', '2 h 20 m 18 s']], { position: 'here' }), table('scores', '**Wexcombe v. St Austell**', [1.6, 0.7, 1.7], [ ['Innings', 'Runs', 'Best'], ['Wexcombe', '168', 'Dunstan 64'], ['St Austell', '127', 'Carne 5 for 22']], { position: 'here' })); // #region nameplate: the front page's H1 is the paper's name, ruled above and below const nameplate = { enabled: true, slot: { elements: [ depth(10), // the masthead is ten grid lines deep; the rules and the dateline hang from it { kind: 'rule', id: 'foot', thickness: pt(2.5), color: col('ink'), placement: { ...at('#depth', 'align-bottom', 0, -1.5), size: { width: 'fill' } } }, ...[['left', '{attr.issue}'], ['center', '{publishDate}'], ['right', '{attr.price}']].map( ([align, content]) => text(`date-${align}`, content, { ...typed(9, { fontWeight: 700 }), align }, { ...at('#foot', 'above', 0, -1.6), size: { width: 'fill' } })), rule('thin', '#date-left', 0.6, 1.8), text('name', '{titleText}', { ...oswald(66, 700, { textTransform: 'uppercase', letterSpacing: pt(1.5) }), lineHeight: 1, align: 'center' }, { ...at('#thin', 'above', 0, -2), size: { width: 'fill' } }), // Two ears over the name: the paper's age on the left, the weather on the right. text('since', '{attr.since}', { ...caps(8, 600, 'flag'), align: 'left' }, { ...at('container', 'top-left', 0, 1), size: { width: mm(90) } }), text('weather', 'Weather: {attr.weather}', { ...typed(8.5, { italic: true }), align: 'right' }, { ...at('container', 'top-right', 0, 1), size: { width: mm(110) } }), ] } }; // #endregion // #region pages: a section flag on each inside page, a folio line at the head const section = { enabled: true, slot: { elements: [ depth(3), { kind: 'rule', id: 'bar', thickness: pt(3), color: col('ink'), placement: { ...at('#depth', 'align-bottom', 0, -2), size: { width: 'fill' } } }, text('flag', '{titleText}', { ...caps(13, 700, 'paper'), box: { backgroundColor: col('flag'), padding: { top: mm(1.4), right: mm(3.2), bottom: mm(1.2), left: mm(3.2) } } }, at('#bar', 'above')), ] } }; const folio = (parity, side, s) => [ text(`n-${parity}`, '{pageNumber}', { ...oswald(11, 700), align: side }, at('page', `top-${side}`, s * MARGIN.side, 5)), text(`t-${parity}`, '{title} · {publishDate}', { ...typed(8), align: side }, at(`#n-${parity}`, s > 0 ? 'right-of' : 'left-of', s * 3, 0.7)), ].map((element) => ({ ...element, parity })); const header = { elements: [...folio('even', 'left', 1), ...folio('odd', 'right', -1)] }; // #endregion const banner = { id: 'banner', backgroundEnabled: false, marginTop: pt(0), padding: { top: pt(0), right: pt(0), bottom: pt(0), left: pt(0) }, marginBottom: pt(LEAD / 2), titleStyle: { ...caps(9, 700, 'flag'), gap: mm(1.4) }, body: { fontFamily: 'Libre Caslon Text', fontSize: pt(12), lineHeight: pt(15), textAlign: 'center', firstLineIndent: pt(0) } }; const briefs = { id: 'briefs', background: col('tint'), columnGap: mm(GUTTER), padding: { top: mm(3), right: mm(4), bottom: mm(4), left: mm(4) }, titleStyle: { ...caps(10, 700, 'flag'), gap: mm(2) }, body: { fontFamily: 'Libre Caslon Text', fontSize: pt(8.4), lineHeight: pt(11), textAlign: 'left', firstLineIndent: pt(0) } }; const headline = (id, size, look = {}) => ({ id, fontFamily: 'Oswald', fontWeight: 600, fontSize: pt(size), lineHeight: pt(size * 1.05), marginBottom: pt(size * 0.3), ...look }); // A box in a column (the tram fares), and the small advertisements across the page's foot. const info = { id: 'info', background: col('tint'), marginTop: pt(LEAD), marginBottom: pt(LEAD), padding: { top: mm(2.5), right: mm(3), bottom: mm(3), left: mm(3) }, stripe: { enabled: true, side: 'top', width: pt(2.5), color: col('flag') }, titleStyle: { ...oswald(11, 600), gap: mm(1.5) }, body: { fontFamily: 'Libre Caslon Text', fontSize: pt(8.4), lineHeight: pt(11), textAlign: 'left', firstLineIndent: pt(0) } }; const ads = { ...briefs, id: 'ads', background: col('paper'), border: { enabled: true, width: pt(0.6), color: col('ink') }, body: { ...typed(8), lineHeight: pt(10.6), textAlign: 'left', firstLineIndent: pt(0) } }; const label = { fontFamily: 'Courier Prime', fontSize: pt(8), firstLineIndent: pt(0) }; const config = () => ({ // a factory: the engine caches resolved configs per object colorPalette, resourceTypes, videoStyle, page: { sizePreset: 'tabloid', dpi: 150, backgroundColor: col('paper'), // 280 × 430 mm margins: { top: mm(MARGIN.top), bottom: mm(MARGIN.bottom), left: mm(MARGIN.side), right: mm(MARGIN.side) } }, layout: { layoutType: 'multiple', columnCount: 4, gutterWidth: mm(GUTTER), columnRule: { enabled: true, color: col('rule'), lineWidth: pt(0.4) } }, bodyText: { fontFamily: 'Libre Caslon Text', fontSize: pt(8.8), lineHeight: pt(LEAD), color: col('ink'), boldColor: col('ink'), textAlign: 'justify', firstLineIndent: mm(3), indentAfterHeading: false, hyphenation: { enabled: true }, optimalLineBreaking: true, minWordSpacing: 0.75, maxWordSpacing: 1.6, avoidWidows: true, avoidOrphans: true, maxJustifyTracking: 15 }, // ‰ of an em: narrow columns justify with a little tracking headings: { fontFamily: 'Oswald', fontWeight: 600, marginBottom: pt(0), levels: [ // Restated: any headings object drops the H1 break (gotcha: headings-drop-h1-break). { level: 1, span: 'page', breakBefore: { enabled: true, parity: 'any' }, advancedDesign: section }, { level: 2, fontSize: pt(20), lineHeight: pt(22), marginTop: pt(LEAD) }, { level: 3, fontSize: pt(10.5), lineHeight: pt(LEAD), fontWeight: 700, marginTop: pt(LEAD / 2) }, ] }, headingStyles: [ { id: 'front', advancedDesign: nameplate, header: { elements: [] } }, { id: 'section' }, // inside pages: the flag of the level-1 design headline('lead', 44, { fontWeight: 700 }), headline('second', 20), headline('third', 12, { marginBottom: pt(2) }), ], calloutStyles: [banner, briefs, info, ads], tableStyle: { rules: 'horizontal', borderColor: col('rule'), borderWidth: pt(0.5), headerBackground: col('ink'), headerColor: col('paper'), headerFontFamily: 'Oswald', headerFontSize: pt(7.5), bodyFontFamily: 'Courier Prime', bodyFontSize: pt(7.8), cellPadding: mm(1.1) }, paragraphStyles: [{ ...label, id: 'byline', textAlign: 'left', color: col('muted') }, { id: 'flush', firstLineIndent: pt(0) }, { ...label, id: 'jump', textAlign: 'right', color: col('flag') }, { id: 'imprint', ...label, fontSize: pt(6.8), lineHeight: pt(9), textAlign: 'left', color: col('muted'), marginTop: pt(LEAD) }], captionStyle: { fontFamily: 'Courier Prime', fontSize: pt(7.8), gap: mm(1.4), note: { fontSize: pt(6.6), color: col('muted') } }, header, footer: { elements: [] }, folio: { tilt: 24, paper: { type: 'newsprint', grammage: 45, shade: '#f9f7f1' }, binding: { type: 'folded', cover: 'pages' }, surface: { type: 'oak' }, lighting: { environment: 'daylight' } }, }); // An unnumbered type: captions print without "Figure 1". const resourceTypes = [{ id: 'picture', name: 'Picture', shortLabel: 'Picture', captionPrefix: '', numberingTemplate: '', resetOn: 'never', counterFormat: 'decimal' }]; // ─── 2 · Content ──────────────────────────────────────────────────────────── const front = String.raw`---Mostra en Markdown · 147 línies · content.en.md
title: "The Wexcombe Lantern" author: "The Wexcombe Lantern" publishDate: "Saturday 10 June 1933" --- # The Wexcombe Lantern {style="front" issue="No. 4,117" since="Established 1871" price="Twopence" weather="Fair, a fresh breeze from the west"} :::callout{type="banner" span="page" title="The Harbour Line"} ## Trams run to the sea at last {style="lead"} *Mayor Edith Rowe opens the new line before four thousand people, and drives the first car down Quay Hill herself* ::: :::paragraphs{style="byline"} By **Arthur Penhale** · Municipal Correspondent ::: :::paragraphs{style="flush"} The trams reached the harbour yesterday, eleven years after the Corporation first promised them. At a quarter past eleven the Mayor, Mrs Edith Rowe, cut a ribbon of green silk across the rails at the foot of Quay Hill, and a crowd that stretched back past the Fish Market cheered loud enough to lift the gulls off the warehouse roofs. ::: ::resource{id="mayor"} “It has taken us longer than the Romans took to build their roads,” Mrs Rowe told the crowd from a platform dressed in bunting, “and our road is only a mile and three furlongs long. But it is ours, it is paid for, and from Monday a docker in Saltings Row can be at the Town Hall in nine minutes for a penny.” The Harbour line leaves the main route at Market Cross and runs down Quay Hill, along the Strand and out to a new turning loop beside the lifeboat house. Four cars, rebuilt in the Corporation’s own works at Fennick Lane, will run every eight minutes from six in the morning until half past eleven at night. The line cost £41,300, of which £12,000 came from the government’s grant for public works. Some two hundred men of the town were taken on to lay it, most of them from the register at the Labour Exchange, and the Borough Engineer, Mr Lionel Cardew, said that not one day had been lost to accident since the first sett was lifted in October. :::paragraphs{style="jump"} A hill too steep, and the fares: **page 2** ::: ::resource{id="aviator"} ## Miss Lacey lands on the Common {style="second"} :::paragraphs{style="flush"} The aviator Miss Constance Lacey brought her biplane down on Wexcombe Common at twenty past six on Wednesday evening, at the end of a flight from Croydon of three hours and fifty minutes. She came in low over the cricket pavilion, scattering a game of the Wednesday Eleven, and rolled to a stop forty yards from the bandstand. ::: Miss Lacey, who is twenty-four, is flying round the coast of England and Wales in stages to raise money for the lifeboat fund, and has so far raised £1,108. “The Common is the best field I have met since Kent,” she said, “though the cows took some persuading.” Her machine is a two-seater of 1929 with a Gipsy engine, bought second-hand from a flying club in Hampshire and named *Kittiwake*. She navigates with a road map folded on her knee, and follows the railway lines where the coast turns misty. “The Great Western has never once let me down,” she said. “I cannot say the same for the weather.” She stayed the night at the Mermaid, where the landlord refused to take her money, and left at seven the next morning for Penzance, waving to a crowd of schoolchildren who had been let out of lessons to see her off. Two of them had sat up all night on the pavilion roof to be sure of a good view. The pavilion has asked for a signed photograph for its wall, and the Wednesday Eleven for a replay. ### The weather Fair at first, with a fresh breeze from the west; cloud later and a little rain in the evening, clearing overnight. Sea moderate, with a swell on the bar at low water. Sunrise 4.58, sunset 9.31. Yesterday’s highest temperature in the town was 68 degrees, at the Fish Market at three o’clock, and the lowest 51, at the coastguard station at dawn. The Harbour Master reports the best visibility of the year from Gannet Point, where the Stags could be seen plainly at noon and the smoke of the Scilly packet an hour before she was due. *Tomorrow:* bright, a light northerly air, good drying weather. Bathers are reminded that the red flag on West Sands means what it says. :::callout{type="briefs" span="page" placement="bottom" title="In brief"} :::columns{count=4 breaks="2,3,4"} **Lighthouse dark for a night.** The Gannet Point light will be shut down on Tuesday from dusk to midnight while its new lamp is fitted. Ships are warned by the Harbour Master. **Bathing tents return.** The striped bathing tents are back on West Sands for the summer, at threepence the hour. Mixed bathing is allowed east of the groyne only. **Choir to Cardiff.** The Wexcombe Male Voice Choir sails on Thursday for the Eisteddfod. Forty-two singers, one harmonium and the conductor’s spaniel will make the crossing. **Lost umbrella.** A lady’s umbrella with an ivory handle in the shape of a heron was left in car No. 4 on Monday. It may be claimed at the Depot, Fennick Lane. ::: ::: # Town & Harbour {style="section"} ## Trams: a hill too steep, they said {style="second"} :::paragraphs{style="byline"} Continued from page 1 ::: Many doubted that a tram could manage Quay Hill at all. The gradient is one in eleven at its worst, and a petition of 1928 warned of runaway cars and ruin in the Fish Market. Mr Cardew’s answer was a track brake of his own design, tried on the hill for six weeks this spring with cars loaded with sandbags. Mrs Rowe silenced the doubters in the simplest way. After the ribbon was cut she climbed to the front platform of car No. 9, took the controller from the motorman and drove the first car down the hill herself, at a steady walking pace and to a roar from the pavements. She is believed to be the first Mayor of Wexcombe to drive anything at all. ## The clock that stopped for nine years strikes again {style="second"} :::paragraphs{style="byline"} By **Mary Tregarthen** ::: :::paragraphs{style="flush"} For nine years the clock on the Guildhall tower has said twenty to four. Yesterday, at noon precisely, it said twelve, and the bell in the cupola struck the hour for the first time since the great gale of 1924 bent its hammer and cracked the escapement wheel. ::: ::resource{id="clock"} The work was done by Mr Samuel Opie, clockmaker, of Chapel Street, and his two sons, who have climbed the tower every evening since February. “The wheel was cast in 1799, and nobody casts them like that now,” said Mr Opie. “So we cast it like that ourselves.” The new hammer was forged at the Harbour Works from an old capstan bar. A small crowd had gathered in Guildhall Square by a quarter to twelve, many of them with their watches out. When the first stroke sounded there was a moment of silence, and then a cheer, and Mr Opie’s younger son was seen to wave his cap from the louvres of the belfry. The Town Clerk said that the old joke, that Wexcombe was the only town in England where it was always teatime, would now have to be retired. The pigeons, he added, would have to get used to the bell. ## A cod the size of a boy {style="second"} :::paragraphs{style="flush"} The largest cod landed at Wexcombe in living memory came ashore on Thursday in the hold of the *Morning Star*, skipper Jacob Hosking, 71. It weighed 82 pounds on the Fish Market scales and measured five feet and an inch from its nose to the fork of its tail. ::: ::resource{id="fisherman"} “I’ve fished these grounds since I was eleven,” Mr Hosking said, “and I never saw his like. He fought for three-quarters of an hour, and I reckon he’d have won if my grandson hadn’t been there to hold my belt.” The fish was sold to Messrs Kitto of the Strand for a guinea and is to be shown in their window, packed in ice, until Monday. A photograph has been sent to the Natural History Museum, which has asked whether the skipper might part with the head. Mr Hosking says he will think about it. ## Council votes for an open-air bathing pool {style="second"} The Town Council on Tuesday voted by eighteen votes to seven to build an open-air bathing pool at Gannet Point, in the hollow of the old quarry below the coastguard cottages. The pool will be filled by the tide through a sluice, and will measure fifty yards by twenty, with diving stages and a café. The Borough Engineer puts the cost at £9,600. Councillor Bastian, who led the opposition, said the town already had a perfectly good sea. Councillor Mrs Pascoe replied that the sea was perfectly good only in August, and that children had been drowned in it. Work is to begin in October, so that the pool may open for the summer of 1934. Seventy men will be employed. ## Lifeboat out twice in one night {style="second"} The Wexcombe lifeboat *Sarah Jolly* was launched twice on Tuesday night, the first time at a quarter to eleven to the yacht *Petrel*, whose engine had failed off Gannet Point with three men aboard, and the second at half past two to a fishing boat reported overdue from the mackerel grounds. The *Petrel* was towed into harbour shortly after midnight. The fishing boat, the *Brothers’ Pride*, was found at anchor in the lee of the Stags, her crew asleep. “They had a good catch and saw no reason to hurry,” said the coxswain, Mr William Bassett. “I told them what I thought of that, and then I gave them a tow, because the tide was turning.” The crew of twelve were back in their beds by five. It was the boat’s fourteenth service this year. ## Fire at the rope works {style="second"} A fire broke out in the tarring shed of Messrs Pengelly’s rope works on Fennick Lane shortly after four on Thursday afternoon, and for an hour threatened the whole of the long rope walk beside it. The alarm was given by a boy delivering newspapers, who saw smoke coming from under the door and ran to the fire station in Market Street, a distance of three hundred yards, in what the Chief Officer called the best time he had ever seen. The brigade had two jets on the shed within ten minutes. The roof fell in at a quarter to five, but the fire was kept from the rope walk, where some forty coils of new manila rope were stored for the harbour, and the walls of the shed are standing. Mr Pengelly puts the damage at £300, and says that work will go on as usual on Monday, in the open air if it must. The boy, Jimmy Rowse, aged twelve, has been given a reward of five shillings by the firm and a ride on the engine by the brigade. He said he preferred the ride. :::callout{type="info" title="Harbour line: what you need to know"} Cars run every eight minutes from Market Cross to the Lifeboat House, calling at Quay Hill, the Fish Market, the Custom House and Strand Corner. The fare is one penny all the way, a halfpenny for children under fourteen. Workmen’s returns at three-halfpence are sold before eight in the morning. On Sundays cars start at nine. ::: ::resource{id="tides"} ## From the Guildhall {style="third"} The Watch Committee has fixed the speed of the trams on Quay Hill at four miles an hour going down and eight coming up, and a man is to stand at the Fish Market corner with a red flag on market days until the public is used to the cars. The Finance Committee reports that the rates will not rise this year, for the first time since 1927, and the Libraries Committee that the new reading room at Saltings Row has lent 2,140 books in its first month, chiefly detective stories. A proposal to ring the restored Guildhall bell at midnight on New Year’s Eve was carried with one vote against, from the councillor who lives next door. ## Shipping {style="third"} **Arrived:** *Mary Jane*, ketch, from Swansea with coal; *Goldfinch*, steamer, from Rotterdam with timber; *Ellen*, schooner, from Fowey with china clay for the potteries; *Pride of Wexcombe*, trawler, from the Smalls with a full hold. **Sailed:** *Morning Star* and *Brothers’ Pride* for the mackerel grounds; *Lynher*, barge, for Plymouth with pit props; *Goldfinch*, in ballast, for Cardiff. **In harbour:** the training ship *Exe*, until Wednesday; visitors on board each afternoon from two, and the band of the ship plays on the quarterdeck at four. **Fish Market:** mackerel 9s. the box, pilchards 6s., hake scarce at 2s. 6d. the stone; crab plentiful and cheap.`; // content.en.md: the front page and Town & Harbour const arts = String.raw`# Stage & Screen {style="section"}Mostra en Markdown · 86 línies · content.stage.en.md
## A Lady of the Garden at the Pavilion {style="second"} :::paragraphs{style="byline"} Reviewed by **Harold Vosper** ::: :::paragraphs{style="flush"} The Pavilion Players opened their summer season on Monday with *A Lady of the Garden*, a comedy in three acts by a playwright who prefers to remain anonymous, and whose modesty the second act fully justifies. The first and third, however, are a delight, and the evening belongs to Miss Rosalind Avery. ::: ::resource{id="actress"} As Miss Hester Lane, a gardener’s daughter who inherits the great house where her father kept the roses, Miss Avery is by turns wistful, imperious and very funny. Her long speech to the assembled relations in the third act, delivered from the top of the garden steps with one arm flung out towards the vegetable beds, stopped the play for a full minute of applause on the first night. Mr Frank Polglase is a fine, glowering butler, who conveys more with one raised eyebrow than the second act manages with its whole cast, and Miss Daisy Kitto, as the cousin who expected to inherit, is spiteful in the best possible way. The painted garden by Mr Tom Rule, the company’s carpenter, is so convincing that a lady in the second row was heard to ask where she might buy the delphiniums. The second act is another matter. It is set in a solicitor’s office, it lasts fifty minutes, and it consists almost entirely of people reading a will aloud to one another. Miss Avery does her best with a scene in which she must faint on to a filing cabinet, but the audience on Monday was plainly waiting for the garden to come back, and when it did, so did the play. The Pavilion itself has been painted for the season, cream and green, and the management has kept its promise of last summer: the seats in the back six rows have been raised on a new floor, so that the shilling public can see the stage over the hats in front. The orchestra of five, under Mr Leonard Jewell, plays a selection from *Bitter Sweet* in the intervals and plays it well. *A Lady of the Garden* runs nightly at eight until the 24th, with a matinée on Wednesdays at half past two. Seats from sixpence to three shillings; the box office at the Pavilion is open from ten. The Players follow it on the 26th with a thriller, *The Lamp in the Lighthouse*, for which the management promises a real storm. ## Band concert on the Parade {style="third"} The Wexcombe Silver Band gives the first of its Sunday concerts on the Parade bandstand tomorrow at three, under its new conductor, Mr Alfred Penna, late of the Royal Marines. The programme includes a selection from *The Gondoliers*, the overture to *Poet and Peasant* and a march of Mr Penna’s own, *The Harbour Line*, written for the opening of the trams and played for the first time in public. Deck chairs twopence; the collection is for the hospital. ## Roses and sweet peas at the Drill Hall {style="third"} The Horticultural Society’s summer show filled the Drill Hall on Thursday with three hundred and forty exhibits, sixty more than last year, and with the scent of sweet peas, which took the whole of the north wall. The challenge cup for roses went to Mr George Nance of Trevenna Lane for six blooms of *Ophelia* that the judges called the best seen in the town since the war, and the cup for vegetables to Mrs Annie Pascoe, whose onions weighed two pounds each. The prize for the best-kept allotment, judged in May, went to the Saltings Row allotments for the second year. In the children’s class for a wild flower collection, every entry received a prize, by order of the President. ## Harbour paintings at the Institute {style="third"} The Wexcombe Art Society’s summer exhibition opened at the Literary Institute on Wednesday, with a hundred and twelve pictures by forty-one members, the largest show in the society’s history. The harbour, as ever, is the favourite subject: there are nineteen views of it, at every hour and in every weather, and one, by Miss Phyllis Body, painted from the top of the Guildhall tower while the clockmakers were at work. The prize for the best picture in oils went to Mr Ernest Lugg for *Pilchard Boats, Evening*, and the prize for water-colours to Mrs Jane Treloar, whose *Quay Hill in Rain* shows the new tram lines glistening, two days before they were finished. The exhibition is open daily from ten until six, until the 1st of July. Admission threepence; members free. ## On the screen this week {style="second"} ::resource{id="pictures"} At the **Regal**, the picture of the week is a musical comedy of shipboard life, with the news and a coloured cartoon. At the **Electric**, a western in the old style, and a serial whose heroine has now been tied to a railway line for three weeks running. Children’s matinée on Saturday mornings at both houses, price twopence. ## Wireless this evening {style="third"} ::resource{id="wireless"} ## Letters to the Editor {style="second"} **Sir,** — Now that we have trams to the harbour, may I ask when we are to have a seat at the top of Quay Hill? Those of us who walk up it, as I have done every morning for forty years, would be glad of somewhere to recover. — *Yours, &c.,* **J. Trevenna**, Saltings Row. **Sir,** — Your correspondent of last week complains of the noise of the gulls on the Strand. I would remind him that the gulls were here first, and that they do not, so far as I know, write to the newspapers about him. — *Yours, &c.,* **Ellen Rodda**, Fore Street. **Sir,** — I see the Guildhall clock is going again. Might the Council now turn its attention to the one at the station, which has been five minutes fast since the Coronation and has caused me to miss nothing, but has caused my wife to wait for a great many trains? — *Yours, &c.,* **H. B. Clemo**, The Parade. **Sir,** — May I, through your columns, thank the young man who carried my shopping basket from Market Cross to the top of Chapel Street on Tuesday and refused to give his name? I hope he reads the *Lantern*. — *Yours, &c.,* **Mary Tonkin**, Chapel Street. **Sir,** — I have read in your paper that the new trams are painted green and cream. Mine, I see, is painted green and cream also: the colour came through the hedge from the Corporation’s works in Fennick Lane, where they paint the cars with the windows open. Who pays for my washing? — *Yours, &c.,* **E. J. Hosken**, Fennick Lane. **Sir,** — Your report of the Harbour line says the cars will run until half past eleven at night. Will somebody tell the Regal, whose second house does not come out until twenty to twelve? — *Yours, &c.,* **A Picturegoer**, Fennick Lane. :::callout{type="ads" span="page" placement="bottom" title="Small advertisements"} :::columns{count=4 breaks="4,7,10"} **TO LET.** Furnished rooms, sea view, use of piano. Suit quiet gentleman. Apply Mrs Jose, 3 Marine Terrace. **BICYCLE** for sale, lady’s, three-speed, little used, £2 10s. or near offer. Box 112, Lantern Office. **WANTED,** a smart boy to deliver groceries, must be able to ride. Apply Hicks & Son, Market Street. **DANCING** every Saturday at the Assembly Rooms, eight till midnight, Billy Rowse and his Melodians. Admission one shilling. **PIANO TUNING** by an experienced man, late of Broadwood’s. Terms moderate. Write J. Kemp, 9 Chapel Street. **LOST** on the Common, Wednesday evening, a child’s red woollen glove. Finder please return to the Mermaid. Reward. **MOTOR COACH** to St Ives every Thursday, leaving Market Cross at 9.30, returning by 7. Fare 3s. 6d. Book at Rodda’s Garage. **FOR SALE,** pram, navy blue, good springs, 25s. Also cot. Apply after six, 21 Saltings Row. **WIRELESS SETS** repaired and recharged. Accumulators called for and delivered weekly. Tregenza, 4 Fore Street. **HOLIDAY APARTMENTS,** two bedrooms and sitting room, near sands, July vacant. Mrs Bray, Seaview, The Parade. **GENTLEMAN’S** gold watch, lost between the Rec and the Mermaid on Saturday. Sentimental value. Reward at this office. **DRESSMAKING** and alterations, ladies’ and children’s, at moderate charges. Miss Uren, 12 Lemon Street. ::: :::`; // content.stage.en.md: Stage & Screen const sport = String.raw`# Sport {style="section"}Mostra en Markdown · 74 línies · content.sport.en.md
## Rundle saves the Cup {style="second"} :::paragraphs{style="byline"} By **Walter Jago** · at Gannet Park ::: :::paragraphs{style="flush"} Wexcombe Athletic are in the final of the County Cup, and they owe it to Tom Rundle. With the score at one goal each and four minutes left on a pitch like a ploughed field, the referee gave a penalty against the home side for a handball nobody else in the ground saw. ::: ::resource{id="keeper"} Penrose of Treviskey placed the ball, took four paces back and struck it hard and low towards the right-hand post. Rundle, who is a carpenter by trade and the shortest goalkeeper in the league, went full length into the mud and held it against his chest. The crowd of 6,200, the largest at Gannet Park since the war, threw caps in the air, and one man was seen to throw his wife’s hat. From the clearance Athletic broke down the left, and young Billy Trounce, who is seventeen, ran half the length of the field before shooting past the Treviskey goalkeeper for the winner. He was carried off the pitch at the final whistle by supporters who had climbed the railings, and did not get his boots back until Sunday. The first half had belonged to Treviskey. Their centre-forward, Hendra, scored after nine minutes with a header from a corner, and only a fine save by Rundle from the same player, low down at the near post, kept the score at one goal to nil at the interval. Athletic were level ten minutes after the restart, when Opie, son of the clockmaker, turned in a cross from Trounce at the second attempt. The final, against Penhallow Rovers, will be played at Gannet Park on the 24th, kick-off at three. The club asks supporters to come early and to leave their wives’ hats at home. ## Rovers come by the long road {style="third"} Penhallow Rovers, Athletic’s opponents in the final, have needed three replays to get there. They drew with St Just twice, the second time after extra time in a thunderstorm, and beat them at the third attempt at Camborne, where the match was stopped for a quarter of an hour while a horse was led off the pitch. Their semi-final against Porthleven was settled by a goal from their captain, Arthur Kitto, the Penhallow blacksmith, who is forty-one and says this will be his last season, as he has said for the past four. Rovers have the stronger side on paper, with three men who have played for the county, and they beat Athletic twice in the league this winter. But they have never won the Cup, and they have never played at Gannet Park, where the slope from the town end is worth a goal to anyone who knows it. Athletic’s trainer, Mr Sam Rowe, has asked the groundsman not to cut the grass this week. Tickets for the stand, at a shilling, go on sale at Hicks & Son on Monday morning, and the club expects them to be gone by dinner time. ## The Harbour Mile {style="third"} The Harbour Mile, run on Whit Monday along the Strand from the Custom House to the lifeboat slip and back, was won for the third year running by Jack Penrose of the Wexcombe Harriers in 4 minutes 31 seconds, a record for the course. He was followed home by his brother Harry, eleven yards behind, and by a visiting runner from Truro, who said that he had been told the course was flat. Forty-four ran, the youngest fourteen and the oldest sixty-two, and every one of them finished, though two had to be helped up the slip. The new trams were stopped for twenty minutes while the race passed, the first time, said the Borough Engineer, that the line has given way to anybody. ## Swimming club’s new raft {style="third"} The Wexcombe Amateur Swimming Club has moored a new diving raft off West Sands, forty yards out and built by members over the winter from timber and oil drums given by the Harbour Board. It was christened on Sunday by the club’s oldest member, Mrs Grace Tonkin, who is seventy-three and swam out to it. The club’s summer gala is on the 15th of July; races for juniors, ladies and veterans, and a novelty race in fancy dress. ## Fixtures {style="third"} ::resource{id="fixtures"} ## Cricket {style="third"} ::resource{id="scores"} Wexcombe beat St Austell by 41 runs at the Rec on Saturday. Dunstan’s 64, his first fifty of the season, carried the town to 168, and the Rev. A. Carne took five wickets for 22 with his slow left-arm bowling, which St Austell found as hard to read as his sermons. ## Regatta and bowls {style="third"} The Harbour Regatta is fixed for Saturday the 1st of July, with gig racing, a swimming race across the harbour mouth and the greasy pole. Entries to the Secretary at the Harbour Office by the 24th. At bowls, Wexcombe Park beat Camborne by nine shots in the County League; Mr W. Oates, aged eighty-one, was again the best player on the green, and was presented afterwards with a new set of woods to mark his sixtieth season with the club. He said that he would keep the old ones for wet days. ## Wheelers’ fifty-mile trial {style="third"} The Wexcombe Wheelers held their fifty-mile time trial on Sunday morning over the coast road to Hayle and back, starting at six o’clock to have the road to themselves. Twenty-three riders started at one-minute intervals and twenty finished; two punctured at Gwithian and one stopped, by his own account, for breakfast. The winner, Mr Leonard Uren, a postman, beat the club record by four minutes on a machine he built himself from parts, and was back at his round by nine. ::resource{id="wheelers"} The club’s next event is the hill climb up Quay Hill on the 25th, which the Watch Committee has allowed on the condition that the riders keep clear of the tram lines, and the trams of the riders. ## Gigs on the bay {style="third"} The pilot gigs were out on the bay on Wednesday evening for the first time this summer, six boats in a practice race from the lifeboat slip round the Stags and back. The Wexcombe crew in the old *Bonnet*, built in 1838 and still the fastest boat on this coast in a flat sea, led from the start and won by four lengths from Penhallow. The crews race for the Harbour Cup at the Regatta on the 1st of July, when nine gigs are expected. ## Angling {style="third"} Sea fishing from the pier has been good all week, with mackerel taken on feathers at every tide and a bass of nine pounds by Mr Fred Curnow on Tuesday evening, the best of the season so far. The Sea Angling Club’s monthly competition on Sunday was won by Mr Curnow again, with a catch of eleven pounds. Visitors may fish from the pier on payment of twopence to the pier master. ## Racing at Penhallow {style="third"} The Whitsun meeting at Penhallow Downs drew a crowd of nine thousand on Monday, many of whom came by the special trains from Wexcombe. The big race, the Downs Plate over a mile and a half, went to *Harbour Lights*, owned by Mr Jory of Trelask and ridden by his nephew, at a hundred to eight. The favourite unseated his rider at the second hurdle and finished the race alone, to great applause, a good length ahead of the field. The bookmakers had a poor afternoon. Three favourites were beaten, and the last race, the Farmers’ Handicap, was won by a grey mare from a farm above Wexcombe that had been ploughing on Saturday and had never run on a course before. Her owner, Mr Tom Oliver, said that he had entered her for a lark and backed her with half a crown, and that he would now buy a new plough with the winnings. The meeting closes on Wednesday with the Hunters’ Steeplechase, and the Great Western will again run special trains from Wexcombe at half past eleven and a quarter past twelve, returning after the last race. The Lantern’s tipster, who has not had a winner since Easter, refuses to name a horse. :::paragraphs{style="imprint"} Printed and published by the Wexcombe Lantern Company, Ltd., at their offices, 14 Market Street, Wexcombe. Every Saturday. The town, its people and its news are invented. The pictures are films generated with diffusion models. :::`; // content.sport.en.md: Sport const markdown = [front, arts, sport].join('\n\n'); // one document, four pages // ─── 3 · Fonts ────────────────────────────────────────────────────────────── const FONTS = { 'Libre Caslon Text': ['400', '400i', '700'], Oswald: ['600', '700'], 'Courier Prime': ['400', '400i', '700'] }; // ─── 4 · Build & show ─────────────────────────────────────────────────────── await loadFonts(FONTS, markdown); await Promise.all(resources.filter((r) => r.kind === 'video').map(({ video: { poster } }) => loadImage(poster.fileId, asset(poster.fileId)))); // the printed frames const doc = await buildWithFonts(() => buildDocument({ markdown, resources }, config()), markdown); showPages(doc, { title: 'The Wexcombe Lantern · a living newspaper' }); // #region folio: the newspaper in 3D, every clip playing on the open pages at once const stage = Object.assign(document.createElement('section'), { id: 'folio', ariaLabel: 'The newspaper in 3D' }); stage.style.cssText = 'height: min(82vh, 820px); margin: 0 auto; max-width: 1280px'; document.getElementById('pages').before(stage); createFolioFromDocument(stage, doc, { // Folio plays each clip from its video.url, muted, while its page is on show. appearance: { textureBaseUrl: 'https://postext.dev/folio/textures' }, onChange: ({ pages }) => kitStatus(`${doc.pages.length} pages · open at ` + pages.map((i) => doc.pages[i].pageLabel || i + 1).join('–')), }); // #endregionKit · core, fonts, viewer, images: igual a totes les receptes · 270 línies
// ─── Kit ── helpers shared by every Cookbook recipe · postext.dev/cookbook ───── // ─── Kit · core v1 ── the same in every recipe · postext.dev/cookbook ───────── function mm(value) { return { value, unit: 'mm' }; } function pt(value) { return { value, unit: 'pt' }; } function em(value) { return { value, unit: 'em' }; } /** The sample language's string: t({ en: 'Figure', es: 'Figura' }). */ function t(strings) { return strings[LANG] ?? Object.values(strings)[0]; } /** A file in this recipe's assets folder, served from the Postext repo by jsDelivr. */ function asset(file) { return `https://cdn.jsdelivr.net/gh/drnachio/postext@main/cookbook/${RECIPE}/assets/${file}`; } // ─── Kit · fonts v1 ── the same in every recipe · postext.dev/cookbook ──────── // Postext measures text with the faces the browser has loaded, and caches the // widths, so every face must be ready before the first build. Faces come from // Fontsource: the same static files the PDF embeds, so screen and PDF agree. /** faces = { 'Family Name': ['400', '400i', '700'] }. `text` is the sample: * letters beyond Latin-1 (č, ł, ő…) also load the latin-ext files. With * `optional`, a face Fontsource does not ship is skipped instead of failing. * Resolves to the number of faces added. */ async function loadFonts(faces, text = '', { optional = false } = {}) { kitStatus('Loading fonts…'); const ranges = { latin: 'U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,' + 'U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD', 'latin-ext': 'U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,' + 'U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF', }; const subsets = /[Ā-˿Ḁ-ỿ]/.test(text) ? ['latin', 'latin-ext'] : ['latin']; const jobs = []; let added = 0; for (const [family, specs] of Object.entries(faces)) { const id = fontsourceId(family); const meta = optional ? await fontsourceMeta(family) : null; for (const spec of new Set(specs)) { const weight = parseInt(spec, 10); const style = spec.endsWith('i') ? 'italic' : 'normal'; if (hasFace(family, weight, style)) continue; if (optional && !(meta?.weights.includes(weight) && meta.styles.includes(style))) continue; for (const subset of subsets) { const url = `https://cdn.jsdelivr.net/npm/@fontsource/${id}@5/files/${id}-${subset}-${weight}-${style}.woff2`; const face = new FontFace(family, `url(${url}) format('woff2')`, { weight: String(weight), style, unicodeRange: ranges[subset] }); jobs.push(face.load().then((ready) => { document.fonts.add(ready); added++; }, () => { if (subset === 'latin' && !optional) throw new Error(`Fontsource has no ${family} ${weight} ${style}`); })); } } } await Promise.all(jobs).catch((error) => { kitFail(error); throw error; }); return added; } /** Runs `build` (a buildDocument or buildBundle call) and checks the faces * the pages use. A regular face missing from FONTS is loaded with a warning; * bold and italic variants are loaded when the family ships them. Then the * measurement caches are cleared and the build runs again. */ async function buildWithFonts(build, text = '') { const tried = new Set(); for (let round = 0; round < 3; round++) { kitStatus('Laying out…'); await new Promise(requestAnimationFrame); // let the status paint first const result = await Promise.resolve().then(build).catch((error) => { kitFail(error); throw error; }); const wanted = { base: {}, variants: {} }; for (const { font, base } of [result].flat().flatMap(fontStringsOf)) { const { family, weight, style } = parseFont(font); const key = `${family}|${weight}|${style}`; if (tried.has(key) || hasFace(family, weight, style)) continue; tried.add(key); (wanted[base ? 'base' : 'variants'][family] ??= []).push(`${weight}${style === 'italic' ? 'i' : ''}`); } if (Object.keys(wanted.base).length) { console.warn(`[cookbook] FONTS does not list ${JSON.stringify(wanted.base)}: loading them.`); } const added = await loadFonts(wanted.base, text) + await loadFonts(wanted.variants, text, { optional: true }); if (added === 0) return result; clearMeasurementCache(); } throw new Error('The fonts did not settle after three builds.'); } /** Every font string of the layout. `base` marks a block's own face; its * bold, italic and bold-italic variants are listed whether or not used. */ function fontStringsOf(doc) { const found = new Map(); const walk = (node) => { if (!node || typeof node !== 'object') return; if (Array.isArray(node)) { node.forEach(walk); return; } for (const [key, value] of Object.entries(node)) { if (typeof value === 'string' && /fontString$/i.test(key)) { found.set(value, found.get(value) || key === 'fontString'); } else if (value && typeof value === 'object') walk(value); } }; walk(doc.pages); walk(doc.blocks); return [...found].map(([font, base]) => ({ font, base })); } /** '700 37.5px Open Sans' / 'italic 400 13px "Source Serif 4"' → { family, weight, style }. * A string with no weight ('95.8px Young Serif', from a design text) is 400. */ function parseFont(font) { const m = /^(?:(italic|oblique)\s+)?(?:small-caps\s+)?(?:(\d+|bold|normal)\s+)?[\d.]+px\s+(.+)$/.exec(font.trim()); if (!m) throw new Error(`Unexpected font string: ${font}`); const weight = m[2] === 'bold' ? 700 : !m[2] || m[2] === 'normal' ? 400 : Number(m[2]); return { family: m[3].replace(/^["']|["']$/g, ''), weight, style: m[1] ? 'italic' : 'normal' }; } /** True when a loaded FontFace covers exactly this family, weight and style * (document.fonts.check() is also true for families nobody declared). */ function hasFace(family, weight, style) { for (const face of document.fonts) { if (face.status !== 'loaded' || face.style !== style) continue; if (face.family.replace(/^["']|["']$/g, '') !== family) continue; const [low, high = low] = face.weight.split(' ').map(Number); if (weight >= low && weight <= high) return true; } return false; } /** Fontsource's id for a family: 'Source Serif 4' → 'source-serif-4'. */ function fontsourceId(family) { return family.toLowerCase().replace(/\s+/g, '-'); } /** The weights and styles a family ships ({ weights: [400, 700], styles: ['normal', 'italic'] }), or null. */ function fontsourceMeta(family) { fontsourceMeta.cache ??= new Map(); const id = fontsourceId(family); if (!fontsourceMeta.cache.has(id)) { fontsourceMeta.cache.set(id, fetch(`https://api.fontsource.org/v1/fonts/${id}`) .then((res) => (res.ok ? res.json() : null), () => null)); } return fontsourceMeta.cache.get(id); } // ─── Kit · viewer v1 ── the same in every recipe · postext.dev/cookbook ─────── /** Shows the pages as facing spreads on a dark desk: the first page is a * recto on its own, then verso | recto pairs, as in a bound book. Pages * are painted when they scroll near the screen. */ function showPages(docs, { title, width = 460 } = {}) { const root = viewer(title); const pages = [docs].flat().flatMap((doc) => doc.pages.map((page) => ({ doc, page, n: (doc.pageIndexOffset ?? 0) + page.index }))); const spreads = []; let verso = null; for (const p of pages) { if (p.n % 2 === 1) { if (verso) spreads.push([verso, null]); verso = p; } else { spreads.push([verso, p]); verso = null; } } if (verso) spreads.push([verso, null]); const density = Math.min(window.devicePixelRatio || 1, 2); showPages.painter?.disconnect(); const painter = new IntersectionObserver((entries) => { for (const { isIntersecting, target } of entries) { if (!isIntersecting) continue; painter.unobserve(target); const { doc, page } = target.postext; renderPageToCanvas(page, doc, target, { scale: (width * density) / page.width }); } }, { rootMargin: '800px' }); showPages.painter = painter; root.replaceChildren(...spreads.map((pair) => { const spread = document.createElement('div'); spread.className = 'pt-spread'; for (const p of pair) { const figure = document.createElement('figure'); if (p) { const label = p.page.pageLabel || String(p.n + 1); const canvas = document.createElement('canvas'); canvas.postext = p; canvas.style.aspectRatio = `${p.page.width} / ${p.page.height}`; canvas.setAttribute('role', 'img'); canvas.setAttribute('aria-label', `Page ${label}`); const folio = document.createElement('figcaption'); folio.textContent = label; figure.append(canvas, folio); painter.observe(canvas); } else figure.className = 'pt-blank'; spread.append(figure); } return spread; })); kitStatus(`${pages.length} ${pages.length === 1 ? 'page' : 'pages'}`); document.documentElement.dataset.postext = 'ready'; return pages.length; } /** The desk, the bar and the error reporting, created once. */ function viewer(title) { if (!document.getElementById('pt-kit')) { document.head.insertAdjacentHTML('beforeend', `<style id="pt-kit"> :root { color-scheme: dark; } body { margin: 0; background: #0e1014; color: #b9bcc4; font: 13px/1.45 system-ui, sans-serif; } #pt-bar { position: sticky; top: 0; z-index: 1; display: flex; flex-wrap: wrap; align-items: center; gap: 6px 16px; padding: 10px 16px; background: rgb(14 16 20 / .92); backdrop-filter: blur(6px); border-bottom: 1px solid #23262d; } #pt-bar strong { color: #f4f1ea; font-weight: 600; } #pt-actions { display: flex; gap: 12px; margin-left: auto; } #pt-actions a, #pt-actions button { color: #d8a21a; font: inherit; background: none; border: 0; padding: 0; cursor: pointer; } #pages { display: grid; justify-items: center; gap: 48px; padding: 32px 16px 72px; } .pt-spread { display: flex; } .pt-spread figure { margin: 0; width: min(460px, 44vw); } .pt-spread canvas { display: block; width: 100%; background: #fff; box-shadow: 0 1px 2px rgb(0 0 0 / .5), 0 22px 44px -16px rgb(0 0 0 / .8); } .pt-spread figure:first-child canvas { box-shadow: inset -14px 0 14px -14px rgb(0 0 0 / .18), 0 1px 2px rgb(0 0 0 / .5), 0 22px 44px -16px rgb(0 0 0 / .8); } .pt-spread figcaption { margin-top: 10px; text-align: center; font: 600 10px/1 system-ui, sans-serif; letter-spacing: .18em; text-transform: uppercase; color: #6c7079; } .pt-blank { visibility: hidden; } @media (max-width: 760px) { .pt-spread { flex-direction: column; gap: 32px; } .pt-spread figure { width: min(460px, 92vw); } .pt-blank { display: none; } } </style>`); document.body.insertAdjacentHTML('afterbegin', '<header id="pt-bar"><strong id="pt-title"></strong><span id="pt-status" role="status"></span><span id="pt-actions"></span></header>'); document.getElementById('pt-title').textContent = document.title || 'Postext'; addEventListener('error', (event) => kitFail(event.error ?? event.message)); addEventListener('unhandledrejection', (event) => kitFail(event.reason)); } if (title) document.getElementById('pt-title').textContent = title; return document.getElementById('pages') ?? document.body.appendChild(Object.assign(document.createElement('main'), { id: 'pages' })); } function kitStatus(text) { viewer(); document.getElementById('pt-status').textContent = text; } function kitFail(error) { document.documentElement.dataset.postext = 'error'; kitStatus(`Error: ${error?.message ?? error}`); } // ─── Kit · images v1 ── recipes with pictures · postext.dev/cookbook ────────── /** Registers a photo or PNG for the canvas and keeps its bytes for the PDF. * fetch → ImageBitmap never taints the canvas (a plain cross-origin <img> would). */ async function loadImage(fileId, url) { const res = await fetch(url); if (!res.ok) throw new Error(`Image not found (${res.status}): ${url}`); const bytes = new Uint8Array(await res.arrayBuffer()); registerResourceImage(fileId, await createImageBitmap(new Blob([bytes]))); (loadImage.bytes ??= new Map()).set(fileId, bytes); } /** Registers SVG markup (drawn in code, or fetched) as a vector image. */ async function loadSvg(fileId, svg) { const img = new Image(); img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`; await img.decode(); registerResourceImage(fileId, img); (loadImage.bytes ??= new Map()).set(fileId, new TextEncoder().encode(svg)); } /** renderToPdf({ resourceBytes: imageBytes }) */ function imageBytes(fileId) { return loadImage.bytes?.get(fileId); } /** renderToHtml({ resourceImageUrl: imageUrl }) */ function imageUrl(fileId) { const bytes = imageBytes(fileId); if (!bytes) return undefined; imageUrl.urls ??= new Map(); if (!imageUrl.urls.has(fileId)) { const type = /\.svg$/i.test(fileId) ? 'image/svg+xml' : /\.png$/i.test(fileId) ? 'image/png' : 'image/jpeg'; imageUrl.urls.set(fileId, URL.createObjectURL(new Blob([bytes], { type }))); } return imageUrl.urls.get(fileId); } // ─── /Kit ───────────────────────────────────────────────────────────────────────
L'script.js compost funciona tal com és: enganxa'l com a script de mòdul en qualsevol pàgina o obre la recepta a CodePen. Carpeta de la recepta a GitHub ↗ (s'obre en una pestanya nova)
Variants
#Deixa que el lector engegui cada clip
Sense autoplay, les fotos es queden quietes fins que el lector en clica una; un cop engegats, els bucles es reprodueixen junts igualment.
- player: { autoplay: true, loop: true, muted: true, exclusive: false, controls: false },
+ player: { loop: true, muted: true, exclusive: false, controls: false },#Un clip exclusiu amb el seu so
Un vídeo es pot quedar amb el valor per defecte i reproduir-se pel seu compte: posa player: { exclusive: true, muted: false } al video d'aquest recurs, i quan s'engega els bucles esperen fins que s'acaba.
#Un diari de gran format a sis columnes
La portada a dues columnes compon un diari més petit amb fotos fixes; la mateixa retícula admet sis columnes en un format llençol amb sizePreset: 'broadsheet'.
Errors freqüents
Error freqüent
Qualsevol objecte headings desactiva el salt de pàgina de l'H1
Per defecte un H1 salta a una pàgina senar (always-odd), però qualsevol objecte headings anul·la aquest valor, de manera que els capítols van seguits i span: 'page' no fa res. Torna a declarar headings.levels[0].breakBefore: { enabled: true, parity } a cada configuració. Capítols que obren en pàgina senar →
Error freqüent
Carrega totes les fonts abans de compondre
La composició mesura el text amb les fonts que el navegador ha carregat i en desa les amplades, així que una font que arriba després de la primera composició deixa talls de línia erronis i un PDF que ja no coincideix amb la pantalla. Carrega abans tots els pesos i estils, i crida clearMeasurementCache() abans de recompondre si alguna arriba tard. Fonts abans de compondre →
Error freqüent
El desbordament del text de disseny és 'ellipsis-end' per defecte
Un element de text de disseny que no cap en la seva amplada acaba en punts suspensius per defecte. Posa overflow: 'wrap' als títols que hagin de passar a més línies. Textos, filets i caixes als dissenys de pàgina →
Error freqüent
:::columns només funciona dins d'un requadre i no es parteix
:::columns s'ignora fora d'un requadre, i un requadre que es parteix mai no talla dins d'un grup de columnes. L'atribut breaks compta blocs fills, i un requadre imbricat compta com un. Columnes dins d'un requadre →
Una secció de diari és un títol de nivell 1 que obre pàgina, i un flotant no el travessa mai. Una foto que no troba buit a l'última pàgina de la seva secció es col·loca en una pàgina pròpia abans de la secció següent, així que ajusta el text a la pàgina: aquí la notícia dels tramvies salta a la pàgina 2 en lloc de fer fora l'aviadora de la portada.
Crèdits
- Recepta
- Ignacio Ferro
- Text
- Text original, CC BY 4.0
- Imatges
- Mrs Edith Rowe speaks at the opening of the Harbour line: a black-and-white loop of 5 s, a still generated from a text prompt and animated with the same first and last frame, encoded with ffmpeg · Generated With Diffusion Models · CC BY 4.0
- The first frame of the mayor loop, its printed picture · Generated With Diffusion Models · CC BY 4.0
- The Guildhall clock tower: a black-and-white loop of 5 s, a still generated from a text prompt and animated with the same first and last frame, encoded with ffmpeg · Generated With Diffusion Models · CC BY 4.0
- The first frame of the clock loop, its printed picture · Generated With Diffusion Models · CC BY 4.0
- Skipper Jacob Hosking with the cod: a black-and-white loop of 5 s, a still generated from a text prompt and animated with the same first and last frame, encoded with ffmpeg · Generated With Diffusion Models · CC BY 4.0
- The first frame of the fisherman loop, its printed picture · Generated With Diffusion Models · CC BY 4.0
- Miss Constance Lacey in the cockpit: a black-and-white loop of 5 s, a still generated from a text prompt and animated with the same first and last frame, encoded with ffmpeg · Generated With Diffusion Models · CC BY 4.0
- The first frame of the aviator loop, its printed picture · Generated With Diffusion Models · CC BY 4.0
- Miss Rosalind Avery on the stage: a black-and-white loop of 5 s, a still generated from a text prompt and animated with the same first and last frame, encoded with ffmpeg · Generated With Diffusion Models · CC BY 4.0
- The first frame of the actress loop, its printed picture · Generated With Diffusion Models · CC BY 4.0
- Tom Rundle in goal: a black-and-white loop of 5 s, a still generated from a text prompt and animated with the same first and last frame, encoded with ffmpeg · Generated With Diffusion Models · CC BY 4.0
- The first frame of the keeper loop, its printed picture · Generated With Diffusion Models · CC BY 4.0
- Fonts
- Libre Caslon Text (SIL OFL 1.1) · Oswald (SIL OFL 1.1) · Courier Prime (SIL OFL 1.1)
- Codi
- MIT, com Postext
Editar aquest text ↗ (s'obre en una pestanya nova)Carpeta de la recepta a GitHub ↗ (s'obre en una pestanya nova)


