# -*- coding: utf-8 -*-
"""Український текст для Level 2 A1 — «Бог створює світ».

Цитати — Турконяк (УБТ, 2020). Не перекладається: `bibletime`, `bes`,
`www.besweb.com`, реєстраційні дані, імена персонажів, маркер `Q:`.
"""
import json
import os
import random

TR = {}
POST = []
WIPES = []

HERE = os.path.dirname(os.path.abspath(__file__))
RED = (0.917, 0.317, 0.353)          # #ea515a — фірмовий колір рівня 2

with open(os.path.join(HERE, 'work', 'Level2_A1', 'template.json'),
          encoding='utf-8') as _fh:
    _TPL = json.load(_fh)
IDX = {f"p{p['page']}b{b['i']}": b for p in _TPL for b in p['blocks']}


def Q(key, *text_lines, qs=0, ts=1, tail=()):
    TR[key] = {'lines': [[('Q:', qs)]] + [[(t, ts)] for t in text_lines]
               + [[seg] for seg in tail]}


def rows(key, *text_lines, s=0):
    TR[key] = {'lines': [[(t, s)] for t in text_lines]}


def head(page, block, big=False, with_name=False):
    lines = []
    if with_name:
        lines.append([("Ім'я:", 0)])
    i = 1 if with_name else 0
    lines.append([('bible', i), ('time', i + 1)])
    if big:
        lines.append([('www.besweb.com', i + 2)])
        lines.append([('Рівень 2 — біблійні історії', i + 3)])
        lines.append([('A1', i + 4)])
    else:
        lines.append([('Рівень 2 — біблійні історії', i + 2)])
        lines.append([('A1', i + 3)])
    TR[f'p{page}b{block}'] = {'lines': lines}


def keyverse(key, ref):
    TR[key] = {'right': 566.0, 'lines': [
        [('КЛЮЧОВИЙ ВІРШ:', 0)], [(ref, 1)],
        [('Попроси когось', 1)], [('допомогти знайти його.', 1)]]}


# ═══════════════════════════════════════════════════════════ сторінка 1
rows('p0b0', 'Впиши тут своє імʼя та адресу.', 'Вік:', "Ім'я:", 'Адреса:')
for _i, _t in ((1, 'Вік:'), (2, "Ім'я:"), (3, 'Адреса:')):
    TR['p0b0']['lines'][_i] = [(_t, 1)]
rows('p0b1', 'Учитель:')
head(0, 2, big=True)
TR['p0b3'] = {'lines': [[('Рівень 2', 0)]]}
TR['p0b4'] = {'lines': [
    [('B.E.S.', 0)], [('мудрий друг', 1)],
    [('Q', 0)], [('ставить', 1)], [('запитання', 1)]]}
TR['p0b5'] = {'right': 514.0, 'lines': [
    [('Pause', 0)], [('любить', 1)], [('подумати', 1)]]}
TR['p0b6'] = {'lines': [
    [('Beam', 0)], [('друг, який', 1)], [('підбадьорює', 1)]]}
TR['p0b43'] = {'lines': [[('ІСТОРІЯ 1: ', 0), ('Бог створює світ', 0)]]}
TR['p0b44'] = {'lines': [[('Дні 1-4', 0)]]}
keyverse('p0b46', 'Буття 1: 1')
TR['p0b48'] = {'lines': [[('ЧИТАЙ:', 0)], [('Буття 1: 1-19', 1)]]}
TR['p0b50'] = {'flow': (
    'Скільки часу пішло б у тебе на будівництво власного будинку? Чи можеш ти '
    'уявити, скільки всього для цього потрібно?')}
TR['p0b51'] = {'flow': (
    'Коли Бог творив світ, зробити треба було так багато — як же Він це зробив? '
    'Ось що каже перший вірш Біблії:')}
TR['p0b52'] = {'lines': [
    [('«На початку Бог створив небо', 0)],
    [('і землю». Буття 1: 1', 0)],
]}
TR['p0b53'] = {'flow': (
    'Це означає, що Він створив їх із нічого. До того, як Бог створив світ, була '
    'сама лише темрява. Бог такий великий, що коли Він творив світ, Йому досить '
    'було просто сказати слово.')}
