refactor(editor): wrap multi-board templates onto rows

Every multi-board template laid its boards out in one long horizontal
strip, so opening a seven-board carousel dropped the user onto a canvas
they had to pan sideways to survey. Three per row matches the deck kit,
and the extra row gap is deliberate: the canvas draws frame names at a
fixed screen-space offset, so at fit-to-screen zoom a plain column gap
lets the second row's labels sit on top of the boards above them.

knowledge-carousel additionally takes hard line breaks in two body
paragraphs. Greedy wrapping put a comma at the start of a line and left
a lone full stop on the last one, which are both CJK line-break faults;
authored breaks remove the engine's freedom to reintroduce them.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
This commit is contained in:
Fini 2026-08-09 19:55:00 +08:00
parent 267a08f7aa
commit c37a109e3c
30 changed files with 195 additions and 94 deletions

View file

@ -3011,8 +3011,8 @@
]
}
],
"x": 3600,
"y": 0
"x": 0,
"y": 1800
},
{
"type": "frame",
@ -3265,8 +3265,8 @@
]
}
],
"x": 4800,
"y": 0
"x": 1200,
"y": 1800
}
]
}

View file

@ -1987,8 +1987,8 @@
]
}
],
"x": 3600,
"y": 0
"x": 0,
"y": 1800
},
{
"type": "frame",
@ -2269,8 +2269,8 @@
]
}
],
"x": 4800,
"y": 0
"x": 1200,
"y": 1800
},
{
"type": "frame",
@ -2792,8 +2792,8 @@
]
}
],
"x": 6000,
"y": 0
"x": 2400,
"y": 1800
}
]
}

View file

@ -720,7 +720,7 @@
"type": "text",
"id": "t37",
"name": "论点阐述",
"content": "轮播的优势是节奏,不是容量。一页塞两个论点,读者一个都记不住;拆成两页,两个都记得住。多拆一页的成本几乎为零,真正贵的是读者的耐心。",
"content": "轮播的优势是节奏,不是容量。一页塞两个论点,\n读者一个都记不住;拆成两页,两个都记得住。\n多拆一页的成本几乎为零,真正贵的是读者的耐心。",
"fontFamily": "Noto Sans SC",
"fontSize": 34,
"fontWeight": 400,
@ -935,7 +935,7 @@
"type": "text",
"id": "t48",
"name": "论点阐述",
"content": "想让一句话被看见,办法不是把它放大,而是把它周围清空。留白足够时,30px 的字也比挤成一堆的 60px 更醒目。",
"content": "想让一句话被看见,办法不是把它放大,\n而是把它周围清空。留白足够时,\n30px 的字也比挤成一堆的 60px 更醒目。",
"fontFamily": "Noto Sans SC",
"fontSize": 34,
"fontWeight": 400,
@ -1058,8 +1058,8 @@
]
}
],
"x": 3600,
"y": 0
"x": 0,
"y": 1800
},
{
"type": "frame",
@ -1633,8 +1633,8 @@
]
}
],
"x": 4800,
"y": 0
"x": 1200,
"y": 1800
}
]
}

View file

@ -1894,8 +1894,8 @@
]
}
],
"x": 3600,
"y": 0
"x": 0,
"y": 1800
},
{
"type": "frame",
@ -2183,8 +2183,8 @@
]
}
],
"x": 4800,
"y": 0
"x": 1200,
"y": 1800
},
{
"type": "frame",
@ -2485,8 +2485,8 @@
]
}
],
"x": 6000,
"y": 0
"x": 2400,
"y": 1800
}
]
}

View file

@ -1975,8 +1975,8 @@
]
}
],
"x": 3600,
"y": 0
"x": 0,
"y": 1800
},
{
"type": "frame",
@ -2551,8 +2551,8 @@
]
}
],
"x": 4800,
"y": 0
"x": 1200,
"y": 1800
}
]
}

View file

@ -1844,8 +1844,8 @@
]
}
],
"x": 3600,
"y": 0
"x": 0,
"y": 1800
},
{
"type": "frame",
@ -2289,8 +2289,8 @@
]
}
],
"x": 4800,
"y": 0
"x": 1200,
"y": 1800
},
{
"type": "frame",
@ -2561,8 +2561,8 @@
]
}
],
"x": 6000,
"y": 0
"x": 2400,
"y": 1800
}
]
}

