In short
The puzzle page of a children's magazine about the sea. Each puzzle sits on its own card, and its answers are printed upside down underneath, so readers turn the magazine round to check.
What you'll build
The puzzle page of Splash!, a children's magazine about the sea, on two A4-sized pages in two columns. A reef painting fills the top of the first page with the title over its open water. Below it, each puzzle sits on a cream card with a coral number: a crossword built from six sea creatures and their clues, a word search that hides eight words across, down, diagonally and backwards, a riddle with three options to ring, a turtle's life to put in order, a mixed-up fact to unscramble and baby animals to join with their names. Under every puzzle, inside its card, the answers are printed upside down in small grey type, the way puzzle magazines have always kept them out of sight.
This recipe answers
- How do I make a word search or a crossword from a list of words?
- How do I print the answers upside down at the foot of an exercise?
The short answer
// placement 'inverted' sets each puzzle's key after it in a box turned 180°, inside the
// puzzle's card, so a page break never parts a puzzle from its answers. The key is made
// from the puzzle: the crossword's words by number, the ringed option, the right order.
const puzzle = {
id: 'puzzle',
box: 'card', // the card each puzzle sits on (a calloutStyles id)
number: { shape: 'disc', fontFamily: DISPLAY, background: col('coral') },
instruction: { fontFamily: TEXT, bold: false },
options: { marker: 'circle', labels: 'none' }, // a ring to colour in, no letters
grid: { fontFamily: LABEL, rule: col('sea'), cell: em(1.45) },
match: { color: col('sea') },
area: { kind: 'lines', color: col('sea') },
answers: { color: col('coral'), fontFamily: LABEL },
solutions: { placement: 'inverted', box: 'turned' },
marginTop: mm(0), marginBottom: mm(0),
};
const card = { id: 'card', title: '', background: col('card'), borderRadius: mm(4),
padding: { top: mm(3.5), right: mm(4), bottom: mm(3), left: mm(4) },
titleStyle: { fontFamily: DISPLAY, fontSize: pt(15), fontWeight: 700, color: col('ink') },
body: { fontFamily: TEXT, fontSize: pt(10.5), lineHeight: pt(15), color: col('ink'),
firstLineIndent: pt(0) },
marginTop: mm(0), marginBottom: mm(5), keepTogether: true };
const turned = { id: 'turned', title: '', backgroundEnabled: false, border: { enabled: false },
padding: { top: pt(0), right: pt(0), bottom: pt(0), left: pt(0) },
body: { fontFamily: LABEL, fontSize: pt(8.5), lineHeight: pt(11), color: col('muted'),
firstLineIndent: pt(0) },
marginTop: mm(2), marginBottom: mm(0) };
Every puzzle on a card, its answers turned upside down under it
Ingredients
- Features
- Word searches and crosswordsSolutionsExercise stylesExercisesChoice and true or falseMatching and sortingOrderingOpen questions and writing areasCallout boxesThree or more columnsDesigned openersHeading attributesPictures in page designsFigures and tables as resourcesSemantic colour palettePDF export
- Also uses
- Boxes that split or stay wholeColumn balancingFigures exactly hereFull-width chapter bandHeads by page roleParagraph stylesFonts embedded in the PDFCustom resource types
- Type
- Baloo 2, Nunito, Fredoka (SIL OFL 1.1)
- Assets
hatchling-600.jpgreef-1600x792.jpg- A coral reef with a smiling sea turtle, an octopus, a clownfish, a pufferfish and a seahorse under sunbeams. (Generated With Diffusion Models, original)
- A baby sea turtle hatching from its egg on the sand. (Generated With Diffusion Models, original)
Method
#1 · Every puzzle on a card, its answers turned
The code is the short answer above. The exercise style's box names a box style, so every puzzle is set on a card, its number in a disc and its title as the first line of its instruction. placement: 'inverted' sets the key after each puzzle in a box turned 180°, inside the card, so the two never fall on different pages; the grids and the writing line stay inside the card's padding.
#2 · Let the engine build the grids
The Markdown holds only the words: - OCTOPUS: Eight arms and three hearts for the crossword, - SHELL for the word search. The engine lays the crossword with every word crossing another and numbers the starts in reading order, and hides the search words in the directions the fence names (directions="across down diagonal backwards"), filling the rest with the words' own letters. Both are seeded, so the grid is the same on every run, in every output, and the Spanish edition builds its own grid from its own words.
#3 · A reef across both columns
const BAND = 104; // mm from the top edge: the painting's height at the page's width
const opener = { enabled: true, minHeight: mm(BAND - 16 + 6), slot: { elements: [
{ kind: 'image', id: 'reef', resourceId: 'reef', decorative: true, reserve: false,
placement: { anchor: { to: 'page', edge: 'top-left' },
size: { width: mm(210), height: mm(BAND) } } },
{ kind: 'text', id: 'kicker', content: '{attr.kicker}', fontFamily: LABEL, fontSize: pt(11),
fontWeight: 600, color: col('paper'), textTransform: 'uppercase', letterSpacing: pt(2),
box: { backgroundColor: col('coral'), borderRadius: mm(2),
padding: { top: mm(0.8), bottom: mm(0.8), left: mm(2.4), right: mm(2.2) } },
placement: { anchor: { to: 'page', edge: 'top-left' }, offset: { x: mm(16), y: mm(14) } } },
{ kind: 'text', id: 'title', content: '{titleText}', fontFamily: DISPLAY, fontSize: pt(56),
fontWeight: 800, lineHeight: 1, color: col('paper'),
placement: { anchor: { to: '#kicker', edge: 'below' }, offset: { x: mm(0), y: mm(3) } } },
{ kind: 'text', id: 'sub', content: '{attr.sub}', fontFamily: TEXT, fontSize: pt(12),
fontWeight: 700, color: col('paper'), overflow: 'wrap', align: 'left',
box: { backgroundColor: col('ink'), borderRadius: mm(2),
padding: { top: mm(1.5), bottom: mm(1.5), left: mm(3), right: mm(3) } },
placement: { anchor: { to: '#title', edge: 'below' }, offset: { x: mm(0), y: mm(2) },
size: { width: mm(110) } } },
] } };
The opener spans both columns (span: 'page'), so the puzzles start under the painting in each column. The picture is cut to the band's proportions and anchored to the page, so it bleeds at the top and the sides; the standfirst sits in a dark box so it reads over the water.
The whole recipe
// ═══ Postext Cookbook · Nº 174 · A puzzle page with the answers upside down ═══════
// https://postext.dev/en/cookbook/puzzle-page-upside-down-answers
// Code: MIT · Text: original (CC BY 4.0) · Pictures: generated with diffusion models
// Fonts: Baloo 2, Nunito, Fredoka (SIL OFL 1.1) · Needs postext ≥ 1.59.0
import {
buildDocumentWithFonts, renderPageToCanvas, registerResourceImage,
} from 'https://esm.sh/postext';
import { renderToPdf, decompressWoff2 } from 'https://esm.sh/postext-pdf';
const LANG = 'en'; // @lang: the language of the sample document ('en' | 'es')
const RECIPE = 'puzzle-page-upside-down-answers';
// ─── 1 · Design ─────────────────────────────────────────────────────────────
// #region palette: reef colours: deep blue ink, coral, sea green, a sandy paper
const palette = {
ink: '#16324a', // text: a deep sea blue
coral: '#e8573f', // the accent: puzzle numbers, the title kicker
sea: '#0f8b8d', // the second: crossword and word-search rules, the riddle's rings
sun: '#ffc93c', // the turtle card's badge
card: '#fff4e0', // the puzzle cards
muted: '#5d7180', // answers, colophon, folio
paper: '#ffffff',
};
const col = (id) => ({ hex: palette[id], model: 'hex', paletteId: id });
const colorPalette = [
...Object.entries(palette).map(([id, hex]) => ({ id, name: id, value: { hex, model: 'hex' } })),
{ id: 'main-color', name: 'accent (defaults)', value: { hex: palette.coral, model: 'hex' } },
];
// #endregion
const [DISPLAY, TEXT, LABEL] = ['Baloo 2', 'Nunito', 'Fredoka'];
// #region answer: every puzzle on a card, its answers turned upside down under it
// placement 'inverted' sets each puzzle's key after it in a box turned 180°, inside the
// puzzle's card, so a page break never parts a puzzle from its answers. The key is made
// from the puzzle: the crossword's words by number, the ringed option, the right order.
const puzzle = {
id: 'puzzle',
box: 'card', // the card each puzzle sits on (a calloutStyles id)
number: { shape: 'disc', fontFamily: DISPLAY, background: col('coral') },
instruction: { fontFamily: TEXT, bold: false },
options: { marker: 'circle', labels: 'none' }, // a ring to colour in, no letters
grid: { fontFamily: LABEL, rule: col('sea'), cell: em(1.45) },
match: { color: col('sea') },
area: { kind: 'lines', color: col('sea') },
answers: { color: col('coral'), fontFamily: LABEL },
solutions: { placement: 'inverted', box: 'turned' },
marginTop: mm(0), marginBottom: mm(0),
};
The composed script.js runs as it is: paste it into any page’s module script, or open the recipe on CodePen. Recipe folder on GitHub ↗ (opens in a new tab)
Variations
#Hide the word list
A harder word search prints only the grid: add hidden to the fence.
-:::exercise{type=wordsearch size=9 directions="across down diagonal backwards"}
+:::exercise{type=wordsearch size=9 directions="across down diagonal backwards" hidden}#Print the answers at the end of the magazine
chapterEnd collects every key at the end instead of turning each one.
- solutions: { placement: 'inverted', box: 'turned' },
+ solutions: { placement: 'chapterEnd' },Pitfalls
- A crossword word that crosses no other is left out and reported as
exerciseWordUnplaced. Choose words that share letters, or add one that links two groups. - A puzzle card is kept together, so a tall crossword moves to the next column when it does not fit: check the Spanish edition, whose words are longer.
Credits
- Recipe
- Ignacio Ferro
- Text
- Original prose, CC BY 4.0
- Images
- A coral reef with a smiling sea turtle, an octopus, a clownfish, a pufferfish and a seahorse under sunbeams. · Generated With Diffusion Models · original
- A baby sea turtle hatching from its egg on the sand. · Generated With Diffusion Models · original
- Fonts
- Baloo 2 (SIL OFL 1.1) · Nunito (SIL OFL 1.1) · Fredoka (SIL OFL 1.1)
Edit this write-up ↗ (opens in a new tab)Recipe folder on GitHub ↗ (opens in a new tab)