TR['p0b54'] = {'lines': [
    [('День 1', 0)],
    [('Першого дня Бог сказав: «Нехай буде світло». І одразу', 0)],
    [('стало світло. Світло Бог назвав днем, а темряву —', 0)],
    [('ніччю. Замість суцільної темряви настав день,', 0)],
    [('а за ним ніч.', 0)],
]}
TR['p0b55'] = {'lines': [
    [('День 2', 0)],
    [('Другого дня Бог знову промовив і створив небо', 0)],
    [('та повітря, що оточують землю. Знову настала', 0)],
    [('ніч, і другий день скінчився.', 0)],
]}
Q('p0b56', 'Розфарбуй малюнки.')
Q('p0b57', 'Що Бог створив другого дня?', '__ __ __ __  і  __ __ __ __ __ __ __ __ .')

# ═══════════════════════════════════════════════════════════ сторінка 2
TR['p1b1'] = {'lines': [[('УСЬОГО:', 0)]]}
head(1, 2)
TR['p1b6'] = {'lines': [
    [('День 3', 0)],
    [('Досі земля була вкрита водою, але тепер настав', 0)],
    [('час створити сушу. Бог промовив — і вода', 0)],
    [('відступила, щоб дати місце новій землі.', 0)],
]}
TR['p1b7'] = {'lines': [
    [('Вода навколо суходолу стала морем.', 0)],
    [('Тоді Бог знову промовив — і земля вкрилася', 0)],
    [('всілякими рослинами та деревами. Бог поглянув', 0)],
    [('на все, що створив, і сказав, що це добре.', 0)],
]}
TR['p1b8'] = {'lines': [
    [('День 4', 0)],
    [('Четвертого дня Бог створив особливі світила, щоб', 0)],
    [('сяяли в небі: сонце — для дня, а місяць і зорі —', 0)],
    [('для ночі. Бог поглянув на створені світила', 0)],
    [('і побачив, що це добре.', 0)],
]}
Q('p1b9', 'Розфарбуй малюнки.')
Q('p1b10', 'Подивись на дні 1-4 і запиши, якого дня Бог', 'створив:')
rows('p1b11', 'сушу, рослини й дерева.', 'ДЕНЬ:')
rows('p1b12', 'світила для неба.', 'ДЕНЬ:')
Q('p1b13', 'Впиши пропущені літери й намалюй світила, які створив Бог.')
rows('p1b15', 'С __ __ __ __ .', 'М __ __ __ __ __ .', 'З __ __ __ .')
TR['p1b16'] = {'flow': (
    'Коли ми бачимо навколо стільки прекрасного, варто памʼятати: усе це '
    'створив Бог, і за це Йому треба дякувати.')}
Q('p1b17', 'Впиши пропущені слова з першого вірша Біблії.')
TR['p1b18'] = {'lines': [[('«На початку __ __ __ створив __ __ __ __', 0)]]}
TR['p1b19'] = {'lines': [[('і __ __ __ __ __ ».', 0)]]}
TR['p1b20'] = {'lines': [[('Буття 1: 1', 0)]]}

# ═══════════════════════════════════════════════════════════ сторінка 3
head(2, 1, with_name=True)
TR['p2b2'] = {'lines': [[('ІСТОРІЯ 2: ', 0), ('Бог створює тварин і людей', 0)]]}
TR['p2b3'] = {'lines': [[('Дні 5-7', 0)]]}
keyverse('p2b5', 'Буття 1: 31')
TR['p2b7'] = {'lines': [
    [('ЧИТАЙ:', 0)], [('Буття', 1)], [('1: 20 — 2: 3', 1)]]}
TR['p2b10'] = {'flow': (
    'Інколи ми буваємо розчаровані тим, що зробили самі. А коли Бог створив '
    'світ, Він сказав, що все дуже добре!')}
