# -*- coding: utf-8 -*-
"""Український текст для Level 3 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__))
GREEN = (0.0, 0.615, 0.470)          # #009d78 — фірмовий колір рівня 3

with open(os.path.join(HERE, 'work', 'Level3_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=()):
    """«Q:» — окремий рядок шаблону на спільній базовій лінії (§2.4)."""
    TR[key] = {'lines': [[('Q:', qs)]] + [[(t, ts)] for t in text_lines]
               + [[seg] for seg in tail]}


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


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)])
    lines.append([('Рівень 3 — біблійні історії', i + 2)])
    lines.append([('A1', i + 3)])
    if big:                       # обкладинка має ще й www у шапці
        lines.insert(1, [('www.besweb.com', i + 2)])
        lines[2] = [('Рівень 3 — біблійні історії', i + 3)]
        lines[3] = [('A1', i + 4)]
    TR[f'p{page}b{block}'] = {'lines': lines}


# ═══════════════════════════════════════════════════════════ сторінка 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': [[('Рівень 3', 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['p0b44'] = {'lines': [[('УРОК 1:', 0), (' На початку', 1)]]}
TR['p0b45'] = {'lines': [[('Бог створює світ', 0)]]}
TR['p0b47'] = {'right': 566.0, 'lines': [[('КЛЮЧОВИЙ ВІРШ:', 0)], [('Буття 1: 1', 1)]]}
TR['p0b49'] = {'lines': [[('ЧИТАЙ:', 0)], [('Буття 1: 1-23', 1)]]}
TR['p0b51'] = {'flow': (
    'Чи дивився ти коли-небудь у небо холодної ясної ночі й думав, як постали '
    'мільйони зірок і планет? Чи звідки взялися люди й тварини?')}
TR['p0b52'] = {'flow': (
    'Християни вірять, що відповіді можна знайти в першій книзі Біблії — Буття.')}
TR['p0b53'] = {'lines': [[('«Буття» означає «початки».', 0)]], 'center': True}
Q('p0b54', 'Який вірш біблійного читання говорить про початок?')
TR['p0b55'] = {'lines': [
    [('Вірш:', 0)],
    [('Q:', 1)],
    [('Якою була земля на початку?', 0)],
]}
Q('p0b56', 'Що сказав Бог першого дня творіння? Познач правильну відповідь.')
TR['p0b57'] = {'lines': [[('«Нехай буде...', 0)]]}
TR['p0b59'] = {'lines': [[('вода', 0)]]}
TR['p0b61'] = {'lines': [[('сонце', 0)]]}
TR['p0b63'] = {'lines': [[('життя', 0)]]}
TR['p0b65'] = {'lines': [[('світло', 0)], [('».', 0)]]}
Q('p0b66', 'Випиши Буття 1: 4 поруч із малюнком.')

# ═══════════════════════════════════════════════════════════ сторінка 2
TR['p1b1'] = {'lines': [[('УСЬОГО:', 0)]]}
head(1, 2)
TR['p1b3'] = {'lines': [[('ФІЛВОРД', 0)]]}
TR['p1b4'] = {'flow': (
    'У філворді заховано вісім слів із біблійного читання. Вони сховані вгору, '
    'вниз, впоперек і задом наперед. Перша літера кожного слова вже дана, щоб '
    'тобі було легше.')}
Q('p1b5',
  'Впиши пропущені літери й обведи слова у ФІЛВОРДІ.',
  'Перші два зроблено за тебе.', ts=1)
Q('p1b8',
  'Намалюй, що сталося кожного з перших пʼяти днів',
  'творіння.')
TR['p1b9'] = {'lines': [[(f'День {i}', 0)] for i in range(1, 6)]}
TR['p1b10'] = {'flow': (
    'Біблія каже, що Бог створив землю, всесвіт і людей. Він зробив це за '
    'шість днів, а сьомого дня відпочив. І хоча у світі мільйони людей, Бог '
    'знає кожного з нас. Ти, мабуть, почуваєшся дуже маленьким поруч із '
    'величчю Божого творіння — але кожен із нас Йому дуже дорогий.')}
Q('p1b11', 'Випиши кінець 10-го вірша — що Бог сказав про Свою роботу.')
rows('p1b89',
     'Наступного разу, коли', 'дивитимешся на зорі,', 'згадай, чого ми навчилися.',
     'Бог створив тебе,', 'і Він тебе любить!')

# ═══════════════════════════════════════════════════════════ сторінка 3
head(2, 1, with_name=True)
TR['p2b2'] = {'lines': [[('УРОК 2:', 0), (' На початку', 1)]]}
TR['p2b3'] = {'lines': [[('Бог створює чоловіка й жінку', 0)]]}
TR['p2b5'] = {'right': 536.0, 'lines': [[('КЛЮЧОВИЙ ВІРШ:', 0)], [('Буття 1: 27', 1)]]}
TR['p2b7'] = {'lines': [[('ЧИТАЙ:', 0)], [('Буття 1: 20-31', 1)]]}
TR['p2b10'] = {'flow': (
    'Чи думав ти колись, яка сила потрібна, щоб стався виверження вулкана, або '
    'яким потужним має бути вітер, щоб перетворитися на ураган, що змітає все '
    'на своєму шляху? Таке важко навіть уявити. А християни вірять, що саме '
    'всемогутня сила Бога створила все суще. Про Божу велич у творінні ми '
    'читаємо в Бутті. Ми бачимо Його у всій славі, коли Він створює тварин, а '
    'потім — найдивовижніше — перших людей.')}
Q('p2b11',
  'Яких двох істот Бог створив першими?',
  'Впиши їхні назви в рамку.')
TR['p2b12'] = {'lines': [[('і', 0)]]}
Q('p2b13', 'Коли Бог створив цих істот, Він поблагословив їх. Що Він сказав?')
Q('p2b14', 'Стисло перекажи вірші 24 і 25, щоб доповнити речення.')
TR['p2b15'] = {'lines': [[('Бог сказав:', 0)]]}
TR['p2b16'] = {'lines': [[('Бог створив:', 0)]]}
TR['p2b17'] = {'lines': [[('Бог побачив:', 0)]]}
Q('p2b18',
  'Коли дизайнери створюють щось нове, вони роблять зразок.',
  'Що Бог узяв за зразок, коли творив чоловіка й жінку? (Вірш 27)')
Q('p2b19', 'Що сказав Бог, коли поблагословив їх? (Дивись вірш 28.)')
Q('p2b20', 'Кого Бог поставив володарем над Своїм творінням?')

# ═══════════════════════════════════════════════════════════ сторінка 4
TR['p3b1'] = {'lines': [[('УСЬОГО:', 0)]]}
head(3, 2)
Q('p3b6',
  'Яку їжу Бог дав людям і тваринам?',
  '(Прочитай вірші 29 і 30.)')
TR['p3b7'] = {'lines': [[('Людям —', 0)]]}
TR['p3b8'] = {'lines': [[('Тваринам —', 0)]]}
Q('p3b9', 'Обведи, скільки часу Бог творив світ.')
rows('p3b10', 'місяць', '10 днів', '6 днів')
rows('p3b11', 'тиждень', 'два тижні', 'рік')
TR['p3b12'] = {'flow': (
    'Найбільшим Божим творінням стала людина. Бог вдихнув в Адама подих '
    'життя — і людина стала живою душею!')}
rows('p3b13', 'Біблія каже, що Бог створив тебе не таким, як тварин.',
     'Ти особливий для Нього.')
TR['p3b14'] = {'flow': (
    'Бог насадив прекрасний сад, у якому міг жити Адам, і навіть створив йому '
    'супутницю — першу жінку. Сам Бог приходив ходити й розмовляти з ними в '
    'саду. Як же це, мабуть, було чудово!')}
# Один блок — ДВІ хмаринки (запитання і відповідь). Режим `fit` зливає їх в
# одну колонку тексту й вивалює за межі фігур, тож тут потрібна сітка рядків.
rows('p3b37',
     'Чи ходить Бог', 'і розмовляє з', 'людьми сьогодні?',
     'Так, Q.', 'Біблія каже, що Бог', 'ніколи не покидає нас',
     '(Повторення Закону 31: 8).', 'З Ним можна говорити', 'будь-коли і будь-де!')
Q('p3b38',
  "Як звали дружину Адама? (Прочитай Буття 3: 20.)",
  'Чи робив ти колись щось справді особливе? Як ти почувався, коли',
  'тобі казали, яка гарна твоя робота? Бог теж радіє, коли ми',
  'захоплюємося Його творінням.')
TR['p3b51'] = {'flow': (
    'Біблія каже, що Бог — Творець усесвіту. Докази Його сили й слави ти '
    'бачиш у природі навколо. Немає сили, більшої за Нього, бо Він — Творець '
    'усього!')}

# ═══════════════════════════════════════════════════════════ сторінка 5
head(4, 1, with_name=True)
TR['p4b2'] = {'lines': [[('УРОК 3:', 0), (' На початку', 1)]]}
TR['p4b3'] = {'lines': [[('Усе йде не так!', 0)]]}
TR['p4b5'] = {'right': 536.0, 'lines': [[('КЛЮЧОВИЙ ВІРШ:', 0)], [('Буття 3: 3', 1)]]}
TR['p4b7'] = {'lines': [
    [('ЧИТАЙ:', 0)], [('Буття 2: 15-17', 1)], [('і 3: 1-24', 1)]]}
TR['p4b9'] = {'flow': (
    'Чи мав ти колись річ у бездоганному стані, яку потім зіпсували? Ти, '
    'мабуть, засмутився. У цьому уроці ми дізнаємося, як гріх зіпсував '
    'Божий світ.')}
rows('p4b10', 'Біблія каже: гріх — це непослух Божим заповідям.')
Q('p4b11',
  'З біблійного читання ми знаємо: Бог дав Адамові та Єві все, чого вони',
  'могли б забажати, але лишив одну важливу заповідь. Яку саме?')
Q('p4b12', 'Обведи правильні відповіді.')
TR['p4b13'] = {'lines': [[('Якщо Адам і Єва зʼїдять із того дерева, Бог сказав, що вони...', 0)]]}
TR['p4b14'] = {'lines': [[('захворіють      муситимуть піти      помруть      будуть покарані', 0)]]}
TR['p4b15'] = {'lines': [[('Якщо Адам і Єва зʼїдять із того дерева, змій сказав, що вони...', 0)]]}
TR['p4b16'] = {'lines': [[('не помруть      засумують      будуть покарані      захворіють', 0)]]}
Q('p4b17',
  'Змій був Божим ворогом, сатаною, у чужій подобі.',
  'Що сказано про змія? (Дивись Буття 3: 1.)')
TR['p4b18'] = {'flow': (
    'Слухаючи змія, Єва побачила в тому плоді дещо добре — і це переконало її '
    'скуштувати. (Дивись Буття 3: 6.)')}
Q('p4b19', 'Що саме?')
for _i, _k in enumerate(('p4b20', 'p4b21', 'p4b22'), start=1):
    TR[_k] = {'lines': [[(f'{_i}.', 0)]]}

# ═══════════════════════════════════════════════════════════ сторінка 6
TR['p5b1'] = {'lines': [[('УСЬОГО:', 0)]]}
head(5, 2)
Q('p5b8', 'Напиши ПРАВДА або НЕПРАВДА до кожного твердження:')
TR['p5b9'] = {'lines': [[('Адам першим відкусив від плоду.', 0)]]}
TR['p5b10'] = {'lines': [[('Адам і Єва зрозуміли, що вони нагі.', 0)]]}
TR['p5b11'] = {'lines': [[('Вони зшили фігове листя, щоб зробити собі одяг.', 0)]]}
TR['p5b12'] = {'lines': [[('Обоє були дуже щасливі.', 0)]]}
Q('p5b13',
  'Склади слова з переставлених літер і дізнайся, що зробили Адам',
  'і Єва, коли почули, що Бог ходить по саду.')
TR['p5b14'] = {'lines': [[('и о В н  /  я с о х в а л и с', 0)]]}
TR['p5b15'] = {'lines': [[('_ _ _ _  /  _ _ _ _ _ _ _ _ _', 0)]]}
Q('p5b16', 'Бог покликав їх, бо вони ховалися. Що Адам сказав Богові?')
Q('p5b17', 'Чим Адам виправдовував свій непослух?')
Q('p5b18', 'Кого звинуватила Єва?')
TR['p5b19'] = {'flow': (
    'Коли ти робиш щось погане, чи буває, що виправдовуєшся або звинувачуєш '
    'когось іншого? Саме так зробили Адам і Єва.')}
TR['p5b20'] = {'flow': (
    'Бог сказав Адамові, що через його гріх земля зіпсована й родитиме терни '
    'та будяки. Їм довелося покинути прекрасний сад, і тепер Адам мусив тяжко '
    'працювати, щоб добути їжу.')}
TR['p5b21'] = {'flow': (
    'Через свій гріх вони більше не могли тішитися Божою присутністю, а '
    'колись мали й померти.')}
TR['p5b22'] = {'flow': (
    'Біблія каже, що всі ми грішимо, але не варто виправдовуватися чи брехати, '
    'щоб це приховати. Від Бога гріха не сховаєш — Він знає все. Він хоче, щоб '
    'ми шкодували й відверталися від гріха, бо Він може простити!')}
TR['p5b23'] = {'fit': True, 'flow': (
    'Я знаю, що грішу, і мені шкода. Але як мені все виправити перед Богом?')}
TR['p5b24'] = {'fit': True, 'flow': (
    'Біблія каже, Q, що Бог послав Свого Сина, Ісуса, у світ, щоб розібратися '
    'з нашим гріхом.')}
TR['p5b25'] = {'fit': True, 'flow': (
    'Так, Q, а чому б нам не прочитати разом Івана 3: 16? Там повно доброї звістки!')}

# ═══════════════════════════════════════════════════════════ сторінка 7
head(6, 1, with_name=True)
TR['p6b2'] = {'lines': [[('УРОК 4:', 0), (' На початку', 1)]]}
TR['p6b3'] = {'lines': [[('Робити так, як хоче Бог', 0)]]}
TR['p6b5'] = {'right': 536.0, 'lines': [[('КЛЮЧОВИЙ ВІРШ:', 0)], [('Івана 14: 6', 1)]]}
TR['p6b7'] = {'lines': [[('ЧИТАЙ:', 0)], [('Буття 4: 1-16', 1)]]}
TR['p6b12'] = {'flow': (
    'Чи зупиняв тебе колись хтось, щоб спитати дорогу? Може, ти згадаєш місце '
    'поблизу, до якого є два чи більше шляхів. У цьому уроці ми дізнаємося '
    'дуже важливу істину: до Бога веде лише один шлях, і це Його шлях.')}
rows('p6b13', 'До Бога веде один шлях. Це Його шлях.')
Q('p6b14', 'Ким за фахом були Авель і Каїн?')
Q('p6b15', 'ДРАБИНКА СЛІВ')
TR['p6b16'] = {'lines': [[('ПО ГОРИЗОНТАЛІ', 0)]]}
TR['p6b17'] = {'lines': [[('1) _ _ _ _ _ _ _ сказав: «Чому ти гніваєшся?»', 0)]]}
TR['p6b18'] = {'lines': [[('3) Молодший брат.', 0)]]}
TR['p6b19'] = {'lines': [[('4) Старший брат.', 0)]]}
TR['p6b20'] = {'lines': [[('5) Старший брат став дуже _ _ _ _ _ _ _.', 0)]]}
TR['p6b21'] = {'lines': [[('ПО ВЕРТИКАЛІ', 0)]]}
TR['p6b22'] = {'lines': [[('2) Те, що брати принесли Богові.', 0)]]}
Q('p6b23', 'Яка жертва була до вподоби Богові?')
Q('p6b24',
  'Каїн дуже розгнівався на Бога, бо Той не прийняв його приношення.',
  'Допиши кожну літеру й дізнайся, що він зробив далі.')
TR['p6b25'] = {'drop': True}      # перемальовується в POST разом із латками

# ═══════════════════════════════════════════════════════════ сторінка 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)],
    [('РІВЕНЬ 3 A1', 3)],
    [('01/2025', 4)],
]}
Q('p7b12', 'Якою була кара для Каїна? (Дивись вірш 12.)')
TR['p7b13'] = {'lines': [[('Каїн сказав, що не зможе знести своєї кари.', 0)]]}
Q('p7b14', 'Подивись вірші 13 і 14 та випиши його причини.', tail=[('1.', 1)])
for _i, _k in enumerate(('p7b15', 'p7b16', 'p7b17'), start=2):
    TR[_k] = {'lines': [[(f'{_i}.', 0)]]}
TR['p7b18'] = {'flow': (
    'Авель розумів, що він грішник і що для Божого прощення потрібно принести '
    'кров ягняти. Каїн же не визнав себе грішником і не прийшов до Бога Його '
    'шляхом.')}
Q('p7b19', 'Обведи імʼя того брата, який пішов своїм шляхом.')
TR['p7b20'] = {'lines': [[('Каїн        Авель', 0)]]}
Q('p7b21', 'Випиши слова ключового вірша.')
TR['p7b22'] = {'flow': (
    'Біблія каже, що Ісус — Агнець Божий, який віддав Себе на хресті, щоб '
    'забрати гріх світу. Сьогодні багато людей намагаються догодити Богові '
    'по-своєму, але Біблія каже, що є лише {s1}ОДИН ШЛЯХ{s0}, щоб Йому '
    'догодити. Ми маємо просити Бога простити нам усе зле, що ми зробили, і '
    'покластися на Ісуса.')}
rows('p7b25',
     'То добрих справ,', 'церкви й пожертв', 'не досить,',
     'щоб догодити', 'Богові?')
rows('p7b26',
     'Це важливі речі,', 'Q, але вони не', 'знімають нашого гріха.',
     'Нам потрібен Ісус!')
rows('p7b27',
     'Саме так!', 'Як нагадує Івана', '14: 6, Ісус —', 'єдиний шлях!')


# ═══════════════════════════════════════════════ головоломки
import fitz                                                   # noqa: E402
from puzzlekit import (cover, draw_grid, extent, number,       # noqa: E402
                       place_crossword)

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

# ── філворд, стор. 2 ─────────────────────────────────────────────────────
# Вісім слів із Буття 1: 1-23 за Турконяком. Літери сітки — окремі блоки по
# одній літері, тож позиції беремо з шаблону, а не вгадуємо.
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):
    """Позиція слова в сітці або None. Повертає (ряд, стовпець, dr, dc)."""
    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 (r, c, dr, dc)
    return None


def _fill_wordsearch():
    """Літери сітки: збираємо всі односимвольні спани в зоні філворда,
    сортуємо в порядку читання й розкладаємо згенеровану сітку."""
    slots = []
    for key, blk in IDX.items():
        if not key.startswith('p1b'):
            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 95 < sp['x'] < 270 \
                        and 220 < sp['y'] < 400:
                    slots.append((round(sp['y']), sp['x'], sp['w'], key, li, si))
    slots.sort()
    global WS_X, WS_Y
    WS_Y = sorted({y for y, x, *_ in slots})
    # x кожної літери трохи різний (гліфи різної ширини й центровані), тож
    # стовпці треба кластеризувати, а не брати множину значень
    WS_X = []
    for x in sorted(x + w / 2 for _, x, w, *_ in slots):
        if not WS_X or x - WS_X[-1] > 8:
            WS_X.append(x)
    n = len(slots)
    side = int(round(n ** 0.5))
    assert side * side == n, f'сітка не квадратна: {n} літер'
    # Перші два слова показані як приклад і обводяться овалом, тож вони мають
    # лежати ГОРИЗОНТАЛЬНО зліва направо — інакше овал довелося б повертати.
    global WS_POS
    for seed in range(1, 6000):
        try:
            grid = _make(side, side, WS_WORDS, seed)
        except RuntimeError:
            continue
        pos = {w: _find(grid, w) for w in WS_WORDS}
        if any(v is None for v in pos.values()):
            continue
        if all(pos[w][2:] == (0, 1) for w in ('ВЕЧІР', 'СВІТЛО')):
            WS_POS = pos
            break
    else:
        raise RuntimeError('не вдалося скласти філворд')
    flat = [ch for row in grid for ch in row]
    from puzzlekit import centre_in_cells
    import fitz as _f
    font = _f.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 side


WS_X, WS_Y, WS_POS = [], [], {}
_SIDE = _fill_wordsearch()

# Перелік відповідей: перші два слова показані повністю, решта — перша літера.
TR['p1b6'] = {'lines': [[('1', 0), ('В', 1), ('\tЕ Ч І Р', 0)]]}
TR['p1b7'] = {'lines': [
    [('2', 0), ('С', 1), ('\tВ І Т Л О', 0)],
    [('3', 0), ('С', 1)],
    [('4', 0), ('Т', 1)],
    [('5', 0), ('Р', 1)],
    [('6', 0), ('С', 1)],
    [('7', 0), ('З', 1)],
    [('8', 0), ('Н', 1)],
]}

# ── драбинка слів, стор. 7 ───────────────────────────────────────────────
# ПРИНОШЕННЯ по вертикалі перетинає всі чотири горизонтальні слова, тож
# драбинка зберігається, а нумерація 1, 3, 4, 5 / 2 збігається з оригіналом.
# ДАРУНОК (коротше) не годиться: усі його літери різні, і два з чотирьох
# слів неминуче стають на сусідні рядки, чого кросворд не допускає.
LADDER = [('ГОСПОДЬ', 'a'), ('АВЕЛЬ', 'a'), ('КАЇН', 'a'),
          ('ГНІВНИЙ', 'a'), ('ПРИНОШЕННЯ', 'd')]
LADDER_AT = (282.0, 436.0)
LADDER_CELL = 17.8          # ширину обмежує фігура праворуч (x = 431)

# ── «допиши літери», стор. 7 ─────────────────────────────────────────────
# В оригіналі це звичайний текст, поверх якого лежать 15 білих квадратиків
# 17×18 pt — вони й ховають половинки літер. Отже перемальовується не гліф,
# а латки: набираємо український напис і кладемо латки по тому самому правилу.
HALF_TEXT = 'ВІН УБИВ СВОГО БРАТА'
HALF_AT = (108.1, 768.8)
HALF_SIZE = 28.0
HALF_PATCH = (17.2, 18.1)

COVERS = {
    1: [(96, 221, 246, 242), (139, 308, 244, 329)],   # старі овали-приклади
    3: [(80, 704, 540, 738)],                 # клітинки акростиха з фото
    6: [(280, 425, 428, 655),                 # драбинка слів + номери
        (95, 730, 470, 786)],                 # напис із половинок літер
}


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


def _oval(page, r, c0, c1):
    """Овал навколо горизонтального слова в сітці філворда."""
    x0 = WS_X[c0] - 9.5
    x1 = WS_X[c1] + 9.5
    yy = WS_Y[r]
    page.draw_oval(fitz.Rect(x0, yy - 13.0, x1, yy + 4.3),
                   color=(0, 0, 0), width=0.9)


def _draw_puzzles(doc, only=None):
    def want(pno):
        return only is None or pno in only

    if want(1):
        # Два перші слова показані як приклад — обводимо їх у НОВІЙ сітці.
        for w in ('ВЕЧІР', 'СВІТЛО'):
            r, c, dr, dc = WS_POS[w]
            _oval(doc[1], r, c, c + len(w) - 1)

    if want(6):
        placed, missed = place_crossword(LADDER, seed=0)
        nums, _, _ = number(placed)
        draw_grid(doc[6], placed, nums, LADDER_AT, LADDER_CELL, GREEN)
        r, c = extent(placed)[2:]
        print(f'  стор. 7: драбинка {r}×{c}'
              + (f', НЕ ЗВʼЯЗАНО: {missed}' if missed else ''))

        # напис із половинок літер
        page = doc[6]
        font = fitz.Font(fontfile='/System/Library/Fonts/Supplemental/Arial Bold.ttf')
        page.insert_font(fontname='uaB',
                         fontfile='/System/Library/Fonts/Supplemental/Arial Bold.ttf')
        x, y = HALF_AT
        page.insert_text((x, y), HALF_TEXT, fontname='uaB',
                         fontsize=HALF_SIZE, color=GREEN)
        # Латка ховає ЛІВУ або ПРАВУ половину гліфа. Перша спроба ховала
        # горизонтальну смугу під базовою лінією — тобто порожнє місце, і
        # напис лишався цілим; а «верхня» латка накривала літеру повністю.
        rnd = random.Random(3)
        top, bot = y - HALF_SIZE * 0.74, y + 1.5
        pen = x
        for ch in HALF_TEXT:
            w = font.text_length(ch, fontsize=HALF_SIZE)
            if ch != ' ' and rnd.random() < 0.75:
                if rnd.random() < 0.5:
                    r = fitz.Rect(pen - 0.6, top, pen + w * 0.48, bot)
                else:
                    r = fitz.Rect(pen + w * 0.52, top, pen + w + 0.6, bot)
                page.draw_rect(r, color=None, fill=(1, 1, 1))
            pen += w

    if want(3):
        # Акростих із фото не переноситься: назви предметів задані картинками
        # (груша, кролик, яблуко, морозиво, сонце, яйце → PRAISE HIM).
        # Замість зіпсованого завдання — письмова відповідь на ті самі 2 бали.
        page = doc[3]
        page.draw_line(fitz.Point(84, 728), fitz.Point(500, 728),
                       color=GREEN, width=0.8)


POST.append(_draw_puzzles)

# Заголовки драбинки з новою нумерацією збігаються з оригінальними
# (1, 3, 4, 5 по горизонталі; 2 по вертикалі), тож підказки лишаються на місці.

# Акростих: інструкція замінена (див. _draw_puzzles).
Q('p3b39', 'Розглянь малюнки. Напиши, за що ти дякуєш Богові за Його творіння.')


# ── праві межі абзаців ───────────────────────────────────────────────────
# §2.4, правило 1: типова межа 508 pt — це край основної колонки. Але якщо
# праворуч від абзацу стоїть ілюстрація або хмаринка з реплікою, український
# текст (він довший) заповзе під неї. Ознака — вузький джерельний bbox: у
# першій збірці так наїхали шість абзаців, зокрема p7b22 під хмаринки і
# p1b10 під лампочку. Беремо межу з оригіналу.
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