File diff suppressed because one or more lines are too long

View file

@ -2249,8 +2249,8 @@
]
}
],
"x": 3600,
"y": 0
"x": 0,
"y": 1710
},
{
"type": "frame",
@ -2785,8 +2785,8 @@
]
}
],
"x": 4800,
"y": 0
"x": 1200,
"y": 1710
},
{
"type": "frame",
@ -3233,8 +3233,8 @@
]
}
],
"x": 6000,
"y": 0
"x": 2400,
"y": 1710
}
]
}

View file

@ -2021,8 +2021,8 @@
]
}
],
"x": 3600,
"y": 0
"x": 0,
"y": 1800
},
{
"type": "frame",
@ -2455,8 +2455,8 @@
]
}
],
"x": 4800,
"y": 0
"x": 1200,
"y": 1800
},
{
"type": "frame",
@ -2905,8 +2905,8 @@
]
}
],
"x": 6000,
"y": 0
"x": 2400,
"y": 1800
}
]
}

View file

@ -1562,8 +1562,8 @@
]
}
],
"x": 3600,
"y": 0
"x": 0,
"y": 1800
},
{
"type": "frame",
@ -2245,8 +2245,8 @@
]
}
],
"x": 4800,
"y": 0
"x": 1200,
"y": 1800
},
{
"type": "frame",
@ -2519,8 +2519,8 @@
]
}
],
"x": 6000,
"y": 0
"x": 2400,
"y": 1800
},
{
"type": "frame",
@ -2807,8 +2807,8 @@
]
}
],
"x": 7200,
"y": 0
"x": 0,
"y": 3600
}
]
}

View file

@ -3657,8 +3657,8 @@
]
}
],
"x": 3600,
"y": 0
"x": 0,
"y": 1800
},
{
"type": "frame",
@ -4363,8 +4363,8 @@
]
}
],
"x": 4800,
"y": 0
"x": 1200,
"y": 1800
},
{
"type": "frame",
@ -5328,8 +5328,8 @@
]
}
],
"x": 6000,
"y": 0
"x": 2400,
"y": 1800
}
]
}

View file

@ -1950,8 +1950,8 @@
]
}
],
"x": 3600,
"y": 0
"x": 0,
"y": 1800
},
{
"type": "frame",
@ -2772,8 +2772,8 @@
]
}
],
"x": 4800,
"y": 0
"x": 1200,
"y": 1800
},
{
"type": "frame",
@ -3423,8 +3423,8 @@
]
}
],
"x": 6000,
"y": 0
"x": 2400,
"y": 1800
},
{
"type": "frame",
@ -3745,8 +3745,8 @@
]
}
],
"x": 7200,
"y": 0
"x": 0,
"y": 3600
},
{
"type": "frame",
@ -4360,8 +4360,8 @@
]
}
],
"x": 8400,
"y": 0
"x": 1200,
"y": 3600
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View file