TR['p2b11'] = {'lines': [
    [('День 5', 0)],
    [("Пʼятого дня Бог створив тисячі й тисячі різних птахів і риб.", 0)],
    [('Що Він сказав, коли створив їх?', 0)],
]}
Q('p2b12', 'Викресли неправильні відповіді.')
rows('p2b13', 'Було погано.', 'Було добре.', 'Було зле.')
TR['p2b14'] = {'lines': [
    [('День 6', 0)],
    [('Шостого дня Бог створив тварин. Жирафа з довгою шиєю, лева', 0)],
    [('і тигра з гострими зубами — усіх створив Бог. Біблія каже, що', 0)],
    [('лише Бог може створити таке дивовижне життя.', 0)],
]}
Q('p2b15', 'Що може Бог, чого не може ніхто інший?')
TR['p2b16'] = {'lines': [[('Лише Бог може створити таке дивовижне ж __ __ __ __ .', 0)]]}
Q('p2b17', 'Домалюй у саду ще чотирьох тварин.', 'Розфарбуй малюнок.')

# ═══════════════════════════════════════════════════════════ сторінка 4
TR['p3b1'] = {'lines': [[('УСЬОГО:', 0)]]}
head(3, 2)
TR['p3b3'] = {'flow': (
    'Хоча Бог створив уже багато прекрасного, лишалося ще одне — і це Він '
    'зробив в особливий спосіб.')}
TR['p3b4'] = {'flow': (
    'Він узяв пороху з землі й створив першу людину! Людина ожила, коли Бог '
    'вдихнув у неї подих життя. Першого чоловіка звали Адам.')}
Q('p3b5', 'Розфарбуй ПРАВИЛЬНІ слова в рамках:')
rows('p3b6', 'З чого Бог створив першу людину?', 'порох', 'вода', 'мавпи')
rows('p3b7', 'Як звали першого чоловіка? Андрій', 'Адам', 'Авель')
TR['p3b8'] = {'flow': (
    'Люди — особливі, не такі, як усе інше, що створив Бог. Біблія каже, що ми '
    'створені подібними до Бога. Це означає, що ми вміємо творити, думати. Це '
    'означає й те, що ми відрізняємо добро від зла!')}
TR['p3b9'] = {'lines': [
    [('День 7', 0)],
    [('Бог створив світ і все, що в ньому, за шість', 0)],
    [('днів. Сьомого дня Він спочив і радів усьому,', 0)],
    [('що створив.', 0)],
]}
Q('p3b10', 'Знайди слова, заховані у філворді.')
TR['p3b11'] = {'lines': [[('птахи     риби     тварини     людина     спочив', 0)]]}
Q('p3b12', 'Впиши пропущені літери.')
TR['p3b13'] = {'lines': [[('Бог с __ __ __ __ __ сьомого д __ __ .', 0)]]}
TR['p3b14'] = {'lines': [
    [('Q:', 0)],
    [('Розфарбуй ці слова з Біблії.', 1)],
    [('«Усе через Нього', 2, None, 1)],
    [(' постало».', 2, None, 1)],
]}
TR['p3b15'] = {'lines': [[('Івана 1: 3', 0)]]}
TR['p3b16'] = {'lines': [[('Біблія вчить, що лише Бог може дати життя — і Він дав життя нам.', 0)]]}
TR['p3b17'] = {'lines': [[('Він хоче, щоб ми жили так, як Йому до вподоби.', 0)]]}
rows('p3b20', 'А як Бог', 'створив першу', 'жінку?')
rows('p3b38', 'Гарне', 'запитання, Q!', 'Дізнаєшся про це', 'в наступній історії!')

# ═══════════════════════════════════════════════════════════ сторінка 5
head(4, 1, with_name=True)
TR['p4b2'] = {'lines': [[('ІСТОРІЯ 3: ', 0), ('Перші чоловік і жінка', 0)]]}
TR['p4b3'] = {'lines': [[('Адам і Єва в саду', 0)]]}
keyverse('p4b5', 'Буття 2: 18')
TR['p4b7'] = {'lines': [[('ЧИТАЙ:', 0)], [('Буття 2: 4-25', 1)]]}
TR['p4b10'] = {'flow': (
    'Адам жив у прекрасному саду, що звався Едем. Тут Бог дав йому все, що було '
    'потрібно, — їжу й воду. Ще Бог доручив Адамові доглядати сад.')}
