Spaces:
Sleeping
Sleeping
| """The mentor's voice for each spread of the grimoire. | |
| MENTOR_TEXT[lang_code][step_index] -> markdown-like string. | |
| Spreads 1 and onward may contain ``{hero_name}`` and ``{village_name}`` | |
| placeholders that get filled in at render time. | |
| """ | |
| from __future__ import annotations | |
| # Inline markup the renderer supports: | |
| # **bold** (rendered as <strong>) | |
| # *italic* (rendered as <em>) | |
| # blockquote lines starting with ``> `` | |
| # --- on its own line for a thematic break | |
| # blank line = paragraph break | |
| MENTOR_TEXT: dict[str, list[str]] = { | |
| "en": [ | |
| # Step 0 — Language | |
| """The candle is lit. The grimoire is open. The pen is sharpened | |
| to a fine bead of black ink. | |
| But first, before I write a word of any of this — in what tongue | |
| shall I write? My apprentice will need to read every parchment | |
| through tears and through dragon-light. Whatever I choose now, I | |
| will keep to. The mentor who switches languages mid-spell loses | |
| the spell. | |
| *Choose for me. I shall write in —*""", | |
| # Step 1 — Hero name | |
| """Seven generations the Iron Peak has cast its shadow. Seven | |
| generations of burned villages and silenced kings. I have | |
| watched, I have waited, and tonight I write. | |
| The Pact binds me — no mentor with magic in his blood may strike | |
| at **Vraskar** directly. The dragon's curse can see the spell in | |
| the hand that casts it, and burn it back tenfold. So the spell | |
| must walk in on someone smaller. Someone the curse will not look | |
| for. | |
| He must be young. He must be unknown. He must have a name a | |
| dragon would forget the moment after hearing it. | |
| *I will name him —*""", | |
| # Step 2 — Village name | |
| """**{hero_name}**, then. Common enough; the dragon will look past | |
| it. I write it twice, as a charm, and the ink dries quickly. | |
| Now — his place of beginning. Somewhere far enough from the Iron | |
| Peak that no smoke has yet darkened his rooftiles. Somewhere with | |
| a well and a bell and a road that does not look like it leads | |
| anywhere. Somewhere a boy can leave without breaking too many | |
| hearts. | |
| *His village will be —*""", | |
| # Step 3 — Oracle I | |
| """**{hero_name} of {village_name}**. Written, sealed in my | |
| memory. Good. | |
| He kneels by the well at dawn. I am no longer present in his | |
| sight; I am no longer present in any room he has ever entered. | |
| But the parchments will speak to him, and he will understand | |
| them. | |
| Now — the words themselves. | |
| I do not know what {hero_name} will face. Only the shape of the | |
| dangers. A creature with appetite. A barrier of water. A | |
| guardian who asks a question. A force without a face. The dragon | |
| himself. | |
| I cannot guess which trial will draw which parchment. So I write | |
| what I have, and I trust the road to choose. | |
| *The first parchment, then. What word does he most need?*""", | |
| # Step 4 — Oracle II | |
| """The first is sealed. I press the wax. I do not feel any wiser | |
| for having written it. Perhaps that is the point — perhaps | |
| wisdom dressed as wisdom is no use at all. | |
| The candle bends in a draft I cannot find the source of. | |
| *The second parchment. Something different. Something I would | |
| never think to say twice.*""", | |
| # Step 5 — Oracle III | |
| """Two parchments sealed. Three remain. The candle is low and the | |
| night is loud beyond the shutters. | |
| The third. **{hero_name}** will read this one only in an hour where | |
| he is no longer the boy I have chosen — only after the road has | |
| asked something of him that I cannot ask for him. | |
| *What shall I leave him for that hour?*""", | |
| # Step 6 — Oracle IV | |
| """The fourth. Three sealed and two to go. He will not know which | |
| parchment he opens first, nor which he saves for last. He will | |
| only know the order in which his life narrows. | |
| Some of these I write because I know they save him. This one I | |
| write because I do not know what else to give. | |
| *The fourth parchment.*""", | |
| # Step 7 — Oracle V (the dragon) | |
| """The fifth and last. The ink hesitates. | |
| This one he will open at the threshold of **Vraskar** — I know | |
| this because there will be no others left in his pack. | |
| If anything I write may save him from a dragon, let it be this. | |
| *The fifth and final parchment.*""", | |
| # Step 8 — Closing | |
| """All five are sealed. The candle is out. | |
| I leave the parchments in his pack as he sleeps. He will not see | |
| my face. He will not know my name. He will know only that they | |
| are there, and that I have whispered, before sleep: | |
| > *"Open one only when your life is in peril. Trust the words I | |
| > have written, however strange they seem."* | |
| I have done what I can. The road takes him from here.""", | |
| ], | |
| "zh": [ | |
| # Step 0 | |
| """烛火已燃。卷书已开。笔尖磨得极细,墨珠饱满如黑曜石。 | |
| 但在我落笔之前——我当以何种文字书写?我的使者必须在泪水 | |
| 中、在龙焰之下,依然能读懂这五张羊皮卷上的每一个字。我此 | |
| 刻所选的语言,便要从一而终。中途换语的导师,咒语必失。 | |
| *请代我抉择。我将以——*""", | |
| # Step 1 | |
| """艾恩峰的阴影已笼罩这片大地七代之久。七代之久的焚毁村 | |
| 庄,七代之久的沉默王者。我一直凝视,一直等待,而今夜 | |
| ——我落笔。 | |
| 古老的契约束缚着我——血脉中怀有法力之人,不可亲手对 | |
| **瓦斯卡尔**出手。巨龙的诅咒能看见施法之手,并十倍奉 | |
| 还。所以法术必须借由更小的人走进去。诅咒所看不见的人。 | |
| 他必须年轻。他必须无名。他必须有一个名字——一条龙听过 | |
| 后转瞬便会忘记的名字。 | |
| *我将为他起名——*""", | |
| # Step 2 | |
| """**{hero_name}**。这个名字够寻常,龙的目光会越过它。我提笔 | |
| 重写了一遍,作为护持,墨迹很快便干。 | |
| 现在——他的起点。一个离艾恩峰足够远的地方,远到他屋顶 | |
| 上的瓦片还没被烟熏黑。一个有水井、有钟声的小地方。一 | |
| 个弟子离开时,不至让太多人心碎的地方。 | |
| *他的村庄将是——*""", | |
| # Step 3 | |
| """**{village_name}的{hero_name}**。已写,已封于我的记忆中。 | |
| 好。 | |
| 他在黎明时分跪于井边。我已不在他的视野中;我已不在他踏 | |
| 足过的任何房间里。但羊皮卷会向他说话,而他能听懂。 | |
| 现在——便是文字本身了。 | |
| 我不知道{hero_name}将面对什么。只知道凶险的形状:一只 | |
| 怀着饥饿的生灵,一道水的阻隔,一个发问的守门者,一种 | |
| 没有面容的力量,还有那条龙本身。 | |
| 我无从猜测哪一重劫难会牵出哪一卷羊皮。所以我写下我所 | |
| 能写的,余下的,便交给道路来抉择。 | |
| *第一卷羊皮,那么。他此刻最需要的词,是什么?*""", | |
| # Step 4 | |
| """第一卷已封缄。我按下蜡印。落笔之后,我并未感到自己变 | |
| 得更睿智。或许这正是真意——也许装作智慧的智慧,本就 | |
| 毫无用处。 | |
| 烛火朝一个我寻不见源头的方向歪去。 | |
| *第二卷羊皮。换些别的。换些我永远不会重说一遍的话。*""", | |
| # Step 5 | |
| """两卷已封。三卷在前。烛火将尽,窗外的夜声喧响。 | |
| 第三卷。**{hero_name}**只会在他不再是我所选的那个弟子 | |
| 的那一刻才会开启它——只会在道路向他索取我无法替他承 | |
| 受之事时。 | |
| *那一刻,我该留给他什么?*""", | |
| # Step 6 | |
| """第四卷。三卷已封,两卷在前。他不会知道他最先开启的是 | |
| 哪一卷,也不会知道最后留下的是哪一卷。他只会知道,他 | |
| 的生命在以何种顺序,渐渐收窄。 | |
| 有些卷我写下,是因为我知道它们能救他。这一卷,我写 | |
| 下,只因我不知道还能给他什么。 | |
| *第四卷。*""", | |
| # Step 7 | |
| """第五卷,也是最后一卷。笔尖犹疑。 | |
| 这一卷他将在**瓦斯卡尔**的门槛上开启——我知道,因为 | |
| 那时他行囊中再无其他可供他选择。 | |
| 若我所书的任何一字仍可救他于龙焰之下,便让此言成为那 | |
| 一字。 | |
| *第五卷,也是最后一卷。*""", | |
| # Step 8 | |
| """五卷皆已封缄。烛火已灭。 | |
| 我将羊皮卷放入他熟睡的行囊中。他不会见我的容貌。他不 | |
| 会知我的名姓。他只会知道:它们在那里,而我,在他入眠 | |
| 之前,于他梦中低语过: | |
| > *"惟有性命攸关之时方可启封。无论我所书之言多么奇 | |
| > 异,亦当信之。"* | |
| 我已尽我所能。前路,从此处起,便是他的了。""", | |
| ], | |
| } | |
| def text_for(lang_code: str, step: int) -> str: | |
| """Return the mentor's text for ``(lang_code, step)``. | |
| Falls back to English if ``lang_code`` isn't supported, and clamps | |
| ``step`` to the valid range. | |
| """ | |
| block = MENTOR_TEXT.get(lang_code) or MENTOR_TEXT["en"] | |
| if step < 0: | |
| step = 0 | |
| if step >= len(block): | |
| step = len(block) - 1 | |
| return block[step] | |
| # --------------------------------------------------------------------------- | |
| # Per-spread pixel-art sigils (one per inscribe step). | |
| # Each grid is a list of strings; ``#`` is an amber pixel, anything else is | |
| # transparent. Designed at 8x8 (some at 9x8 for visual balance). | |
| # --------------------------------------------------------------------------- | |
| GRIMOIRE_SIGILS: list[list[str]] = [ | |
| # Step 0 — QUILL (the mentor chooses his tongue / picks up the pen) | |
| [ | |
| ".......#", | |
| "......##", | |
| ".....##.", | |
| "....##..", | |
| "...##.#.", | |
| "..###...", | |
| "..####..", | |
| "##......", | |
| ], | |
| # Step 1 — STAR (naming the hero — like naming a constellation) | |
| [ | |
| "...##...", | |
| "...##...", | |
| "########", | |
| ".######.", | |
| ".##..##.", | |
| "##....##", | |
| "........", | |
| "........", | |
| ], | |
| # Step 2 — HOUSE (the village he leaves behind) | |
| [ | |
| "...##...", | |
| "..####..", | |
| ".######.", | |
| "########", | |
| ".##.#.##", | |
| ".##.#.##", | |
| ".##.#.##", | |
| "........", | |
| ], | |
| # Step 3 — SUN (Oracle I — first light, opening) | |
| [ | |
| ".#....#.", | |
| "..#..#..", | |
| "...##...", | |
| ".######.", | |
| ".######.", | |
| "...##...", | |
| "..#..#..", | |
| ".#....#.", | |
| ], | |
| # Step 4 — MOON (Oracle II — the second mark, balance) | |
| [ | |
| "..####..", | |
| ".######.", | |
| "###.....", | |
| "###.....", | |
| "###.....", | |
| "###.....", | |
| ".######.", | |
| "..####..", | |
| ], | |
| # Step 5 — HOURGLASS (Oracle III — time, midpoint) | |
| [ | |
| "########", | |
| "########", | |
| ".##..##.", | |
| "..####..", | |
| "..####..", | |
| ".##..##.", | |
| "########", | |
| "########", | |
| ], | |
| # Step 6 — EYE (Oracle IV — vision) | |
| [ | |
| ".######.", | |
| "########", | |
| "##.##.##", | |
| "##.##.##", | |
| "##.##.##", | |
| "########", | |
| ".######.", | |
| "........", | |
| ], | |
| # Step 7 — FLAME (Oracle V — the dragon's parchment) | |
| [ | |
| "...#....", | |
| "..##....", | |
| ".###.#..", | |
| "####.#..", | |
| "#####...", | |
| "######..", | |
| ".####...", | |
| "..##....", | |
| ], | |
| # Step 8 — KEY (closing — the sealed parchments hand off to the road) | |
| [ | |
| ".####...", | |
| "######..", | |
| ".####...", | |
| "..##....", | |
| "..##....", | |
| "..####..", | |
| "..#..#..", | |
| "..####..", | |
| ], | |
| ] | |
| def sigil_svg(step: int, color: str = "#f0c060", render_px: int = 36) -> str: | |
| """Render the pixel-art sigil for ``step`` as inline SVG. | |
| Uses ``shape-rendering="crispEdges"`` so the pixels stay crisp at any | |
| rendered size. Returns the bare ``<svg>`` element (no wrapper).""" | |
| if step < 0: | |
| step = 0 | |
| if step >= len(GRIMOIRE_SIGILS): | |
| step = len(GRIMOIRE_SIGILS) - 1 | |
| grid = GRIMOIRE_SIGILS[step] | |
| h = len(grid) | |
| w = max(len(row) for row in grid) if grid else 0 | |
| rects: list[str] = [] | |
| for y, row in enumerate(grid): | |
| for x, c in enumerate(row): | |
| if c == "#": | |
| rects.append( | |
| f'<rect x="{x}" y="{y}" width="1" height="1" fill="{color}"/>' | |
| ) | |
| return ( | |
| f'<svg viewBox="0 0 {w} {h}" ' | |
| f'width="{render_px}" height="{render_px}" ' | |
| f'shape-rendering="crispEdges" ' | |
| f'style="display:block;image-rendering:pixelated">' | |
| + "".join(rects) | |
| + "</svg>" | |
| ) | |