@ -8,6 +8,13 @@ from oplib import (Ids, frame, rect, text, solid, stroke, write_doc,
PLACEHOLDER_TITLE, PLACEHOLDER_SPEC)
W, H, GAP = 1080, 1440, 120
# 3 板一行 —— 与 deck 体系(deckkit.BOARDS_PER_ROW)同一约定:多板模板在画布上
# 分行铺开,而不是拖成一长排。行间距比列间距多 240 不是手滑:画布在帧上方以
# **屏幕空间**固定偏移画帧名,缩到能整屏看时 120 文档像素只剩十几个屏幕像素,
# 第二行的帧名会压到上一行的板上。
BOARDS_PER_ROW = 3
ROW_GAP = GAP + 240
PAD_X, PAD_Y = 72, 88
VARS = color_vars({
@ -236,8 +243,8 @@ def build():
"不要把字塞满整张图。",),
cta_page()]
for i, p in enumerate(pages):
p["x"] = i * (W + GAP)
p["y"] = 0
p["x"] = (i % BOARDS_PER_ROW) * (W + GAP)
p["y"] = (i // BOARDS_PER_ROW) * (H + ROW_GAP)
dst = sys.argv[1]
write_doc(dst, VARS, pages, "截图教程卡 · 小红书 3:4 模板")

View file

@ -5,6 +5,13 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from oplib import Ids, frame, rect, path, text, solid, stroke, write_doc, color_vars
W, H, GAP = 1080, 1440, 120
# 3 板一行 —— 与 deck 体系(deckkit.BOARDS_PER_ROW)同一约定:多板模板在画布上
# 分行铺开,而不是拖成一长排。行间距比列间距多 240 不是手滑:画布在帧上方以
# **屏幕空间**固定偏移画帧名,缩到能整屏看时 120 文档像素只剩十几个屏幕像素,
# 第二行的帧名会压到上一行的板上。
BOARDS_PER_ROW = 3
ROW_GAP = GAP + 240
PAD_X, PAD_Y = 72, 88
VARS = color_vars({
@ -19,18 +26,32 @@ VARS = color_vars({
ids = Ids()
# 论点阐述的正文在 936px 宽、34px 字号下每行放得下 27 个汉字(936/34)。交给
# 引擎贪心折行时,第 2、3 条的断点正好落在标点前,折出「,真正贵的是…」这种
# 行首标点、和「。」独占末行的孤字——中文行首禁则的两类事故。
#
# 断点写死在标点**之后**,每段 ≤27 字:这样每一行都是作者断的硬行,引擎不再
# 有折行的余地,两类事故按构造消失(检测器的孤字/行首禁则只对引擎折出的软行
# 成立)。分段按**渲染宽度**挑而不是字数——第 3 条里的 30px/60px 是半角,24 字
# 那行实际只有 18.5em,所以 18/15/24 字的分法反而是三行最齐的。
#
# 第 1 条没有写死断点:它引擎折出来本就干净,而它唯一可行的三段分法首行要占
# 满 27.0em / 27.53em 的行宽,把这条冻成硬行等于交出引擎在字体度量有出入时重
# 折的余地,不值得。
POINTS = [
("先有结论,再有论据",
"读者划到你这一页,只会给你一眼的时间。把结论放进标题,把论据留给正文,"
"不要让人读完三行才知道你想说什么。你以为在铺垫,读者以为你没重点。",
"标题写结论,正文写理由。"),
("一页只讲一个论点",
"轮播的优势是节奏,不是容量。一页塞两个论点,读者一个都记不住;"
"拆成两页,两个都记得住。多拆一页的成本几乎为零,真正贵的是读者的耐心。",
"轮播的优势是节奏,不是容量。一页塞两个论点,\n"
"读者一个都记不住;拆成两页,两个都记得住。\n"
"多拆一页的成本几乎为零,真正贵的是读者的耐心。",
"讲不完,就拆下一页。"),
("留白比字号更重要",
"想让一句话被看见,办法不是把它放大,而是把它周围清空。"
"留白足够时,30px 的字也比挤成一堆的 60px 更醒目。",
"想让一句话被看见,办法不是把它放大,\n"
"而是把它周围清空。留白足够时,\n"
"30px 的字也比挤成一堆的 60px 更醒目。",
"想突出,先清空周围。"),
]
@ -194,8 +215,8 @@ def build():
pages = [cover(), point_page(1), point_page(2), point_page(3),
summary_page()]
for i, p in enumerate(pages):
p["x"] = i * (W + GAP)
p["y"] = 0
p["x"] = (i % BOARDS_PER_ROW) * (W + GAP)
p["y"] = (i // BOARDS_PER_ROW) * (H + ROW_GAP)
write_doc(sys.argv[1], VARS, pages, "知识轮播 · 小红书 3:4 模板")

View file

@ -102,6 +102,13 @@ CJK = "Noto Sans SC"
NUM = "Inter"
W, H, GAP = 1080, 1440, 120
# 3 板一行 —— 与 deck 体系(deckkit.BOARDS_PER_ROW)同一约定:多板模板在画布上
# 分行铺开,而不是拖成一长排。行间距比列间距多 240 不是手滑:画布在帧上方以
# **屏幕空间**固定偏移画帧名,缩到能整屏看时 120 文档像素只剩十几个屏幕像素,
# 第二行的帧名会压到上一行的板上。
BOARDS_PER_ROW = 3
ROW_GAP = GAP + 240
EDGE = 80
TOP, BOT = 96, 300 # 下边距被山形吃掉,见母版规则 2
INNER = W - EDGE * 2
@ -235,8 +242,8 @@ def board(page, name, main, extra_decor=None):
fill=solid("$c-bg"), clipContent=True)
# 装饰层是最后一个孩子 —— jian 里 index 0 最上,写反了山会盖住正文。
shell["children"] = [content, decor]
shell["x"] = (page - 1) * (W + GAP)
shell["y"] = 0
shell["x"] = ((page - 1) % BOARDS_PER_ROW) * (W + GAP)
shell["y"] = ((page - 1) // BOARDS_PER_ROW) * (H + ROW_GAP)
return shell

View file

@ -106,6 +106,13 @@ CJK = "Noto Sans SC"
NUM = "DM Mono"
W, H, GAP = 1080, 1440, 120
# 3 板一行 —— 与 deck 体系(deckkit.BOARDS_PER_ROW)同一约定:多板模板在画布上
# 分行铺开,而不是拖成一长排。行间距比列间距多 240 不是手滑:画布在帧上方以
# **屏幕空间**固定偏移画帧名,缩到能整屏看时 120 文档像素只剩十几个屏幕像素,
# 第二行的帧名会压到上一行的板上。
BOARDS_PER_ROW = 3
ROW_GAP = GAP + 240
EDGE = 80
TOP, BOT = 96, 128
INNER = W - EDGE * 2
@ -201,8 +208,8 @@ def board(page, name, label, main, source):
shell = frame(ids, f"{page:02d} {name}", width=W, height=H,
layout="vertical", fill=solid("$c-bg"), clipContent=True)
shell["children"] = [content]
shell["x"] = (page - 1) * (W + GAP)
shell["y"] = 0
shell["x"] = ((page - 1) % BOARDS_PER_ROW) * (W + GAP)
shell["y"] = ((page - 1) // BOARDS_PER_ROW) * (H + ROW_GAP)
return shell

View file

@ -104,6 +104,13 @@ CJK = "Noto Sans SC"
NUM = "Inter"
W, H, GAP = 1080, 1440, 120
# 3 板一行 —— 与 deck 体系(deckkit.BOARDS_PER_ROW)同一约定:多板模板在画布上
# 分行铺开,而不是拖成一长排。行间距比列间距多 240 不是手滑:画布在帧上方以
# **屏幕空间**固定偏移画帧名,缩到能整屏看时 120 文档像素只剩十几个屏幕像素,
# 第二行的帧名会压到上一行的板上。
BOARDS_PER_ROW = 3
ROW_GAP = GAP + 240
EDGE = 80
TOP, BOT = 96, 128
INNER = W - EDGE * 2 # 920,12 列 × 62 + 11 沟槽 × 16
@ -209,8 +216,8 @@ def board(page, name, main, note, decor=None):
content["children"] = [header(page), main, footer(note)]
shell = stack(ids, f"{page:02d} {name}", content, decor or [],
width=W, height=H, fill=solid("$c-bg"))
shell["x"] = (page - 1) * (W + GAP)
shell["y"] = 0
shell["x"] = ((page - 1) % BOARDS_PER_ROW) * (W + GAP)
shell["y"] = ((page - 1) // BOARDS_PER_ROW) * (H + ROW_GAP)
return shell

View file

@ -105,6 +105,13 @@ CJK = "Noto Sans SC"
NUM = "Inter"
W, H, GAP = 1080, 1440, 120
# 3 板一行 —— 与 deck 体系(deckkit.BOARDS_PER_ROW)同一约定:多板模板在画布上
# 分行铺开,而不是拖成一长排。行间距比列间距多 240 不是手滑:画布在帧上方以
# **屏幕空间**固定偏移画帧名,缩到能整屏看时 120 文档像素只剩十几个屏幕像素,
# 第二行的帧名会压到上一行的板上。
BOARDS_PER_ROW = 3
ROW_GAP = GAP + 240
EDGE = 80
TOP, BOT = 96, 128
INNER = W - EDGE * 2
@ -252,8 +259,8 @@ def board(page, name, main, smudge_at, tag=None):
fill=solid("$c-bg"), clipContent=True)
# 装饰层最后 —— jian 里 index 0 最上,写反了擦痕会糊住板书。
shell["children"] = [content, decor]
shell["x"] = (page - 1) * (W + GAP)
shell["y"] = 0
shell["x"] = ((page - 1) % BOARDS_PER_ROW) * (W + GAP)
shell["y"] = ((page - 1) // BOARDS_PER_ROW) * (H + ROW_GAP)
return shell

View file

@ -107,6 +107,13 @@ CJK = "Noto Sans SC"
NUM = "Inter"
W, H, GAP = 1080, 1440, 120
# 3 板一行 —— 与 deck 体系(deckkit.BOARDS_PER_ROW)同一约定:多板模板在画布上
# 分行铺开,而不是拖成一长排。行间距比列间距多 240 不是手滑:画布在帧上方以
# **屏幕空间**固定偏移画帧名,缩到能整屏看时 120 文档像素只剩十几个屏幕像素,
# 第二行的帧名会压到上一行的板上。
BOARDS_PER_ROW = 3
ROW_GAP = GAP + 240
EDGE = 80
TOP, BOT = 96, 128
INNER = W - EDGE * 2
@ -214,8 +221,8 @@ def board(page, name, main, stage, decor=None):
shell = stack(ids, f"{page:02d} {name}", content,
[rain_reflection()] + (decor or []),
width=W, height=H, fill=solid("$c-bg"))
shell["x"] = (page - 1) * (W + GAP)
shell["y"] = 0
shell["x"] = ((page - 1) % BOARDS_PER_ROW) * (W + GAP)
shell["y"] = ((page - 1) // BOARDS_PER_ROW) * (H + ROW_GAP)
return shell

View file

@ -107,6 +107,13 @@ CJK = "Noto Sans SC"
NUM = "Inter"
W, H, GAP = 1080, 1440, 120
# 3 板一行 —— 与 deck 体系(deckkit.BOARDS_PER_ROW)同一约定:多板模板在画布上
# 分行铺开,而不是拖成一长排。行间距比列间距多 240 不是手滑:画布在帧上方以
# **屏幕空间**固定偏移画帧名,缩到能整屏看时 120 文档像素只剩十几个屏幕像素,
# 第二行的帧名会压到上一行的板上。
BOARDS_PER_ROW = 3
ROW_GAP = GAP + 240
EDGE = 80
MARGIN_X = 112 # 页边红线的位置 = 1 个列宽
TEXT_EDGE = 152 # 正文左界,留出红线的呼吸
@ -232,8 +239,8 @@ def board(page, name, main, note):
shell = frame(ids, f"{page:02d} {name}", width=W, height=H, layout="none",
fill=solid("$c-bg"), clipContent=True)
shell["children"] = [content, paper]
shell["x"] = (page - 1) * (W + GAP)
shell["y"] = 0
shell["x"] = ((page - 1) % BOARDS_PER_ROW) * (W + GAP)
shell["y"] = ((page - 1) // BOARDS_PER_ROW) * (H + ROW_GAP)
return shell

View file

@ -80,6 +80,13 @@ CJK = "Noto Sans SC"
NUM = "Inter"
W, H, GAP = 1080, 1440, 120
# 3 板一行 —— 与 deck 体系(deckkit.BOARDS_PER_ROW)同一约定:多板模板在画布上
# 分行铺开,而不是拖成一长排。行间距比列间距多 240 不是手滑:画布在帧上方以
# **屏幕空间**固定偏移画帧名,缩到能整屏看时 120 文档像素只剩十几个屏幕像素,
# 第二行的帧名会压到上一行的板上。
BOARDS_PER_ROW = 3
ROW_GAP = GAP + 240
EDGE = 72
LH_DISPLAY, LH_HEAD, LH_BODY = 1.2, 1.35, 1.7
@ -135,7 +142,8 @@ def page(name, children, *, index, fill="$c-bg", pad_top=80):
padding=[pad_top, EDGE, 72, EDGE], gap=PAGE_GAP,
fill=solid(fill), clipContent=True)
node["children"] = children
node["x"], node["y"] = index * (W + GAP), 0
node["x"] = (index % BOARDS_PER_ROW) * (W + GAP)
node["y"] = (index // BOARDS_PER_ROW) * (H + ROW_GAP)
return node

View file

@ -67,6 +67,13 @@ CJK = "Noto Sans SC"
NUM = "Inter"
W, H, GAP = 1080, 1350, 120
# 3 板一行 —— 与 deck 体系(deckkit.BOARDS_PER_ROW)同一约定:多板模板在画布上
# 分行铺开,而不是拖成一长排。行间距比列间距多 240 不是手滑:画布在帧上方以
# **屏幕空间**固定偏移画帧名,缩到能整屏看时 120 文档像素只剩十几个屏幕像素,
# 第二行的帧名会压到上一行的板上。
BOARDS_PER_ROW = 3
ROW_GAP = GAP + 240
EDGE = 72
PAGE_GAP = 46
@ -124,7 +131,8 @@ def page(name, children, *, index, fill="$c-bg"):
padding=[72, EDGE], gap=PAGE_GAP, fill=solid(fill),
clipContent=True)
node["children"] = children
node["x"], node["y"] = index * (W + GAP), 0
node["x"] = (index % BOARDS_PER_ROW) * (W + GAP)
node["y"] = (index // BOARDS_PER_ROW) * (H + ROW_GAP)
return node

View file

@ -71,6 +71,13 @@ CJK = "Noto Sans SC"
NUM = "Inter"
W, H, GAP = 1080, 1440, 120
# 3 板一行 —— 与 deck 体系(deckkit.BOARDS_PER_ROW)同一约定:多板模板在画布上
# 分行铺开,而不是拖成一长排。行间距比列间距多 240 不是手滑:画布在帧上方以
# **屏幕空间**固定偏移画帧名,缩到能整屏看时 120 文档像素只剩十几个屏幕像素,
# 第二行的帧名会压到上一行的板上。
BOARDS_PER_ROW = 3
ROW_GAP = GAP + 240
EDGE = 72
PAGE_GAP = 34
@ -131,7 +138,8 @@ def page(name, children, *, index):
padding=[76, EDGE, 68, EDGE], gap=PAGE_GAP,
fill=solid("$c-bg"), clipContent=True)
node["children"] = children
node["x"], node["y"] = index * (W + GAP), 0
node["x"] = (index % BOARDS_PER_ROW) * (W + GAP)
node["y"] = (index // BOARDS_PER_ROW) * (H + ROW_GAP)
return node

View file

@ -112,6 +112,13 @@ CJK = "Noto Sans SC"
NUM = "Inter"
W, H, GAP = 1080, 1440, 120
# 3 板一行 —— 与 deck 体系(deckkit.BOARDS_PER_ROW)同一约定:多板模板在画布上
# 分行铺开,而不是拖成一长排。行间距比列间距多 240 不是手滑:画布在帧上方以
# **屏幕空间**固定偏移画帧名,缩到能整屏看时 120 文档像素只剩十几个屏幕像素,
# 第二行的帧名会压到上一行的板上。
BOARDS_PER_ROW = 3
ROW_GAP = GAP + 240
EDGE = 80
TOP, BOT = 96, 128
INNER = W - EDGE * 2
@ -317,8 +324,8 @@ def board(page, name, main, note, extra_decor=None):
fill=solid("$c-bg"), clipContent=True)
# 装饰层最后 —— jian 里 index 0 最上,写反了剥落会盖住正文。
shell["children"] = [content, decor]
shell["x"] = (page - 1) * (W + GAP)
shell["y"] = 0
shell["x"] = ((page - 1) % BOARDS_PER_ROW) * (W + GAP)
shell["y"] = ((page - 1) // BOARDS_PER_ROW) * (H + ROW_GAP)
return shell

Binary file not shown.

Before

Width:  |  Height:  |  Size: 177 KiB

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 411 KiB

After

Width:  |  Height:  |  Size: 443 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 347 KiB

After

Width:  |  Height:  |  Size: 383 KiB