TR['p4b11'] = {'flow': (
    'Бог показав Адамові всіх створінь, яких зробив. І Адам дав імʼя кожній '
    'живій істоті.')}
Q('p4b12', 'Пройди головоломку й впиши пропущені слова.')
TR['p4b15'] = {'lines': [[('СТАРТ', 0)]]}
TR['p4b17'] = {'lines': [[('__ __ __ __  __ __ __  __  __ __ __ __ ,', 0)]]}
TR['p4b18'] = {'lines': [[('__ __ __ __  __ __ __ __ __ __  __ __ __ __ .', 0)]]}
TR['p4b19'] = {'flow': (
    'Боже творіння ще не було завершене. Бог навів на Адама глибокий сон, і '
    'поки той спав, створив із його ребра першу жінку. Тепер Адам був не сам: '
    'у нього зʼявилася дружина й помічниця, разом з якою він доглядав сад, куди '
    'його поселив Бог.')}
# Акростих із фото (яйце, скрипка, око → EVE) українською не складається:
# назви предметів задані картинками. Клітинки лишаються, механіка змінена.
Q('p4b20', 'Впиши в клітинки імʼя', 'першої жінки.')
Q('p4b24', 'Обведи правильне слово, щоб доповнити речення.')
TR['p4b25'] = {'lines': [
    [('Єва була Адамові', 0)], [('ворогом.', 1)], [('рабинею.', 1)],
    [('дружиною.', 1)], [('/ 2 ', 2)],
]}

# ═══════════════════════════════════════════════════════════ сторінка 6
TR['p5b1'] = {'lines': [[('УСЬОГО:', 0)]]}
head(5, 2)
TR['p5b3'] = {'lines': [
    [('У саду росло багато дерев. Бог сказав Адамові', 0)],
    [('та Єві, що вони можуть їсти плоди з кожного', 0)],
    [('дерева, крім одного — «дерева пізнання', 0)],
    [('добра і зла».', 0)],
]}
Q('p5b4', 'Обведи дерево, з якого Адам і Єва не мали їсти.')
rows('p5b5', 'Апельсин', 'Пізнання', 'Вишня', 'Кокос', 'добра і зла')
TR['p5b6'] = {'flow': (
    'Бог створив прекрасний світ. Перші чоловік і жінка, Адам і Єва, доглядали '
    'його. Сьогодні Бог хоче, щоб ми робили все, що можемо, аби подбати про '
    'все, що Він створив.')}
Q('p5b7', 'Намалюй у рамках, як ми можемо дбати про світ.')
TR['p5b8'] = {'lines': [[('Q:', 0)], [('Розфарбуй вірш унизу.', 1)]]}
TR['p5b9'] = {'render': 1, 'border': 0.5, 'lines': [
    [('«Господня земля', 0)],
    [('і все, що', 0)],
    [('наповнює її».', 0)],
]}
TR['p5b10'] = {'lines': [[('Псалом 24: 1', 0)]]}
rows('p5b13', 'Цікаво, що', 'було б, якби', 'ми не мали', 'жодних правил?')

# ═══════════════════════════════════════════════════════════ сторінка 7
head(6, 1, with_name=True)
TR['p6b2'] = {'lines': [[('ІСТОРІЯ 4: ', 0), ('Божий світ зіпсовано', 0)]]}
TR['p6b3'] = {'lines': [[('Гріх входить у світ', 0)]]}
keyverse('p6b5', 'Римлян 5: 1')
TR['p6b7'] = {'lines': [[('ЧИТАЙ:', 0)], [('Буття 3: 1-24', 1)]]}
TR['p6b10'] = {'flow': (
    'Коли світ був щойно створений, усе було досконале. Не було ні хвороб, ні '
    'смутку, ні смерті. Та одного дня в сад прийшов Божий ворог, сатана. Він '
    'був у подобі змія. Сатана заговорив до Єви й сказав, що коли вона зʼїсть '
    'плід із «дерева пізнання добра і зла», то стане мудрою, як Бог!')}
