Skip to content

Ảnh cover của khoá học ​

Một trăm chín mươi khoá thì không ai chụp đủ ảnh, và một thẻ không ảnh nằm cạnh chín thẻ có ảnh trông như khoá bị bỏ dở. Ảnh cover ở đây được vẽ bằng thuật toán: cùng một đầu vào luôn ra cùng một tệp, sửa phong cách một chỗ thì cả cộng đồng đổi theo, và mỗi ảnh chỉ vài kilobyte nằm thẳng trong D1.

Hai lớp, cố ý tách rời ​

LớpAi quyếtỞ đâu
Phong cách cộng đồng, bảng màu, hình nền, khungCố định theo cộng đồngSTYLES + LOOK trong scripts/course_cover_svg.py
Motif của khoá, hình ở giữaNgười soạn chọn theo nội dungcontent/courses/covers.json

Phong cách không lấy ngẫu nhiên theo slug. Ngẫu nhiên thì mỗi ảnh một kiểu và mất luôn cái chung, mười khoá của một cộng đồng phải nhìn là biết cùng một nhà.

Phần ngẫu nhiên duy nhất là các nhiễu nhỏ (độ nghiêng, khoảng cách, sắc độ), gieo từ slug bằng SHA-256 nên cố định vĩnh viễn với một khoá.

Cộng đồngNềnKhung
foodKem như giấy gói, lưới gạch men bếpĐĩa trắng
drinkXanh đá lạnh, sọc dọc mặt quầyLy cao nhìn thẳng
eduGiấy kẻ ngang như vở học tròBảng bo góc

Sáu motif dùng chung cho mọi cộng đồng: bars, gauge, flow, grid, stack, split.

Luật: không chữ trong ảnh ​

Chữ trong ảnh không dịch được sang tiếng Anh, trình đọc màn hình không đọc được, và nó vỡ khi thẻ co lại trên điện thoại. Bộ biên dịch chặn cứng: thấy <text hoặc font- là dừng.

Tên khoá đã nằm ngay dưới ảnh, bằng chữ thật, dịch được và chọn được.

cover_prompt để làm gì ​

Câu mô tả ảnh bằng tiếng Việt, do người soạn viết, không hiển thị ở đâu cả. Nó tồn tại vì SVG là một đống toạ độ không ai đọc được, ba tháng sau, câu đó là thứ duy nhất cho biết ảnh này lẽ ra vẽ cái gì. Nó cũng là thứ đưa cho người vẽ nếu sau này thay ảnh thuật toán bằng ảnh thật.

Ba tầng ảnh trên thẻ khoá ​

image_url   (ảnh riêng, người soạn tải lên qua admin, cất ở R2)
   ↓ không có
cover_svg   (ảnh vẽ, cất trong D1)
   ↓ không có
khung màu token + chữ đầu của tên

Không tầng nào bắt buộc. image_url luôn thắng, ảnh vẽ chỉ là đường lùi.

SVG đổi thành data URI ở SERVER, không ở frontend ​

API đổi cover_svg thành data:image/svg+xml,... rồi trả trong image_url, và xoá cột thô khỏi payload, đúng cách bìa sách đã làm (svgDataUri trong domains/communities/index.ts). Frontend không bao giờ cầm một chuỗi markup.

Vì sao đổi ở server chứ không ở client: trả chuỗi SVG thô ra ngoài thì sớm muộn có người nhúng nó bằng dangerouslySetInnerHTML, và khi đó mọi thẻ script trong chuỗi ấy chạy với quyền của trang. Chặn ở chỗ dữ liệu rời khỏi API là chặn một lần cho mọi frontend.

Dấu # cắt đứt data URI

Mã màu trong SVG là #C4542B. Không encodeURIComponent thì trình duyệt đọc # là mốc neo và cắt data URI ngay tại đó, ảnh hiện ra mất sạch màu, và không có lỗi nào trong console. svgDataUri đã lo phần này; đừng tự viết lại chỗ khác.

Đường ống ​

bash
# kiểm: render hết, đếm, và chặn ảnh có chữ
python3 scripts/course_covers_to_sql.py --check

# xem bằng mắt cả một cộng đồng: làm trước khi biên dịch
python3 scripts/course_covers_to_sql.py --preview food /tmp/food.html

# xem đúng một ảnh
python3 scripts/course_cover_svg.py food food-menu-engineering bars > /tmp/x.svg

# biên dịch ra migration
python3 scripts/course_covers_to_sql.py > migrations/<ngày>_covers_<nhóm>.sql

Khớp theo legacy_courses.id chứ không theo slug: legacy_courses.slug là slug tiếng Việt (thiet-ke-thuc-don) còn khoá trong pack mang slug tiếng Anh (food-menu-engineering), và hai thứ đó đã có lúc lệch nhau. course_id lấy từ manifest của chính khoá.

Mười tám khung, mỗi cộng đồng một vật của nghề ​

Cộng đồngKhungNềnCộng đồngKhungNền
FoodđĩalướiMarketingthẻ có sọclưới
Drinkly caosọc dọcOperationsbăng chuyềnkẻ ngang
Educationbảngkẻ ngangPeople & HRkẹp hồ sơsọc dọc
Beautygương đứngsọc dọcProductlục giáclưới
Commercetem hànglướiSalesphễusọc dọc
Healthvòng chạykẻ ngangFinancetrang sổkẻ ngang
Homemái nhàlướiFounderhạt nảy mầmsọc dọc
Hospitalitycửa vòmsọc dọcCEOhuy hiệukẻ ngang
AI & Technologymàn hìnhlướiCustomer Successbong bóng thoạisọc dọc

Đủ 190 khoá. Migration tách một tệp một cộng đồng (20260914c01…c18, mỗi tệp 22–48 KB): gộp cả 190 ảnh vào một tệp là khoảng 600 KB, vượt mốc 425 KB đã chạy được, và migration quá cỡ từng làm D1 bỏ dở giữa chừng.

Hệ số thu nhỏ motif: bắt buộc, không phải tuỳ chọn ​

LOOK có ba phần: khung, kiểu nền, và hệ số thu nhỏ motif. Motif được vẽ theo bán kính của cái đĩa, khung rộng nhất trong bộ. Khung hẹp hơn mà vẫn vẽ ở bán kính đó thì hình chồm ra ngoài viền và ảnh trông như lỗi dựng.

Đo theo chỗ hẹp nhất của khung, không theo chỗ rộng nhất. Gương và hạt nảy mầm xuống tới 0,58–0,62; tem hàng và màn hình giữ được 0,78–0,80.

Một motif viết số cứng thì hệ số không chạm tới được

m_split từng viết cứng cx - 230, width 215, height 300. Nó vẽ đúng một cỡ bất kể khung to nhỏ, nên hệ số thu nhỏ không có tác dụng gì với nó, và trong khung hẹp thì hai khối chồm hẳn ra ngoài. Lỗi này đã âm thầm tồn tại ở Drink và Education từ đợt đầu.

Mọi số đo trong một motif phải tính TỪ r. Có một phép kiểm hình học so hộp bao của motif với hộp bao của khung; nó bắt được đúng lỗi này.

Tài liệu nội bộ nền tảng Conan School.