Q('p6b11', 'Хто такий Божий ворог? __ __ __ __ __ __')
TR['p6b12'] = {'lines': [[('До кого він заговорив? __ __ __', 0)]]}
TR['p6b13'] = {'flow': (
    'Єва послухала Божого ворога. Вона не послухалася Бога і взяла плід. '
    'Частину дала Адамові, і обоє зʼїли.')}
Q('p6b14', 'Напиши ТАК або НІ після кожного твердження.')
TR['p6b15'] = {'lines': [[('Єва послухала сатану.', 0)]]}
TR['p6b16'] = {'lines': [[('Єва послухалася Бога.', 0)]]}
TR['p6b17'] = {'lines': [[('Єва дала плід сатані.', 0)]]}
TR['p6b18'] = {'lines': [[('Адам і Єва зʼїли плід.', 0)]]}
Q('p6b19', 'Впиши літери з крапкою в клітинки внизу — і дізнаєшся, що')
TR['p6b20'] = {'lines': [[('каже Біблія.', 0)]]}
TR['p6b55'] = {'lines': [[('__ __ __  __ __ __ __ __ __  __ __ __  __ __ __ __ __ __ .', 0)]]}
TR['p6b56'] = {'flow': (
    'Того вечора Бог покликав Адама, щоб дізнатися, де він. Але Адам і Єва '
    'знали, що не послухалися Бога, і сховалися від Нього.')}
TR['p6b57'] = {'flow': (
    'Бог знав, що вони не послухалися Його, і хоч Він і далі їх любив, вони '
    'мали понести кару за свій непослух. Бог вигнав Адама і Єву із саду.')}

# ═══════════════════════════════════════════════════════════ сторінка 8
TR['p7b0'] = {'lines': [[('Бали', 0), (' (вписує вчитель)', 1)]]}
for _i in range(1, 5):
    TR[f'p7b{_i}'] = {'lines': [[(f'ІСТОРІЯ {_i}:', 0)]]}
TR['p7b5'] = {'lines': [[('Разом', 0)]]}
TR['p7b6'] = {'lines': [[('Загалом', 0)]]}
rows('p7b7', 'Коментар учителя:', 'Надішли свої уроки на адресу:')
TR['p7b8'] = {'lines': [[('УСЬОГО:', 0)]]}
TR['p7b9'] = {'lines': [
    [('©', 0), (' Bible Educational Services. Відвідай ', 1), ('www.besweb.com', 2),
     (' або телефонуй +44 (0) 28 6632 2462.', 1)],
    [('Registered Charity UK CIO 1186004', 1)],
    [('РІВЕНЬ 2 A1', 3)],
    [('01/2025', 4)],
]}
TR['p7b10'] = {'flow': (
    'Їхній гріх зіпсував Боже творіння і зруйнував їхню дружбу з Богом. Гріх '
    'завжди псує. Він не лише засмучує нас — він руйнує Божий світ. Через гріх '
    'Адама і Єви зʼявилися терни та будяки. Тварини й люди почали хворіти, а '
    'найгірше — у світ прийшла смерть.')}
Q('p7b11', 'Доповни речення:')
TR['p7b12'] = {'lines': [[('Гріх завжди п __ __ __ __ .', 0)]]}
TR['p7b13'] = {'lines': [[('Гріх привів у світ с __ __ __ __ __ .', 0)]]}
TR['p7b14'] = {'flow': (
    'Хоч Бог, напевно, дуже гнівався на Адама і Єву, Він не забув про них. Він '
    'пообіцяв, що одного дня сатана буде знищений, а дорога назад до Бога стане '
    'можливою.')}
TR['p7b15'] = {'flow': (
    'Саме тому Ісус прийшов у світ. Ісус прожив досконале життя й ніколи не '
    'порушив Божої волі. Але коли Він помер на хресті, Його було покарано за '
    'гріх усього світу.')}
TR['p7b16'] = {'flow': (
    'Біблія каже, що нам може бути прощено, коли ми повіримо, що Ісус поніс '
    'кару за наш гріх. Тоді ми можемо дякувати Богові й знати Його як Отця '
    'і Друга.')}
TR['p7b17'] = {'lines': [
    [('Q:', 0)],
    [('Розфарбуй і вивчи ключовий вірш:', 1)],
    [('«Ми маємо мир з', 2, None, 1)],
    [('Богом через Господа', 2, None, 1)],
    [('нашого Ісуса Христа».', 2, None, 1)],
]}
TR['p7b18'] = {'lines': [[('Римлян 5: 1', 0)]]}
rows('p7b48', 'Що означає', 'мати мир з Богом?', '', 'Це означає, що коли', 'ми віримо в Ісуса,',
     'наша дружба з Богом', 'відновлюється.')
rows('p7b49', 'Це і є', 'Добра звістка!')


# ═══════════════════════════════════════════════ головоломки
import fitz                                                   # noqa: E402
from puzzlekit import cover                                   # noqa: E402

ALPHABET = 'АБВГДЕЄЖЗИІЇЙКЛМНОПРСТУФХЦЧШЩЮЯ'
ALL_DIRS = [(0, 1), (1, 0), (0, -1), (-1, 0), (1, 1), (-1, -1), (1, -1), (-1, 1)]

# ── філворд 7×6, стор. 4 ─────────────────────────────────────────────────
WS_WORDS = ['ПТАХИ', 'РИБИ', 'ТВАРИНИ', 'ЛЮДИНА', 'СПОЧИВ']


def _make(rows_n, cols_n, words, seed):
    rnd = random.Random(seed)
    grid = [[None] * cols_n for _ in range(rows_n)]
    for w in sorted(words, key=len, reverse=True):
        for _ in range(40000):
            dr, dc = rnd.choice(ALL_DIRS)
            r, c = rnd.randrange(rows_n), rnd.randrange(cols_n)
            er, ec = r + dr * (len(w) - 1), c + dc * (len(w) - 1)
            if not (0 <= er < rows_n and 0 <= ec < cols_n):
                continue
            if any(grid[r + dr * i][c + dc * i] not in (None, ch)
                   for i, ch in enumerate(w)):
                continue
            for i, ch in enumerate(w):
                grid[r + dr * i][c + dc * i] = ch
            break
        else:
            raise RuntimeError('не вклалося: ' + w)
    for r in range(rows_n):
        for c in range(cols_n):
            if grid[r][c] is None:
                grid[r][c] = rnd.choice(ALPHABET)
    return grid


def _find(grid, word):
    R, C = len(grid), len(grid[0])
    for r in range(R):
        for c in range(C):
            for dr, dc in ALL_DIRS:
                er, ec = r + dr * (len(word) - 1), c + dc * (len(word) - 1)
                if 0 <= er < R and 0 <= ec < C and all(
                        grid[r + dr * i][c + dc * i] == ch
                        for i, ch in enumerate(word)):
                    return True
    return False


def _fill_wordsearch():
    slots = []
    for key, blk in IDX.items():
        if not key.startswith('p3b'):
            continue
        for li, ln in enumerate(blk['lines']):
            for si, sp in enumerate(ln['spans']):
                t = sp['text'].strip()
                if len(t) == 1 and t.isalpha() and sp['x'] > 350 and 380 < sp['y'] < 520:
                    slots.append((round(sp['y']), sp['x'], sp['w'], key, li, si))
    slots.sort()
    ys = sorted({y for y, *_ in slots})
    rows_n, cols_n = len(ys), len(slots) // len(ys)
    for seed in range(1, 4000):
        try:
            grid = _make(rows_n, cols_n, WS_WORDS, seed)
        except RuntimeError:
            continue
        if all(_find(grid, w) for w in WS_WORDS):
            break
    else:
        raise RuntimeError('не вдалося скласти філворд 7×6')
    flat = [ch for row in grid for ch in row]
    # центруємо літери в клітинках (див. puzzlekit.centre_in_cells)
    from puzzlekit import centre_in_cells
    font = fitz.Font(fontfile='/System/Library/Fonts/Supplemental/Arial Bold.ttf')
    xs = centre_in_cells(slots, flat, font, 12.0)
    per_key = {}
    for (y, x, w, key, li, si), ch in zip(slots, flat):
        per_key.setdefault(key, {})[(li, si)] = ch
    for key, cells in per_key.items():
        blk = IDX[key]
        TR[key] = {'lines': [[(cells[(li, si)], sp['s'], xs[key][(li, si)])
                              for si, sp in enumerate(ln['spans'])]
                             for li, ln in enumerate(blk['lines'])]}
    return rows_n, cols_n


WS_SHAPE = _fill_wordsearch()

# ── ланцюжок по змійці, стор. 5 ──────────────────────────────────────────
# Три ряди кружечків читаються «змійкою»: перший ліворуч-праворуч, другий
# праворуч-ліворуч, третій знову ліворуч-праворуч. Разом 26 літер, тож
# українська фраза має бути рівно такої довжини.
SNAKE = 'АДАМЖИВУСАДУЯКИЙЗВАВСЯЕДЕМ'          # «Адам жив у саду, який звався Едем»
assert len(SNAKE) == 26, len(SNAKE)
_r1, _r2, _r3 = SNAKE[:10], SNAKE[10:20], SNAKE[20:]
for _key, _txt in (('p4b13', _r1), ('p4b14', _r2[::-1]), ('p4b16', _r3)):
    _blk = IDX[_key]
    # кожна літера — окремий рядок шаблону зі спільною базовою лінією (§2.4)
    TR[_key] = {'lines': [[(ch, ln['spans'][0]['s'])]
                          for ch, ln in zip(_txt, _blk['lines'])]}

# ── літери з крапкою, стор. 7 ────────────────────────────────────────────
# Перемальовуємо цілком: у оригіналі крапка стоїть між двома літерами, і
# геометрія не каже, якій із них вона належить. Коли літери й крапки малюємо
# самі, двозначності немає.
DOT_PHRASE = 'БОГЛЮБИТЬНАСЗАВЖДИ'              # «Бог любить нас завжди» — 18 літер
DOT_ORIGIN = (57.2, 579.7)
DOT_CELL = 26.5
DOT_PITCH = 26.45
DOT_COLS, DOT_ROWS = 17, 2

COVERS = {6: [(54, 576, 511, 636)]}             # клітинки, літери й крапки


def apply_covers(page):
    rects = COVERS.get(page.number)
    if rects:
        cover(page, rects, WIPES)


def _draw_puzzles(doc, only=None):
    if only is not None and 6 not in only:
        return
    page = doc[6]
    page.insert_font(fontname='uaB',
                     fontfile='/System/Library/Fonts/Supplemental/Arial Bold.ttf')
    font = fitz.Font(fontfile='/System/Library/Fonts/Supplemental/Arial Bold.ttf')
    n = DOT_COLS * DOT_ROWS
    rnd = random.Random(11)
    marked = sorted(rnd.sample(range(n), len(DOT_PHRASE)))
    letters, it = [], iter(DOT_PHRASE)
    for i in range(n):
        letters.append(next(it) if i in marked else rnd.choice(ALPHABET))
    ox, oy = DOT_ORIGIN
    for i in range(n):
        r, c = divmod(i, DOT_COLS)
        x = ox + c * DOT_PITCH
        y = oy + r * DOT_PITCH
        page.draw_rect(fitz.Rect(x, y, x + DOT_CELL, y + DOT_CELL),
                       color=RED, width=0.9)
        ch = letters[i]
        w = font.text_length(ch, fontsize=13)
        page.insert_text((x + (DOT_CELL - w) / 2, y + DOT_CELL * 0.70), ch,
                         fontname='uaB', fontsize=13, color=(0, 0, 0))
        if i in marked:
            page.draw_circle(fitz.Point(x + DOT_CELL / 2, y + DOT_CELL * 0.85),
                             1.5, color=None, fill=(0, 0, 0))
    print(f'  стор. 7: клітинки з крапкою — {len(DOT_PHRASE)} із {n}')


POST.append(_draw_puzzles)

# ── праві межі абзаців (§2.4, правило 1) ─────────────────────────────────
for _k, _v in TR.items():
    if 'flow' in _v and 'right' not in _v:
        _r = IDX[_k]['bbox'][2]
        if _r < 480:
            _v['right'] = _r + 2.0
