---
version: 6
name: mza-vn-reading
description: "Giao diện đọc như sách cho blog mza.vn (\"My Personal Tech MBA\"). Đen – trắng là chính, chữ có chân cho tiêu đề và thân bài, nhiều khoảng trắng, gần như không màu. Ba nền đọc: Sáng, Giấy, Tối. Astro, trang tĩnh, nâng cấp dần (progressive enhancement); chạy tốt trên máy cũ, màn nhỏ (iPhone SE 1, iPhone mini) và khi cài thành PWA."
credit: "Phỏng theo DESIGN.md “Claude” (VoltAgent/awesome-design-md, giấy phép MIT), đổi sang đen – trắng cho mza.vn."   # dòng nguồn hiện trên /design-system/ (UC-114); `source` bên dưới là ghi chú nội bộ
source: >
  Phỏng theo DESIGN.md "Claude" trong VoltAgent/awesome-design-md (MIT) — giữ tinh thần biên tập, nền ấm, serif;
  bỏ màu cam san hô và font độc quyền của Anthropic; đổi sang đen – trắng theo yêu cầu tác giả (ADR-29).
  Soát và bổ sung bằng ak-ui-ux-pro-max (pattern "Newsletter / Content First", style "Swiss Modernism 2.0" phần lưới và kỷ luật khoảng cách;
  bỏ gợi ý màu xanh/cam và cặp Cormorant/Libre Baskerville vì trái ADR-29 và Libre Baskerville không đủ dấu tiếng Việt).

colors:
  # Mặc định = nền Sáng. Nền Giấy và Tối ở themes. Code chỉ dùng token, không ghi mã màu.
  ink: "#111111"            # chữ chính, nút chính, viền nhấn — màu thương hiệu
  body: "#2b2b2b"           # thân bài
  muted: "#6b6b6b"          # chữ phụ: ngày cập nhật, "Còn N phút", nhãn tầng
  muted-soft: "#9a9a9a"     # chỉ cho đường gạch chân link, viền vô hiệu — KHÔNG dùng cho chữ (tương phản < 4.5:1)
  hairline: "#e4e4e4"       # đường kẻ, viền thẻ
  canvas: "#ffffff"
  surface-soft: "#f6f6f4"   # nền khối phụ: "Từ thực tế", gate
  surface-strong: "#ececea" # nền thanh tiến độ, nút phụ khi nhấn
  primary: "{colors.ink}"
  on-primary: "#ffffff"
  highlight: "#e9e6dc"      # nền đoạn người đọc đánh dấu
  focus-ring: "#111111"     # vòng focus 2px + offset 2px; trên nút đen dùng vòng ngoài màu canvas + vòng đen
  scrim: "rgba(17,17,17,0.5)" # nền tối sau popup/ngăn kéo (40–60%)
  success: "#2f6b3b"        # chỉ tô chữ trạng thái, luôn kèm ký hiệu/chữ
  error: "#a3261b"          # chỉ tô chữ lỗi biểu mẫu, luôn kèm chữ "Lỗi:" hoặc biểu tượng
  accent-alt: "#5b3fd1"     # TÍM — dự phòng, KHÔNG dùng mặc định (ADR-29)

themes:
  light: { canvas: "#ffffff", ink: "#111111", body: "#2b2b2b", muted: "#6b6b6b", hairline: "#e4e4e4", surface-soft: "#f6f6f4", surface-strong: "#ececea", highlight: "#e9e6dc", meta-theme-color: "#ffffff" }
  paper: { canvas: "#faf9f5", ink: "#141413", body: "#3d3d3a", muted: "#6c6a64", hairline: "#e6dfd8", surface-soft: "#f5f0e8", surface-strong: "#ebe6df", highlight: "#e8e0d2", meta-theme-color: "#faf9f5" }
  dark:  { canvas: "#141413", ink: "#f5f4ef", body: "#d9d7d0", muted: "#a09d96", hairline: "#2e2c29", surface-soft: "#1f1e1b", surface-strong: "#2a2825", highlight: "#3a3832", on-primary: "#141413", focus-ring: "#f5f4ef", meta-theme-color: "#141413" }
  # Tương phản đã tính (2026-10-18): thân bài/nền — Sáng 14.2, Giấy 10.3, Tối 12.8; chữ phụ/nền — 5.3, 5.1, 6.8.

share-backgrounds:
  # Nền của ẢNH chia sẻ (publishing UC-115, ADR-35) — CHỈ cho ảnh, không đổi giao diện web. Thứ tự = thứ tự nút chọn nền; tên hiện trên giao diện ở `src/i18n/vi.ts` (share.backgrounds).
  # Tương phản đã tính (2026-10-20) với canvas, ink / body / muted: Trắng 18.9 / 14.2 / 5.3 · Giấy 17.5 / 10.3 / 5.1 · Đen 16.7 / 12.8 / 6.8 · Chấm 17.5 / 10.3 / 5.1 (mỗi dòng chữ nằm trên dải nền Giấy trơn, chấm không chạy dưới chữ) · Tím 6.8 / 6.1 / 5.2 — mọi chữ ≥ 4.5:1.
  default: paper   # ảnh xem trước khi dán link luôn dùng nền này; bảng chia sẻ dùng nền này tới khi người đọc chọn nền khác (nhớ trên máy)
  light:  { canvas: "#ffffff", ink: "#111111", body: "#2b2b2b", muted: "#6b6b6b", rule: "#e4e4e4" }   # Trắng = themes.light
  paper:  { canvas: "#faf9f5", ink: "#141413", body: "#3d3d3a", muted: "#6c6a64", rule: "#e6dfd8" }   # Giấy = themes.paper
  dark:   { canvas: "#141413", ink: "#f5f4ef", body: "#d9d7d0", muted: "#a09d96", rule: "#2e2c29" }   # Đen = themes.dark
  dots:   { canvas: "#faf9f5", ink: "#141413", body: "#3d3d3a", muted: "#6c6a64", rule: "#e6dfd8", pattern: dots, pattern-color: "#cdc5b8", pattern-step: 28, pattern-radius: 2 }   # Chấm — hoạ tiết: lưới chấm đơn sắc kiểu giấy kỹ thuật trên nền Giấy
  violet: { canvas: "#5b3fd1", ink: "#ffffff", body: "#f4f1ff", muted: "#e3ddff", rule: "#7c64dd" }   # Tím — màu: colors.accent-alt làm nền, chữ trắng; chỉ ở ảnh chia sẻ (ADR-29: tím vẫn không dùng trên web)

fonts:
  # Tự host (ADR-28). Cả ba có bộ tiếng Việt (đã kiểm Google Fonts 2026-10-18), giấy phép SIL OFL.
  serif: "Literata, 'Noto Serif', Georgia, 'Times New Roman', serif"
  sans: "'Be Vietnam Pro', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif"
  mono: "'JetBrains Mono', ui-monospace, Menlo, Consolas, monospace"
  files:
    # Bản tĩnh (không dùng bản biến thiên) để nhẹ và chắc chạy trên máy cũ; woff2; cắt unicode-range Latin + Latin-ext + Vietnamese.
    - "Literata 400, 400 italic, 600"   # thân bài, câu định nghĩa, tiêu đề
    - "Be Vietnam Pro 400, 500"          # giao diện
    - "JetBrains Mono 400"               # chỉ tải ở trang có khối code
  loading:
    font-display: swap
    preload: "chỉ Literata 400 (font thân bài)"
    fallback-metrics: "khai size-adjust/ascent-override cho Georgia để chữ không nhảy khi font tải xong (CLS < 0.1)"

typography:
  # Đơn vị rem (1rem = cỡ chữ gốc của trình duyệt, mặc định 16px) — để người đọc phóng chữ của hệ thống vẫn có tác dụng.
  display-lg:   { fontFamily: "{fonts.serif}", fontSize: "clamp(1.75rem, 1.2rem + 2.6vw, 2.75rem)", fontWeight: 600, lineHeight: 1.15, letterSpacing: "-0.01em", desktop-fine: "clamp(2.25rem, 1.25rem + 1.25vw, 2.5rem)" }  # 28→44px; chuột/trackpad từ 1200px 36→40px (tác giả 2026-10-19: trên MacBook "nó khá là to")
  display-md:   { fontFamily: "{fonts.serif}", fontSize: "clamp(1.5rem, 1.2rem + 1.4vw, 2rem)", fontWeight: 600, lineHeight: 1.2 }                         # 24→32px
  heading-h2:   { fontFamily: "{fonts.serif}", fontSize: "clamp(1.3125rem, 1.15rem + 0.7vw, 1.625rem)", fontWeight: 600, lineHeight: 1.3 }               # 21→26px
  heading-h3:   { fontFamily: "{fonts.serif}", fontSize: "1.1875rem", fontWeight: 600, lineHeight: 1.35 }                                               # 19px
  definition:   { fontFamily: "{fonts.serif}", fontSize: "clamp(1.125rem, 1.05rem + 0.4vw, 1.3125rem)", fontStyle: italic, fontWeight: 400, lineHeight: 1.5 }
  body-read:    { fontFamily: "{fonts.serif}", fontSize: "var(--read-size)", fontWeight: 400, lineHeight: 1.7 }   # xem reading.font-steps
  body-ui:      { fontFamily: "{fonts.sans}", fontSize: "1rem", fontWeight: 400, lineHeight: 1.5, desktop-fine: "0.9375rem" }  # 16px (chuột/trackpad từ 1200px: 15px) — ô nhập ≥ 16px để iOS không tự phóng (iOS luôn là cảm ứng)
  label:        { fontFamily: "{fonts.sans}", fontSize: "0.8125rem", fontWeight: 500, lineHeight: 1.4, letterSpacing: "0.01em" }  # 13px — nhỏ nhất cho chữ
  button:       { fontFamily: "{fonts.sans}", fontSize: "1rem", fontWeight: 500, lineHeight: 1.2 }
  code:         { fontFamily: "{fonts.mono}", fontSize: "0.9375rem", fontWeight: 400, lineHeight: 1.6 }
  numbers:      { fontVariantNumeric: "tabular-nums" }   # số A → B, "Còn N phút", số ủng hộ

reading:
  measure: { narrow: "29em", medium: "33em", wide: "37.5em", desktop-wide: "34.5em" }   # nút Aa; mặc định medium, riêng chuột/trackpad từ 1200px mặc định wide = 34.5em (đầu bài và thân bài cùng độ rộng nên dùng nấc rộng nhất vẫn ≤ 75 ký tự — đo 2026-10-19 trên 44 dòng đầy ở 18px: 34.5em → 62–74, 37.5em → tới 81; tác giả 2026-10-19). ≈ 58 · 66 · 75 ký tự tiếng Việt (đo trên Literata: 61–69 ký tự/dòng ở medium 19px, 2026-10-18; 61–73 ở medium 18px, 2026-10-19). Chưa chọn thì theo thiết bị; đã chọn Hẹp/Vừa/Rộng thì nhớ đúng nấc. Không dùng `ch`: chữ "0" của Literata rộng hơn chữ tiếng Việt trung bình, 66ch ra ~84 ký tự/dòng, vượt mức 75 của reading-standard
  font-steps:
    mobile:  ["1rem", "1.0625rem", "1.125rem", "1.25rem", "1.375rem"]   # < 768px: 16 17 18 20 22px; mặc định nấc 3 (18px)
    tablet:  ["1.0625rem", "1.125rem", "1.1875rem", "1.3125rem", "1.4375rem"]  # ≥ 768px trừ chuột/trackpad từ 1200px (gồm màn cảm ứng rộng): 17 18 19 21 23px; mặc định nấc 3 (19px)
    desktop: ["1rem", "1.0625rem", "1.125rem", "1.25rem", "1.375rem"]   # (pointer: fine) and (min-width: 1200px): 16 17 18 20 22px; mặc định nấc 3 (18px) — màn MacBook/laptop đã to sẵn; nút Aa vẫn đủ 5 nấc
  paragraph-gap: "1.1em"
  gutter: { "<360px": "16px", "360–767px": "20px", "768–1199px": "32px", ">=1200px": "lề của container" }
  container: { from: "1200px", width: "mục lục 240px + 2 × khoảng cột clamp(40px, 3.5vw, 56px) + cột giữa 39rem + 'Trong bài này' 220px ≈ 1196–1236px, căn giữa; lề ngoài tối thiểu {spacing.xl}", shared-by: "top-nav (nội dung; nền vẫn trải hết bề ngang), dải 'Bản nháp' (chữ; nền trải hết), lưới ba cột trang bài, trang chủ (listing rộng), chân trang (nội dung; đường kẻ trên trải hết — tác giả 2026-10-19 'footer cũng làm như header'). Mép trái logo = mép trái cột mục lục = mép trái chân trang, mép phải cụm hành động top-nav = mép phải cột 'Trong bài này' = mép phải khung chân trang (tác giả 2026-10-19, theo VnExpress International); trang listing hẹp (trang trụ, Chủ đề…) giữ cột 44rem căn giữa trong khung", below: "dưới 1200px giữ lề gutter như cũ" }
  words-per-minute: 200   # giả định (ADR-28)

rounded:
  none: 0
  sm: 4px      # nút, ô nhập
  md: 8px      # thẻ, khối gate, popover, ngăn kéo
  pill: 9999px # chip trụ, tab tầng

spacing: # nhịp 4/8px
  xxs: 4px
  xs: 8px
  sm: 12px
  md: 16px
  lg: 24px
  xl: 32px
  xxl: 48px
  section: "clamp(48px, 8vw, 80px)"

breakpoints:
  # Thiết kế từ nhỏ lên (mobile-first). Mốc nhỏ nhất 320px = iPhone SE thế hệ 1, iPhone 5s.
  xs: "320px"     # sàn — mọi màn phải dùng được, không cuộn ngang
  sm: "375px"     # iPhone mini, iPhone 6–8, SE 2/3
  md: "768px"     # máy tính bảng dọc
  lg: "1200px"    # mục lục + "Trong bài này" hiện cạnh bài (UC-113)
  short-height: "max-height: 600px"   # máy màn thấp (SE 1 = 568px) và điện thoại xoay ngang — giảm thanh dính

touch:
  target-min:
    coarse: 44px            # cảm ứng (pointer: coarse) ở mọi bề rộng, và mọi màn dưới 1200px — icon nhỏ hơn thì nới vùng bấm
    fine: 32px              # chuột/trackpad từ 1200px — (pointer: fine) and (min-width: 1200px); vẫn vượt mức AA 24px của WCAG 2.5.8
  density-fine: "cùng media đó: top-nav 48px, tab tầng/chip padding 6px 12px (cao 32px), nút chữ padding dọc 8px, chữ giao diện {typography.body-ui} 15px, nhãn {typography.label} giữ 13px, display-lg 36–40px, display-md 28px, heading-h2 24px, definition 20px, thân bài theo reading.font-steps.desktop — token dùng chung nên trang chủ, trang trụ, trang bài cùng mật độ (tác giả 2026-10-19)"
  gap-min: 8px
  tap-action: "touch-action: manipulation"   # bỏ trễ 300ms trên trình duyệt cũ
  input-font-min: 16px      # tránh iOS tự phóng khi bấm vào ô nhập

motion:
  duration: { fast: 150ms, base: 200ms, slow: 300ms }
  easing: { enter: "cubic-bezier(0.2, 0, 0, 1)", exit: "cubic-bezier(0.4, 0, 1, 1)" }
  rules: "chỉ animate transform/opacity; tối đa 1–2 chuyển động mỗi màn; prefers-reduced-motion: reduce → tắt hết chuyển động, kể cả cuộn mượt"

z-index:
  content: 0
  sticky: 10       # thanh menu, thanh tiến độ
  dropdown: 20     # menu Chủ đề ▾
  drawer: 40       # mục lục điện thoại, ghi chú
  popover: 50      # cài đặt đọc, popover chọn chữ
  notice: 60       # thông báo cookie (ADR-30) — trên popover, dưới popup ủng hộ
  modal: 100       # popup ủng hộ
  toast: 1000

logo:
  wordmark: "mza — luôn chữ thường, không bao giờ viết hoa (MZA, Mza); tác giả 2026-10-18. Là FILE SVG `brand/logo/mza-wordmark.svg` (Literata 600 chỉnh tay theo quẻ Địa Thiên Thái ䷊ — Đất ☷ ở trên, Trời ☰ ở dưới, trời đất giao hoà: nửa trên các nét giữ góc vuông như Literata, đầu nét trái của m là khối chữ nhật vuông góc (cạnh trên ngang, cao bằng đỉnh vòm), nửa dưới bo tròn r ≈ 18% độ dày thân, đuôi a cắt dọc rồi khép bằng nửa tròn — tác giả chốt 2026-10-24, ADR-36; các bản chốt trước lưu ở `brand/source/history/`), KHÔNG gõ chữ bằng font"
  size: "cao theo cỡ chữ của chỗ đặt (1em = 1000 đơn vị của file): 20px ở top-nav và chân trang, 18px dưới 360px; tối thiểu 32px chiều rộng trên màn hình, 12mm khi in"
  color: "{colors.ink} (đổi theo nền đọc; SVG dùng currentColor) — chỉ một màu, không gradient, không hiệu ứng"
  link: "về trang chủ /, aria-label 'mza.vn — trang chủ'; SVG bên trong aria-hidden"
  clearspace: "≥ 1 chiều cao chữ x (≈ 0.5em) quanh khung chữ trên ấn phẩm; trên web ≥ 8px quanh chữ, vùng bấm ≥ 44px"
  favicon: "chữ 'm' đơn của logo (`brand/logo/mza-mark.svg`), đen trên trắng: favicon.svg, favicon.ico 16/32/48, PNG 32, apple-touch 180, PWA 192/512 + maskable 512 — sinh từ `brand/icons` bằng `node brand/source/build.mjs`"
  guideline: "`brand/GUIDELINE.md` (màu, vùng an toàn, cỡ tối thiểu, điều không làm)"

icons:
  set: "Lucide (SVG nội tuyến, nét 1.5px); ngoại lệ duy nhất: dấu Facebook và Zalo của contact-list (Lucide không có dấu thương hiệu) — vẽ riêng trên cùng lưới 24px, nét 1.5px, một màu currentColor"
  size: { sm: 16px, md: 20px, lg: 24px }
  rule: "không dùng emoji làm biểu tượng điều khiển (☕ trong chữ 'Ủng hộ' thay bằng SVG cốc); nút chỉ có biểu tượng phải có aria-label tiếng Việt"

components:
  top-nav:            { background: "{colors.canvas}", text: "{colors.ink}", typography: "{typography.body-ui}", height: "56px; chuột/trackpad từ 1200px 48px", borderBottom: "1px {colors.hairline}", paddingTop: "env(safe-area-inset-top)", frame: "từ 1200px nội dung nằm trong {reading.container} (logo không còn lề âm, thẳng mép mục lục); nền và đường kẻ trải hết bề ngang", behavior: "từ 1200px: dính, gồm logo · link trang · Aa; dưới 1200px ở trang bài: chỉ logo · menu (768–1199px menu là link hiện sẵn), KHÔNG dính — cuộn đi cùng trang; công cụ đọc nằm ở reading-toolbar (tác giả chọn 2026-10-18: 'Đọc trước')", reading-tools: "trang bài từ 1200px: một cụm bên phải cùng thứ tự với reading-toolbar — Ghi chú · Cài đặt (Aa) · Sao chép; nút chữ nhỏ {typography.label} 500 kèm icon {icons.size.sm}, không viền, nền {colors.surface-soft} khi rê chuột, vùng bấm ≥ touch.target-min; aria-label đầy đủ ('Ghi chú của tôi', 'Cài đặt đọc', 'Sao chép bài (Markdown)'); cả ba cần JS (tắt JS thì ẩn); trình duyệt chặn lưu thì 'Ghi chú' vẫn mở khung với câu báo (reader UC-210 E1); lỗi chép (UC-108 E1) hiện một dòng nền {colors.ink} ngay dưới top-nav, căn phải theo mép khung, 2 giây; KHÔNG có nút sao chép trong thân bài, KHÔNG có 'Ghi chú của tôi' ở cuối mục lục (một chỗ duy nhất — tác giả 2026-10-19 'xem kỹ ảnh để đưa ra phương án tối ưu nhất cho UI')" }
  topics-menu:        { label: "'Chủ đề ▾' — mục đầu của link trang, trước 'Giới thiệu' (ADR-31; thay 'Phòng ban ▾' + link 'Chủ đề')", open: "<details>/<summary>: bấm/chạm/Enter mở, tắt JS vẫn mở–đóng được; có JS thì Esc (focus về nút) hoặc bấm ngoài đóng; KHÔNG mở khi rê chuột", groups: "3 nhóm theo group trong config/pillars.yaml, thứ tự theo file: Nguyên lý (foundation) · Trụ cột (pillar) · Nền tảng (people + system); tiêu đề nhóm {typography.label} 500 {colors.muted}, không bấm được", items: "tên title_en, LUÔN đủ 9 mục; mục chưa có gì để đọc (không chủ đề đã đăng làm trụ chính, không chủ đề liên quan qua impacts) chữ {colors.muted} + nhãn 'sắp ra' {typography.label} {colors.muted}, vẫn là link tới /<key>/ (trang trống); mục của trang đang xem gạch chân, aria-current=page", footer: "dòng cuối 'Xem tất cả chủ đề →' /topics/, cách nhóm bằng hairline", panel: "≥768px: khung nổi dưới nút, {colors.canvas}, viền 1px {colors.hairline}, {rounded.md}, ba cột cạnh nhau (mỗi nhóm một cột), z-index {z-index.dropdown}; <768px: cùng 3 nhóm (tiêu đề nhóm + mục thụt vào) đầu menu ☰, rồi 'Xem tất cả chủ đề →', 'Giới thiệu'; khung ☰ cuộn bên trong khi cao hơn màn", target: "mỗi mục cao touch.target-min (44px cảm ứng; 32px chuột/trackpad từ 1200px)" }
  skip-link:          { text: "Bỏ qua, tới nội dung", visible: "chỉ khi được focus bằng bàn phím" }
  button-primary:     { background: "{colors.primary}", text: "{colors.on-primary}", typography: "{typography.button}", rounded: "{rounded.sm}", padding: "12px 20px (chuột/trackpad từ 1200px: 8px 20px)", minHeight: "touch.target-min", pressed: "opacity 0.85", disabled: "opacity 0.4 + aria-disabled", loading: "giữ nguyên kích thước, chữ 'Đang gửi…'" }
  button-secondary:   { background: transparent, text: "{colors.ink}", border: "1px {colors.ink}", typography: "{typography.button}", rounded: "{rounded.sm}", padding: "12px 16px (chuột/trackpad từ 1200px: 8px 16px)", minHeight: "touch.target-min", pressed: "background {colors.surface-strong}" }
  icon-button:        { size: 44px, icon: "{icons.size.md}", rounded: "{rounded.sm}", label: "aria-label bắt buộc" }
  text-link:          { text: "{colors.ink}", textDecoration: "underline 1px", underlineOffset: 3px, decorationColor: "{colors.muted-soft}", pressed: "decorationColor {colors.ink}" }
  pain-card:          { background: "{colors.canvas}", border: "1px {colors.ink}", rounded: "{rounded.md}", padding: "{spacing.lg}", title: "{typography.heading-h3}", caption: "{typography.label}", grid: "1 cột <768px · 2 cột 768–1199px · 3 cột ≥1200px", pressed: "background {colors.surface-soft}" }
  portfolio-row:      { role: "một mục của trang Portfolio (publishing UC-116): <details>/<summary>, bấm cả hàng để mở–đóng", border: "1px {colors.hairline}", rounded: "{rounded.md} — cả lúc thu gọn và lúc mở", background: "{colors.canvas}", shadow: none, row: "cao ≥ touch.target-min, padding {spacing.xs} {spacing.md} {spacing.xs} {spacing.xs}, cách {spacing.sm}; rê chuột nền {colors.surface-soft}; focus-visible vòng 2px {colors.focus-ring} offset 2px", list: "các hàng cách nhau {spacing.sm}", logo: "tròn 52px ({rounded.pill}), viền 1px {colors.hairline}, nền {colors.surface-soft}; không có ảnh thì chữ cái đầu {typography.body-ui} 600 {colors.muted}", name: "{typography.body-ui} 600 {colors.ink}", summary: "cỡ {typography.label}, {colors.muted}; thu gọn một dòng '…', mở thì đầy đủ", caret: "Lucide chevron-down {icons.size.md} {colors.muted}, xoay 180° khi mở ({motion.duration.fast} {motion.easing.enter}; giảm chuyển động thì không xoay mượt)", details: "cỡ {typography.label} {colors.body}, lề trái bằng cột chữ ({spacing.xs} + 52px + {spacing.sm}); < 480px lề trái {spacing.md}" }
  contact-list:       { role: "khối 'Liên hệ' cuối trang Giới thiệu và cuối trang Portfolio (publishing UC-103 trang giới thiệu bước 6, UC-116; tác giả 2026-11-03 'phần liên hệ này nên để chi tiết thêm: email mặc định, kết nối facebook, kết nối zalo nè'): kênh tác giả khai ở /admin/settings/ (analytics UC-405 bước 7), tải lúc xem", title: "'Liên hệ' {typography.label} 500 {colors.muted}, letterSpacing 0.02em", rows: "thứ tự cố định Email · Facebook · Zalo; kênh chưa khai không có dòng; không kênh nào, API lỗi hoặc tắt JavaScript → không có khối (không tiêu đề, không khung trống)", row: "cả dòng là một link, cao ≥ touch.target-min, padding {spacing.xxs} {spacing.xs}; lưới: biểu tượng {icons.size.md} · tên kênh {typography.body-ui} 600 {colors.ink} · giá trị {typography.body-ui} {colors.body} (xuống dòng được, không cuộn ngang ở 320px) · '↗' {colors.muted} với link ngoài; rê chuột nền {colors.surface-soft}; focus-visible vòng 2px {colors.focus-ring} offset 2px", border: "đường kẻ 1px {colors.hairline} trên khối và dưới mỗi dòng; không khung, không bo, không bóng", icons: "Email: Lucide mail; Facebook, Zalo: dấu vẽ riêng (icons.set) — đen trắng, KHÔNG màu thương hiệu", external: "Facebook, Zalo mở tab mới, rel=noopener, chữ ẩn '(mở trang ngoài)'", value: "Email: địa chỉ; Facebook: link bỏ 'https://www.' (vd 'facebook.com/<tên>'); Zalo: số điện thoại → https://zalo.me/<số>", placement: "cuối trang, sau thân trang: khối hiện muộn không đẩy nội dung đang đọc (CLS)" }
  pillar-chip:        { border: "1px {colors.hairline}", text: "{colors.ink}", typography: "{typography.label}", rounded: "{rounded.pill}", padding: "10px 14px (chuột/trackpad từ 1200px: 6px 12px)", minHeight: "touch.target-min" }
  tier-tabs:          { item: "{component.pillar-chip}", active: { background: "{colors.ink}", text: "{colors.on-primary}", ariaCurrent: page }, small-screen: "< 480px: lưới 2×2, mỗi tab rộng bằng ô (tránh 3+1 lệch ở 375–479px), KHÔNG cuộn ngang, KHÔNG cắt chữ" }
  article-header:     { content: "tab tầng · dòng '<Nhóm>: <title_en>' (vd 'Nguyên lý: Principles', 'Trụ cột: Marketing', 'Nền tảng: AI' — ADR-31) · tiêu đề {typography.display-lg} · term_en · dòng thông tin (+ Trước/Tiếp gọn ở cuối dòng từ 1200px)", maxWidth: "CÙNG độ rộng với thân bài ở mọi khung — {reading.measure} đang chọn (máy tính chuột/trackpad mặc định desktop-wide 34.5em), căn trái ở đầu cột giữa; Aa đổi Hẹp/Vừa/Rộng thì đầu bài và thân bài đổi cùng nhau (tác giả 2026-10-19: thân bài tràn tới mép 'Bài tiếp')", title-wrap: "từ 1200px text-wrap: pretty (balance tách đôi cụm 'dữ / liệu' khi tiêu đề vừa quá một dòng); tiêu đề dài tự xuống dòng; dưới 1200px giữ balance", spacing: "từ 1200px: padding {spacing.md} trên, {spacing.lg} dưới; tab tầng cách dòng dưới {spacing.sm}; kicker và term_en cách tiêu đề {spacing.xxs}; dòng thông tin cách {spacing.sm}", first-screen: "đo 2026-10-19 (bản xem trước có dải 'Bản nháp' 40px): 1440×900 và 1568×759 — tiêu đề 'Hợp nhất dữ liệu khách hàng' một dòng, đỉnh câu định nghĩa ở 310–311px" }
  article-body:       { maxWidth: "{reading.measure} đang chọn (mặc định medium; chuột/trackpad từ 1200px desktop-wide), không quá bề rộng cột giữa", typography: "{typography.body-read}", text: "{colors.body}", headings: "{colors.ink}", images: "max-width 100%, khai width/height hoặc aspect-ratio, loading=lazy dưới màn đầu", tables: "bọc trong khung cuộn ngang riêng có viền; trang không bao giờ cuộn ngang", code: "khối code cuộn ngang trong khung, không xuống dòng" }
  from-practice-box:  { background: "{colors.surface-soft}", borderLeft: "3px {colors.ink}", padding: "{spacing.lg}", rounded: "{rounded.none}" }
  quote:              { typography: "{typography.definition}", borderLeft: "2px {colors.ink}", paddingLeft: "{spacing.md}" }
  toc-sidebar:        { width: 240px, typography: "{typography.body-ui}", text: "{colors.muted}", current: { text: "{colors.ink}", borderLeft: "2px {colors.ink}" }, align: "từ 1200px: chữ chủ đề, nhãn tầng, bài thẳng hàng chữ tên trụ (sau mũi tên 16px + 4px); vạch 2px ở mép trái cột", read-mark: "biểu tượng check + chữ ẩn 'đã đọc' cho trình đọc màn hình", notes: "dưới 1200px: cuối ngăn có 'Ghi chú của tôi'; từ 1200px: không — lối vào ở cụm công cụ đọc của top-nav", small-screen: "<1200px: ngăn kéo (dưới 768px toàn màn hình, 768–1199px rộng tối đa 400px) mở từ nút 'Mục lục' của reading-toolbar, nút đóng 44px góc trên, focus vào tiêu đề ngăn kéo khi mở; đầu ngăn có 'Trong bài này'" }
  in-article-toc:     { width: 220px, typography: "{typography.label}", current: { text: "{colors.ink}", fontWeight: 500, borderLeft: "2px {colors.ink}" }, align: "từ 1200px: tiêu đề 'Trong bài này' thẳng hàng chữ các mục", small-screen: "<1200px: không có khối trong thân bài; nút 'Trong bài' của reading-toolbar mở tấm trượt từ đáy chứa danh sách" }
  progress-bar:       { height: 3px, track: "{colors.surface-strong}", fill: "{colors.ink}", position: "từ 1200px: dính dưới top-nav; dưới 1200px: cố định ở mép trên (cộng env(safe-area-inset-top)), là thanh dính duy nhất ở mép trên", ariaHidden: true, reducedMotion: "không chuyển động mượt" }
  reading-toolbar:    { placement: "trang bài, dưới 1200px: cố định ở mép dưới; ẩn khi cuộn xuống, hiện khi cuộn lên hoặc khi tới cuối trang. Từ 1200px KHÔNG có thanh này: máy tính dùng cụm công cụ đọc của top-nav (Ghi chú · Cài đặt · Sao chép), mục lục và 'Trong bài này' là hai cột", items: "Mục lục · Trong bài · Aa (nhãn 'Cài đặt') · Sao chép — icon {icons.size.md} trên, nhãn ngắn {typography.label} dưới; mục 'Trong bài' chỉ có khi bài có ≥ 2 tiêu đề ##", height: 52px, item: "chia đều bề rộng, mỗi nút ≥ 44×44px, aria-label đầy đủ ('Cài đặt đọc', 'Sao chép bài (Markdown)')", background: "{colors.canvas}", borderTop: "1px {colors.hairline}", paddingBottom: "env(safe-area-inset-bottom)", copy-status: "lỗi chép (UC-108 E1) hiện một dòng nền {colors.ink} ngay trên thanh, 2 giây", no-js: "chỉ còn 'Mục lục' và 'Trong bài' là link tới khối mục lục cuối trang", reason: "tác giả chọn 2026-10-18 'Đọc trước': màn đầu điện thoại (kể cả 320×568) thấy tiêu đề, câu định nghĩa và đoạn mở đầu" }
  reading-settings:   { background: "{colors.canvas}", border: "1px {colors.hairline}", rounded: "{rounded.md}", padding: "{spacing.md}", segmented: { minHeight: 44px, active: { background: "{colors.ink}", text: "{colors.on-primary}" } }, small-screen: "<1200px (mở từ reading-toolbar): tấm trượt từ đáy (bottom sheet) có padding-bottom env(safe-area-inset-bottom); từ 1200px: khung nổi dưới nút Aa của top-nav" }
  prev-next:          { borderTop: "1px {colors.hairline}", label: "{typography.label}", title: "{typography.heading-h3}", padding: "{spacing.xl} 0", small-screen: "<375px: hai khối xếp dọc, mỗi khối rộng 100%", compact: "dòng gọn '← Bài trước · Bài tiếp →' ở đầu bài, {typography.label}: từ 1200px nằm cuối dòng thông tin, hai vế CẠNH NHAU trên một hàng (cách {spacing.md}, không tách dòng giữa hai vế), mép phải 'Bài tiếp →' = mép phải thân bài; dòng thông tin dài quá thì cả cụm xuống hàng, vẫn căn phải; dưới 1200px là dòng riêng ngay dưới dòng thông tin; cuối bài giữ khối đầy đủ ở trên" }
  gate-invite:        { background: "{colors.surface-soft}", border: "1px {colors.hairline}", rounded: "{rounded.md}", padding: "clamp(16px, 4vw, 32px)" }
  email-box:          { placement: "nằm yên trong dòng chảy bài: cuối bài sau nút ủng hộ (thứ tự ở end-of-post), và khối 'Nhận bài mới' trang chủ; KHÔNG bao giờ dính, nổi, trượt vào, popup khi cuộn", lead: "một câu mời ngắn {typography.body-ui} {colors.body} (vd 'Bài mới về thẳng hộp thư của bạn.'), không tiêu đề lớn", label: "nhãn 'Email' ẩn trực quan nhưng có cho trình đọc màn hình (câu mời đã nói rõ); KHÔNG chỉ dựa placeholder", field: { layout: "một hàng: ô nhập chiếm hết, nút chữ 'Gửi →' nằm trong đầu phải của ô", border: "chỉ gạch chân 1px {colors.ink} (kiểu dòng kẻ giấy), không khung, không nền", input: { type: email, autocomplete: email, inputmode: email, fontSize: 16px, minHeight: 44px, background: transparent }, submit: { style: "chữ {typography.button} {colors.ink}, không nền, không viền", hitArea: "≥ 44×44px", loading: "chữ đổi 'Đang gửi…'", done: "thay cả hàng bằng câu 'Đã nhận, hãy kiểm tra hộp thư.' (UC-201 AC-7)" } }, focus: "gạch chân dày 2px + vòng focus", consent: "{typography.label} {colors.muted}, một dòng dưới ô: 'Bạn sẽ nhận bài mới qua email, huỷ được bất cứ lúc nào. Chính sách quyền riêng tư' (UC-201 bước 1)", google: "KHÔNG có nút/link 'Tiếp tục với Google' (reader UC-201 AC-16; tác giả 2026-10-20 chọn 'Bỏ khỏi ô Nhận bài mới; chỉ giữ ở form bình luận + khối mở Hướng dẫn chi tiết') — đăng nhập Google ở comment-form và gate-invite", error: "ngay dưới ô, role=alert, nêu nguyên nhân + cách sửa", small-screen: "giữ một hàng kể cả 320px (nút chữ ngắn 'Gửi →'); không xếp dọc, không nút rộng 100%", avoid: "không nút đen tô kín, không khối nền đậm, không chiếm quá ~3 dòng chiều cao" }
  donate-button:      { role: "nút ☕ 'Mời tác giả một ly cà phê' cuối bài (payments UC-302) và trên /support/", style: "{component.button-primary} — biểu tượng Lucide coffee {icons.size.md} trước chữ (không emoji), cách chữ {spacing.xs}", width: "theo chữ, căn trái thẳng mép thân bài; không rộng 100%" }
  comment-form:       { role: "khối 'Câu hỏi & bình luận' cuối bài (reader UC-205)", frame: "viền 1px {colors.hairline}, {rounded.md}, padding {spacing.md}, nền {colors.canvas}; tiêu đề {typography.heading-h3} {colors.ink}", fields: "ô tên, ô email, ô nội dung: viền 1px {colors.hairline}, {rounded.sm}, padding {spacing.xs} {spacing.sm}, chữ {fonts.sans} 16px (touch.input-font-min), cao ≥ touch.target-min; ô tên + email một hàng từ {breakpoints.md}, hẹp hơn xếp dọc, cách {spacing.xs}", optin: "ô đánh dấu 'Gửi mình bài mới qua email' {typography.label} {colors.body}, vùng bấm ≥ touch.target-min, mặc định trống", google: "chỉ khi chưa có phiên: {component.button-secondary} 'Tiếp tục với Google' ngay dưới ô đánh dấu, trong cùng phần ẩn khi có phiên; cách phần trên {spacing.sm}; bấm mang theo trạng thái ô đánh dấu (reader UC-203 5a)", kind: "radio 'Bình luận' · 'Câu hỏi' {typography.label}, vùng bấm ≥ touch.target-min", submit: "{component.button-secondary} 'Gửi bình luận' — form nhẹ, không nút đen tô kín", consent: "{typography.label} {colors.muted} ngay dưới nút gửi, link chính sách gạch chân", status: "{typography.label}; thành công chữ {colors.success} kèm '✓', lỗi chữ {colors.error} kèm 'Lỗi:'; nền {colors.surface-soft}, viền 1px {colors.hairline}, {rounded.sm}" }
  end-of-post:        { role: "thứ tự các khối sau thân bài (publishing UC-102 bước 12, AC-41; tác giả 2026-10-20 chọn 'Đưa lên trước ô email (ngay sau Bài trước/Bài tiếp)')", order: "prev-next (khối đầy đủ) → donate-button → email-box → comment-form; bài Triển khai có gate: prev-next → donate-button → comment-form (không email-box)", spacing: "mỗi khối cách khối trước {spacing.xl} (prev-next đã có padding dưới {spacing.xl} nên nút ủng hộ không thêm lề trên); KHÔNG khối nào dính khối nào; khoảng cuối trang trước chân trang {spacing.section}" }
  highlight-mark:     { background: "{colors.highlight}", borderBottom: "1px {colors.ink}" }
  selection-popover:  { background: "{colors.ink}", text: "{colors.on-primary}", typography: "{typography.label}", rounded: "{rounded.sm}", buttonMinHeight: 44px, position: "dưới đoạn chọn (iOS/Android đặt menu hệ thống phía trên); không che con trỏ kéo chọn" }
  share-sheet:        { role: "bảng chia sẻ ảnh (publishing UC-115): mở từ nút 'Chia sẻ' ở cuối cụm công cụ đọc (top-nav từ 1200px: Ghi chú · Cài đặt · Sao chép · Chia sẻ; reading-toolbar: Mục lục · Trong bài · Cài đặt · Sao chép · Chia sẻ) hoặc 'Chia sẻ câu này' của selection-popover", frame: "dùng chung .sheet: <1200px tấm trượt từ đáy, từ 1200px khung 400px dưới top-nav; bẫy focus, Esc/nút đóng 44px, focus về nút mở", preview: "ảnh vuông 1:1 — hình vuông lớn nhất vừa khung cao min(46dvh, 360px) và bề ngang tấm — trên nền {colors.surface-soft}, viền 1px {colors.hairline}; giữ chỗ khi đang vẽ", card: "thẻ bài và ảnh trích dẫn CÙNG khổ vuông 1080×1080 (tác giả 2026-10-20: 'cái ảnh này tôi muốn hình vuông nhé'); lề 96; trên cùng logo 'mza' ({logo.wordmark}, vẽ từ path của file, cỡ 56); dưới cùng đường kẻ 2px màu rule + link ngắn {fonts.sans} 500 32 (≤ 2 dòng, chỉ xuống dòng sau '/'); thẻ bài: nhãn {fonts.sans} 500 30 màu muted (≤ 2 dòng) · tiêu đề {fonts.serif} 600 co 88 → 52 (≤ 5 dòng; co tới khi summary còn ≥ 2 dòng) · summary {fonts.serif} 400 nghiêng 36 màu body (≤ 3 dòng, rút dòng trước khi tràn); ảnh trích dẫn: đoạn {fonts.serif} 400 co 64 → 34, không cắt · vạch 64×3 màu ink · tên bài {fonts.serif} 600 36 (≤ 2 dòng) · dòng nhóm {fonts.sans} 500 30 muted; ảnh xem trước khi dán link vẫn 1200×630 (logo cỡ 44)", backgrounds: "5 ô tròn 40px tô bằng share-backgrounds (chữ 'Aa'), nhãn {typography.label} dưới; ô chọn có vòng 2px {colors.ink}", actions: "button-primary 'Tải ảnh' + button-secondary 'Sao chép link' · 'Chia sẻ…' (chỉ khi trình duyệt chia sẻ được tệp), xuống hàng ở 320px", status: "{typography.label} {colors.ink}, 2 giây" }
  notes-panel:        { background: "{colors.canvas}", borderLeft: "1px {colors.hairline}", width: "min(360px, 100vw)", small-screen: "<768px: toàn màn hình" }
  result-card:        { border: "1px {colors.ink}", rounded: "{rounded.md}", padding: "{spacing.lg}", number: "{typography.display-md} + {typography.numbers}" }
  donation-popup:     { background: "{colors.canvas}", border: "1px {colors.ink}", rounded: "{rounded.md}", padding: "{spacing.xl}", scrim: "{colors.scrim}", qr: "≥ 200px, nền trắng thuần kể cả ở nền Tối (để app ngân hàng quét được)", small-screen: "<768px hoặc short-height: toàn màn hình, cuộn bên trong, nút đóng luôn thấy" }
  offline-banner:     { background: "{colors.surface-soft}", border: "1px {colors.hairline}", typography: "{typography.label}", role: status }
  cookie-notice:      { role: "ngoại lệ có tên DUY NHẤT của 'không popup' (ADR-30, reader UC-209): thông báo, không xin đồng ý — chỉ câu chữ + link + nút ×; không nút Đồng ý/Từ chối", background: "{colors.ink}", text: "{colors.on-primary}", link: "gạch chân, cùng màu chữ {colors.on-primary}", typography: "{typography.label} (13px), lineHeight 1.4 — gọn, không lấn chữ bài (tác giả 2026-10-19: 'to quá… cần bé lại')", rounded: "{rounded.md}", padding: "{spacing.xxs} {spacing.xxs} {spacing.xxs} {spacing.sm}", close: "nút × hiển thị 32px, vùng bấm vẫn 44×44px (lớp ::after vô hình), icon {icons.size.sm}, aria-label 'Đóng thông báo', nền trong suốt cùng màu chữ, vòng focus màu {colors.on-primary}", placement: "≥768px: góc dưới trái, cách mép trái/dưới {spacing.md} + env(safe-area-inset-*), rộng tối đa 320px; <768px: sát đáy, cách mép trái/phải/dưới {spacing.xs} + safe-area, rộng hết khung; trang bài <1200px: NẰM TRÊN reading-toolbar khi thanh đang hiện (không chồng lên), thanh ẩn thì hạ xuống sát đáy, cùng nhịp {motion.duration.base}; ngăn kéo mục lục đang mở thì ẩn tạm", space: "khi hộp đang hiện, cuối trang chừa một khoảng bằng chiều cao hộp + {spacing.md} để chân trang cuộn ra được trên hộp", z-index: "{z-index.notice}", motion: "vào/ra bằng opacity + translateY(8px) {motion.duration.base}; prefers-reduced-motion → hiện/ẩn ngay", a11y: "<section aria-label='Thông báo cookie'> (vùng có tên, landmark region); KHÔNG lấy focus khi hiện, không bẫy focus, không scrim, không khoá cuộn; đóng bằng × đang có focus → focus về <main>", themes: "nền Tối: token đảo nên hộp sáng ({colors.ink} sáng), chữ tối ({colors.on-primary} tối); Sáng/Giấy: hộp đen chữ trắng", no-js: "không render hiển thị (hidden) — không nhớ được đã đóng" }
  continue-reading:   { placement: "trang chủ: dòng ĐẦU TIÊN trong nội dung trang (trong {reading.container} từ 1200px, lề gutter dưới), mép trái thẳng dòng giới thiệu — KHÔNG phải thanh riêng giữa top-nav và trang (tác giả 2026-10-19: 'sao phần đọc tiếp này vẫn còn ở đây nhỉ ?? -> fix lại toàn bộ nhé', chọn 'Giữ nhưng đưa vào khung, nhẹ hơn')", typography: "{typography.label}, chữ {colors.muted}; chỉ tên bài là link {component.text-link} màu {colors.ink}", background: "không nền, không viền, không đường kẻ", close: "nút × cuối dòng: icon {icons.size.sm} {colors.muted}, vùng bấm touch.target-min (margin âm theo chiều dọc để dòng vẫn mảnh), aria-label 'Ẩn dòng “Đọc tiếp”'; bấm → ẩn tới khi bài gần nhất đổi (reader UC-210 4b), focus về nội dung", spacing: "cách dòng giới thiệu {spacing.md}", no-js: "tắt JS hoặc chặn lưu: không hiện" }
  home-layout:        { role: "bố cục trang chủ (publishing UC-103 Main Flow, AC-38, AC-39; tác giả 2026-11-03 'ủa sao bên trang chủ này tôi thấy nó có 1/3 trống ?', chọn 'Hai cột: trái nội dung chính, phải cột phụ')", small-screen: "< 1200px: một cột theo thứ tự UC-103 bước 1–7, như cũ", frame: "từ 1200px: dòng 'Đọc tiếp', tagline, 'Bạn đang gặp chuyện gì?' trải hết {reading.container}; dưới đó lưới hai cột trong {reading.container} — mép trái = mép trái logo, mép phải = mép phải nút Aa của top-nav", columns: "chính 2fr · phụ 1fr (token --home-columns), cách nhau {reading.container} column-gap (clamp(40px, 3.5vw, 56px)); hai cột cùng đỉnh", main: "cột trái: 'Bắt đầu từ đây' · 'Kết quả thật' · 'Bài mới' · 'Tiếng nói người đọc' · ô 'Nhận bài mới' (ô email ở cột phải lọt vào màn đầu 1440×900 — trái luật màn đầu không có ô email, nên ở lại cột trái)", side: "cột phải: 'Xem theo chủ đề' (nhãn nhóm ở trên chip) · dòng lối rẽ 'Đội kỹ thuật? …'; KHÔNG dính khi cuộn", measure: "câu tóm tắt, tiêu đề bài trong cột trái không rộng quá {reading.measure}; thẻ, danh sách, ô email rộng hết cột", order: "chỉ xếp bằng grid-template-areas; thứ tự HTML (trình đọc màn hình, phím Tab) luôn là thứ tự một cột", gaps: "không row-gap: khoảng cách là margin của từng khối, nên khối tải lúc xem chưa có ('Đọc tiếp', 'Tiếng nói người đọc') không để khung trống hay khoảng hở" }
  footer:             { structure: "hai hàng, từ trên xuống (publishing UC-103 bước 8; tác giả 2026-10-19 'footer cũng nên làm lại sao cho chi tiết nhé', theo mẫu blog Toàn Bùi; v19 bỏ hàng 'Theo dõi bài mới' — tác giả 2026-10-19 'cái RSS tôi nghĩ nên cho nhỏ lại nè, bỏ phần đó đi cũng được, phần RSS cuối cùng thì thêm icon của nó vào'): (1) cột thương hiệu (wordmark mza — file SVG {logo.wordmark}, cỡ 20px + câu tagline {typography.body-ui} {colors.body}, rộng ≤ 28rem; tagline trống thì chỉ wordmark) · cột 'Chủ đề' · 'Đọc' · 'Về mza' (mục đầu 'Design system' → /design-system/, UC-114 — tên tiếng Anh nguyên gốc, tác giả 2026-10-19 'để đúng từ nguyên gốc của nó là Design system… để lên trên chỗ về mza'; mục cuối 'Liên hệ' → /about/#contact, khối contact-list — UC-103 AC-35); (2) dòng cuối {typography.label} {colors.muted}: trái '© <năm> mza.vn', phải Chính sách quyền riêng tư · RSS (icon Lucide rss {icons.size.sm} trước chữ, aria-hidden, căn theo đường chân chữ) · llms.txt; KHÔNG nút RSS, KHÔNG ô email", background: "{colors.surface-soft} — KHÔNG đảo màu nền {colors.ink}: ở nền Tối ink là màu sáng nên khối 'tối như mẫu' thành khối sáng chói cuối trang; surface-soft là một bậc lệch nhẹ khỏi canvas ở cả Sáng/Giấy/Tối, tách chân trang mà vẫn hài hoà (tương phản đo 2026-10-19 trên surface-soft: muted 4.9 · 4.8 · 6.2, body 13.1 · 9.6 · 11.6)", borderTop: "1px {colors.hairline}; giữa các hàng cũng kẻ 1px {colors.hairline}; nền và đường kẻ trải hết bề ngang", column-title: "{typography.label} 500 {colors.muted}, letterSpacing 0.02em — KHÔNG viết hoa toàn bộ (text.emphasis)", link: "{typography.body-ui} {colors.body}, không gạch chân, rê/nhấn → {colors.ink} + gạch chân; cao ≥ touch.target-min (44px; chuột/trackpad từ 1200px 32px); dòng cuối link {colors.muted}", grid: "< 480px: 1 cột · 480–1199px: 2 cột (thương hiệu + Chủ đề / Đọc + Về mza) · ≥ 1200px: thương hiệu 2fr + 3 cột 1fr, cách {reading.container} column-gap", frame: "từ 1200px nội dung mọi hàng trong {reading.container} (mép trái = mép trái logo, mép phải = mép phải cụm hành động top-nav); dưới 1200px lề gutter", spacing: "hàng 1 padding dọc {spacing.xl}; dòng cuối {spacing.xs} trên, max({spacing.md}, env(safe-area-inset-bottom)) dưới", no: "không icon mạng xã hội ở chân trang (kênh Email · Facebook · Zalo có biểu tượng chỉ ở contact-list của trang Giới thiệu và Portfolio; chân trang chỉ có link chữ 'Liên hệ'), không địa chỉ email hay link mạng xã hội viết trong chân trang, không tên người", cookie-room: "khoảng chừa cho thông báo cookie là khối riêng ngay sau chân trang (UC-209 bước 8), không đổi" }
  admin-shell:        { role: "khung mọi màn /admin/ (ADR-39, ADR-32, analytics UC-404, UC-406): dashboard có leftbar, chỉ tác giả thấy (sau đăng nhập quản trị), không lập chỉ mục, không top-nav/chân trang công khai; tối giản đen–trắng, không màu, không bóng đổ (tác giả 2026-10-22: 'làm lại theo đúng dạng dashboard bên trái nhé, không để từng cục thế')", layout: "từ {breakpoints.md} (768px): lưới hai cột — leftbar 240px (bằng toc-sidebar.width) + vùng nội dung; leftbar dính (sticky top 0, cao 100dvh, cuộn bên trong khi dài), borderRight 1px {colors.hairline}, nền {colors.canvas}. Dưới 768px: một cột, leftbar thu vào nút ☰ (trạng thái thu gọn duy nhất — màn rộng KHÔNG có leftbar chỉ-icon)", leftbar: "padding {spacing.lg} {spacing.sm}; trên cùng: wordmark {logo.wordmark} 20px {colors.ink} + chữ 'Quản trị' {typography.label} {colors.muted}, link về /admin/; nhóm: tiêu đề nhóm chữ hoa (ĐO LƯỜNG · NGƯỜI ĐỌC · BẢN TIN · ỦNG HỘ) {typography.label} 500 {colors.muted} letterSpacing 0.04em, cách nhóm trên {spacing.lg}; cuối cột: email đang đăng nhập {typography.label} {colors.muted} (overflow-wrap anywhere) + link 'Xem site' {component.text-link}", item: "link cả dòng, flex: tên trái, badge phải; {typography.body-ui} {colors.body}; cao ≥ touch.target-min (chuột/trackpad từ 1200px: 32px), padding 0 {spacing.sm}, {rounded.sm}; rê chuột nền {colors.surface-soft}; focus-visible vòng {colors.focus-ring} 2px + offset 2px", current: "mục của màn đang mở: aria-current='page', nền {colors.ink}, chữ {colors.on-primary} 500 (cùng kiểu tab đang chọn); màn con đánh dấu mục cha", badge: "số việc chờ: viên {rounded.pill}, cao 20px, minWidth 20px, padding 0 6px, {typography.label} 600 tabular-nums (không nhỏ hơn 13px), nền {colors.ink} chữ {colors.on-primary}; ở mục đang chọn đảo lại nền {colors.canvas} chữ {colors.ink}; số 0 không hiện; kèm chữ ẩn ' đang chờ' cho trình đọc màn hình; trên 99 hiện '99+'", phone: "dưới 768px: thanh trên 56px (borderBottom 1px {colors.hairline}, paddingTop env(safe-area-inset-top)): nút ☰ icon {icons.size.md} vùng bấm 44×44px aria-label 'Mở menu quản trị' + aria-expanded, cạnh đó wordmark 20px + 'Quản trị'; ☰ mở ngăn kéo từ trái rộng min(300px, 100vw − 56px) nền {colors.canvas} borderRight 1px {colors.hairline} trên scrim {colors.scrim}, z {z-index.drawer}; nội dung ngăn = leftbar (cả badge) + nút đóng × 44px góc trên; bẫy focus, Esc/nút đóng/bấm scrim đóng và trả focus về ☰; tắt JS thì ☰ là link tới #admin-nav và ngăn mở bằng :target; không cuộn ngang ở 320px; reduced-motion: không trượt", dev-note: "chỉ khi ENV=development + ADMIN_DEV_EMAIL: dải {colors.surface-soft} {typography.label} trên đầu vùng nội dung, báo 'Chế độ máy cục bộ' — đang bỏ qua đăng nhập quản trị (analytics UC-406 5a)", main: "vùng nội dung: padding {spacing.xl} {reading.gutter} {spacing.section}, rộng tối đa 72rem, CĂN GIỮA phần còn lại bên phải leftbar (màn rất rộng chỉ lề hai bên lớn ra, không dồn trái để trống bên phải — tác giả 2026-10-22: 'sao k làm full trang mà lại kéo trái, bên phải trống như này, nếu thế thì content cho vào giữa chứ nhỉ ?'); tiêu đề trang {typography.display-md}, câu dẫn {colors.muted}; bảng theo text.tables (khung cuộn ngang riêng, trang không cuộn ngang)", period: "chọn kỳ 7 · 30 · 90 ngày: nhóm tab {typography.label} 500 cao touch.target-min, tab đang chọn nền {colors.ink} chữ {colors.on-primary}; dưới đó dòng 'Số liệu tới hết ngày …' {typography.label} {colors.ink}; gom trễ → khung viền 1px {colors.ink}", queue: "'Việc chờ bạn' ở đầu Tổng quan: lưới tự chia cột (tối thiểu 200px, tối đa 4 cột), mỗi ô là một link cả khối {rounded.md}: viền 1px {colors.ink} khi có việc, {colors.hairline} khi không; padding {spacing.md}; tên {typography.label} {colors.muted}, số {typography.heading-h2} 600 tabular-nums {colors.ink}, dòng phụ {typography.label}", metric-card: "thẻ số đo: viền 1px {colors.hairline}, {rounded.md}, padding {spacing.md}; tên nhóm {typography.heading-h3} serif; mỗi số: nhãn {typography.label} {colors.muted} · giá trị {fonts.sans} {typography.heading-h2} 600 tabular-nums {colors.ink} · dòng so kỳ trước {typography.label}: mũi tên chữ ↑/↓/→ (không emoji, không màu xanh/đỏ) + '20 %' hoặc '3 điểm' {colors.ink} 500 + 'so với <kỳ trước>' {colors.muted}; chưa có số → '—'; lưới thẻ tự chia cột (tối thiểu min(280px, 100%))", chart: "biểu đồ cột SVG dựng ở máy chủ (không thư viện biểu đồ, không JS): cột {colors.ink}, trục 1px {colors.hairline}, cao 8rem, ngày chưa gom bỏ trống cột; aria-hidden, cạnh đó luôn có bảng số thay thế trong khối gập 'Số theo từng ngày'; lưới biểu đồ minmax(min(320px, 100%), 1fr) để không tràn ở 320px", list: "bảng xếp hạng (Top bài, Top nguồn, phễu): bảng quản trị thường, cột số căn phải; thanh tỉ lệ mảnh 4px nền {colors.surface-strong}, phần đầy {colors.ink} (trang trí, aria-hidden — số % luôn có chữ)" }

images:
  # Hành vi tạo bản nén theo publishing UC-107; ở đây là cách hiển thị.
  formats: "AVIF → WebP → ảnh gốc (thẻ <picture>); SVG giữ vector"
  max-width-px: 1200            # UC-107 (còn [OPEN] tác giả xác nhận); đủ nét cho cột chữ trên máy thường, hơi mềm trên màn 2x máy tính
  display: "rộng tối đa bằng cột chữ (không tràn lề); ảnh dọc cao tối đa 80vh"
  sizes-attr: "(min-width: 1200px) 66ch, calc(100vw - 40px)"
  layout-shift: "khai width/height thật hoặc aspect-ratio; nền chờ {colors.surface-soft}"
  loading: "ảnh đầu bài (nếu có) eager + fetchpriority=high; mọi ảnh khác loading=lazy decoding=async"
  alt: "bắt buộc với ảnh mang thông tin, viết tiếng Việt, nói ảnh cho thấy điều gì (không 'ảnh chụp màn hình'); ảnh trang trí alt rỗng"
  caption: "<figcaption> dưới ảnh, {typography.label} {colors.muted}, ≤ 2 dòng; nguồn ảnh (nếu có) ở cuối chú thích"
  screenshots: "đã ẩn danh (tên, số điện thoại, email, số tiền thật của khách); cắt sát vùng cần xem; chữ trong ảnh khi hiển thị trên điện thoại không nhỏ hơn ~11px — nhỏ hơn thì cắt/phóng vùng chính hoặc thêm ảnh phóng to"
  dark-theme: "ảnh chụp không đảo màu; có viền 1px {colors.hairline} và bo {rounded.sm} để tách nền; giảm chói: filter brightness(0.92) chỉ ở nền Tối"
  zoom: "bấm vào ảnh mở bản lớn nhất trong lớp phủ (nền scrim, Esc/nút đóng 44px); điện thoại cho phóng bằng hai ngón"
  video: "YouTube chỉ tải khi người đọc bấm (ảnh bìa + nút phát), tỉ lệ 16:9 giữ chỗ, đúng privacy.md"

illustrations:
  style: "nét đen trên nền trong suốt, kiểu bản vẽ sách kỹ thuật; không màu, không gradient, không bóng"
  format: "SVG nội tuyến hoặc file .svg; màu nét/chữ dùng currentColor để tự đúng ở Sáng/Giấy/Tối"
  stroke: "1.5px (chi tiết), 2px (đường chính); đầu mũi tên đặc, nhỏ"
  fills: "chỉ {colors.surface-soft}/{colors.surface-strong} qua biến CSS hoặc hoạ tiết sọc/chấm để phân nhóm — không dùng màu để mang nghĩa"
  text: "chữ trong sơ đồ là chữ thật (không vẽ thành đường), font {fonts.sans}, ≥ 13px khi hiển thị ở 320px; tiếng Việt có dấu"
  width: "thiết kế sơ đồ đọc được ở 320px; sơ đồ rộng → bản dọc riêng cho điện thoại (<picture media>) hoặc khung cuộn ngang có gợi ý 'Vuốt ngang để xem hết'"
  accessible: "alt ngắn + khối gập 'Mô tả sơ đồ' bằng chữ cho sơ đồ phức tạp (luồng, bảng so sánh)"
  numbering: "không đánh số Hình 1, 2 (bài đọc riêng vẫn trọn vẹn); dẫn bằng chú thích"

text:
  # Quy ước chữ tiếng Việt trong bài — engine hiển thị đúng, người viết theo.
  align: "căn trái, KHÔNG căn đều hai bên (justify tạo khoảng trắng lỗ chỗ, khó đọc với người khó đọc)"
  hyphens: "none — không ngắt từ tiếng Việt"
  wrap: "text-wrap: pretty cho đoạn văn, text-wrap: balance cho tiêu đề — trừ tiêu đề bài từ 1200px dùng pretty (article-header) (trình duyệt không hỗ trợ thì bỏ qua); overflow-wrap: anywhere cho URL và mã dài"
  quotes: "ngoặc kép cong “…” và ‘…’ (engine đổi tự động từ \"...\")"
  dashes: "gạch nối giữa câu dùng – có khoảng trắng hai bên; khoảng số dùng – không cách (2020–2026)"
  numbers: "dấu chấm hàng nghìn, dấu phẩy thập phân: 1.500 · 2,5 · 200.000 đ; tháng/năm 10/2026; khoảng trắng không ngắt giữa số và đơn vị (15 %, 6 phút, 200.000 đ)"
  emphasis: "đậm cho từ khoá (≤ 1–2 chỗ mỗi đoạn); nghiêng cho câu định nghĩa và thuật ngữ tiếng Anh; không gạch chân ngoài link; không viết hoa toàn bộ"
  lists: "khoảng cách giữa mục 0.4em; danh sách lồng tối đa 2 cấp"
  tables: "đầu bảng {typography.label} đậm; số căn phải + tabular-nums + không xuống dòng (white-space: nowrap; đầu cột số cũng căn phải); dưới 768px bảng rộng cuộn ngang trong khung, cột đầu dính"
  links: "chữ link nói đích đến ('đọc hồ sơ triển khai'), không 'bấm vào đây'; link ra ngoài có biểu tượng ↗ nhỏ + chữ ẩn 'mở trang ngoài'"
  footnotes: "không dùng chú thích cuối trang; đưa vào ngoặc hoặc ô phụ"

reading-standard:
  # Tiêu chuẩn đo được cho trải nghiệm đọc. Kiểm khi build/QA; trượt = lỗi cần sửa trước khi đăng.
  wcag: "WCAG 2.2 mức AA toàn trang; thân bài đạt AAA về tương phản (≥ 7:1)"
  reflow: "320px không cuộn ngang, không mất nội dung (WCAG 1.4.10)"
  resize: "phóng chữ 200% không mất chữ, không chồng lấn (WCAG 1.4.4)"
  text-spacing: "người đọc tự tăng giãn dòng 1.5, giãn đoạn 2×, giãn chữ 0.12em, giãn từ 0.16em vẫn không vỡ (WCAG 1.4.12)"
  target-size: "≥ 44×44px với cảm ứng và mọi màn dưới 1200px; chuột/trackpad từ 1200px ≥ 32px (touch.target-min.fine) — cả hai vượt mức AA 24px của WCAG 2.5.8"
  measure: "45–75 ký tự mỗi dòng ở mọi nấc cỡ chữ và độ rộng"
  line-height: "thân bài 1.6–1.8; tiêu đề 1.15–1.35"
  contrast-ui: "chữ ≥ 4.5:1, viền điều khiển và biểu tượng ≥ 3:1 ở cả ba nền"
  lighthouse-mobile: "Accessibility 100, Best Practices ≥ 95, SEO 100, Performance ≥ 90 (trang chủ, trang bài dài nhất)"
  automated: "axe-core 0 lỗi nghiêm trọng ở mọi mẫu trang"
  manual: "đọc trọn một bài bằng VoiceOver (iOS) và TalkBack; điều hướng toàn bộ bằng bàn phím"

compatibility:
  # Mốc hỗ trợ đầy đủ (mọi tính năng nâng cấp dần). Dưới mốc: vẫn ĐỌC được trọn bài và mục lục (HTML tĩnh), chỉ mất tính năng JS.
  ios-safari: "15.4+"        # gồm iPhone 6s/7/SE 1 (dừng ở iOS 15.8); iPhone 8/X (iOS 16)
  chrome-android: "100+"
  samsung-internet: "18+"
  desktop: "Chrome/Edge 100+, Firefox 115 ESR+, Safari 15.4+"
  css-allowed: "custom properties, grid/flex gap, clamp(), aspect-ratio, env(safe-area-inset-*), :focus-visible, dvh (có dự phòng vh), <dialog>, @supports"
  css-avoid-or-guard: "CSS nesting, :has() làm bố cục chính, container queries, color-mix(), @layer chưa cần, view transitions — chỉ dùng sau @supports và phải có dự phòng"
  js: "ES2020; không dùng cú pháp cần trình duyệt mới hơn mốc; mỗi tính năng JS hỏng thì trang vẫn đọc được"
  budgets:
    js-article: "≤ 30 KB gzip toàn bộ JS của trang bài (đo khi build)"
    fonts-first-view: "chỉ font thân bài preload; tổng font tải ở màn đầu ≤ 150 KB (mục tiêu, đo khi build)"
    lcp: "≤ 2.5s trên 4G chậm mô phỏng, máy tầm thấp (Lighthouse mobile)"
    cls: "< 0.1"

pwa:
  manifest: { name: "mza.vn", short_name: "mza", lang: vi, start_url: "/?utm_source=pwa", display: standalone, background_color: "{themes.light.canvas}", theme_color: "{themes.light.meta-theme-color}" }
  icons: "192, 512, 512 maskable (PNG), apple-touch-icon 180; chữ 'm' đơn của logo ({logo.favicon}) đen trên trắng, chừa vùng an toàn maskable"
  meta: "viewport width=device-width, initial-scale=1, viewport-fit=cover (KHÔNG chặn phóng to); theme-color 3 thẻ theo prefers-color-scheme + cập nhật khi người đọc đổi nền; apple-mobile-web-app-status-bar-style=default"
  standalone: "@media (display-mode: standalone): không có nút Back của trình duyệt → top-nav hiện nút '←' (quay lại) và nút 'Sao chép link bài' (không có thanh địa chỉ để chép)"
  safe-area: "mọi thanh dính, ngăn kéo, bottom sheet, popup cộng env(safe-area-inset-*)"
  offline: "giao diện cho trạng thái mất mạng có sẵn (offline-banner, trang 'Bạn đang ngoại tuyến'); HÀNH VI ngoại tuyến (lưu bài nào, bao lâu) chưa có trong spec — phải sửa spec trước khi làm"
---

## Tổng quan

Blog đọc như một cuốn sách in tốt: đen – trắng, chữ có chân, dòng vừa tầm mắt, nhiều lề. Không ảnh trang trí, không gradient, không bóng đổ. Thứ duy nhất "nổi" là chữ. CEO mở ra phải thấy nghiêm túc, chậm, tin được; người kỹ thuật thấy gọn, đọc lâu không mỏi.

Màu thương hiệu là **mực đen** (`{colors.ink}`). Nút chính đen chữ trắng; nút phụ viền đen; link gạch chân. Tím (`{colors.accent-alt}`) chỉ là dự phòng (ADR-29).

`DESIGN.md` quy định **trông thế nào**; `specs/wireframe.html` quy định **có gì, ở đâu**; hành vi theo spec (UC/AC). Phạm vi: mọi trang công khai và màn quản trị (dùng chung token).

## Màu

- Ba nền đọc (UC-113): **Theo máy** (mặc định) · **Sáng** · **Giấy** · **Tối**. Đổi nền chỉ đổi biến trong `themes`.
- Không dùng màu để mang nghĩa một mình: trạng thái luôn có chữ hoặc ký hiệu (✓ kèm chữ ẩn "đã đọc", "Lỗi: …").
- `muted-soft` không dùng cho chữ. Chữ nhỏ nhất dùng `muted` (≥ 5:1 ở cả ba nền).
- Nền Tối không đảo màu ảnh; ảnh chụp màn hình giữ nguyên, có viền `hairline` để tách nền. Mã QR luôn trên nền trắng.

## Chữ

- **Literata** cho tiêu đề và thân bài; **Be Vietnam Pro** cho giao diện; **JetBrains Mono** cho code. Tự host, bản tĩnh, cắt Latin + Vietnamese.
- Cỡ chữ theo `rem`: người đọc phóng chữ trong cài đặt trình duyệt/hệ thống thì cả trang phóng theo; bố cục không vỡ ở 200%.
- Thân bài: 18px trên điện thoại, 19px trên máy tính bảng và màn cảm ứng rộng, 18px trên máy tính dùng chuột/trackpad từ 1200px (nấc 3; màn MacBook đã to sẵn — tác giả 2026-10-19), nút Aa có 5 nấc. Không chữ nào dưới 13px.
- Tiêu đề tối đa 600; không viết hoa toàn bộ chữ tiếng Việt.
- Kiểm dấu chồng (ặ, ỗ, ữ, ỹ, Ợ) ở mọi cỡ, cả ba nền, trước khi đổi font hay cỡ.

## Bố cục

- **≥ 1200px:** một khung chung (`reading.container`, ≈ 1200–1240px, căn giữa) cho top-nav, dải "Bản nháp", lưới trang bài và trang chủ: mép trái logo thẳng mép mục lục, mép phải cụm hành động thẳng mép "Trong bài này"; nền top-nav vẫn trải hết bề ngang; màn rất rộng chỉ lề ngoài lớn ra. Trang bài ba cột — mục lục 240px · cột giữa · "Trong bài này" 220px, cách nhau 40–56px; hai cột bên dính dưới top-nav. Đầu bài và thân bài **cùng một độ rộng** (`reading.measure`, máy tính chuột/trackpad mặc định 34.5em ≈ 62–74 ký tự), căn trái đầu cột giữa. Công cụ đọc là một cụm trên top-nav: Ghi chú · Cài đặt (Aa) · Sao chép (cùng thứ tự với reading-toolbar); "← Bài trước" và "Bài tiếp →" cạnh nhau ở cuối dòng thông tin, mép phải thẳng thân bài. Chuột/trackpad: mật độ gọn theo `touch.density-fine`.
- **Trang chủ ≥ 1200px** (`home-layout`): tagline và thẻ nỗi đau trải hết khung chung; dưới đó hai cột 2 : 1 cách nhau khoảng cột của khung — trái "Bắt đầu từ đây" · "Kết quả thật" · "Bài mới" · "Tiếng nói người đọc" · ô "Nhận bài mới"; phải (không dính) "Xem theo chủ đề" · dòng "Đội kỹ thuật?". Thứ tự HTML giữ thứ tự một cột; dưới 1200px một cột như cũ.
- **768–1199px:** một cột thân bài, lề 32px; top-nav logo · link trang (không dính); công cụ đọc ở **reading-toolbar** dưới cùng (Mục lục · Trong bài · Aa · Sao chép); mục lục là ngăn kéo; "Trong bài này" là tấm trượt từ đáy.
- **< 768px:** một cột, lề 20px (dưới 360px: 16px); top-nav chỉ logo · menu (44px, không dính); reading-toolbar như trên, mỗi nút 44px.
- Trang không bao giờ cuộn ngang ở 320px. Bảng và code cuộn ngang **trong khung riêng**.
- Màn đầu trang chủ không có ô email, không tên/ảnh tác giả (UC-103) — kể cả từ 1200px: ô "Nhận bài mới" ở cột trái, sau "Bài mới" (`home-layout`).

## Ảnh

- Ảnh chỉ dùng khi mang thông tin (sơ đồ, ảnh chụp màn hình, số liệu); không ảnh minh hoạ cho vui, không ảnh stock.
- Hiển thị qua `<picture>` (AVIF → WebP → gốc), rộng tối đa bằng cột chữ, khai kích thước để không giật trang.
- **Alt** tiếng Việt nói ảnh cho thấy điều gì; **chú thích** ngắn dưới ảnh, chữ phụ.
- **Ảnh chụp màn hình:** đã ẩn danh; cắt sát; chữ trong ảnh trên điện thoại phải đọc được — không được thì cắt vùng chính hoặc cho phóng to.
- **Nền Tối:** không đảo màu ảnh; thêm viền mảnh và giảm chói nhẹ.
- Bấm ảnh để xem lớn (lớp phủ, Esc/nút đóng); điện thoại phóng bằng hai ngón.
- Video YouTube chỉ tải khi bấm, giữ chỗ 16:9.

## Minh hoạ và sơ đồ

- Phong cách **nét đen, kiểu bản vẽ sách kỹ thuật**: không màu, không gradient, không bóng; nét 1.5–2px.
- **SVG dùng `currentColor`** nên tự đúng ở Sáng/Giấy/Tối; phân nhóm bằng nền xám nhạt hoặc hoạ tiết sọc/chấm, không bằng màu.
- **Chữ trong sơ đồ là chữ thật**, font giao diện, tiếng Việt có dấu, ≥ 13px khi xem ở 320px.
- Sơ đồ rộng: có bản dọc cho điện thoại, hoặc khung cuộn ngang có dòng "Vuốt ngang để xem hết".
- Sơ đồ phức tạp có khối gập **"Mô tả sơ đồ"** bằng chữ cho trình đọc màn hình và người đọc chậm.

## Chữ trong bài (quy ước tiếng Việt)

- Căn trái, không căn đều; không ngắt từ; tiêu đề cân dòng (tiêu đề bài từ 1200px: tránh chữ mồ côi, không cân dòng — để không tách đôi cụm như "dữ / liệu"), đoạn văn tránh chữ mồ côi (trình duyệt hỗ trợ thì dùng).
- Ngoặc kép cong “…”; gạch nối giữa câu " – "; khoảng số "2020–2026".
- Số kiểu Việt: 1.500 · 2,5 · 200.000 đ; khoảng trắng không ngắt giữa số và đơn vị.
- Đậm cho từ khoá (ít), nghiêng cho câu định nghĩa và thuật ngữ tiếng Anh; không gạch chân ngoài link.
- Link nói đích đến; link ra ngoài có dấu ↗.
- Bảng: số căn phải, chữ số đều độ rộng, ô số không xuống dòng (thử ở 320px: "200.000 đ" bị gãy thành hai dòng nếu thiếu); trên điện thoại bảng cuộn ngang trong khung, cột đầu dính.

## Tiêu chuẩn trải nghiệm đọc

Đo được, kiểm ở mọi mẫu trang (chi tiết trong `reading-standard`):

| Tiêu chuẩn | Mức |
|---|---|
| WCAG 2.2 | AA toàn trang; tương phản thân bài AAA (≥ 7:1) |
| Co giãn 320px / phóng chữ 200% / tự giãn dòng–chữ | không cuộn ngang, không mất chữ, không chồng lấn |
| Vùng bấm | ≥ 44×44px (cảm ứng, dưới 1200px); ≥ 32px với chuột/trackpad từ 1200px |
| Độ dài dòng / giãn dòng | 45–75 ký tự / 1.6–1.8 |
| Lighthouse mobile | Accessibility 100 · SEO 100 · Best Practices ≥ 95 · Performance ≥ 90 |
| axe-core | 0 lỗi nghiêm trọng |
| Thử tay | đọc trọn bài bằng VoiceOver, TalkBack; chỉ dùng bàn phím |

## Thiết bị nhỏ (320–375px: iPhone SE 1, 5s, mini, 6–8)

- Thiết kế bắt đầu từ 320px rồi mới mở rộng; kiểm ở **320×568, 375×667, 375×812**, cả dọc và ngang.
- **Tab tầng** "Vì sao · Làm thế nào · Vận hành ra sao · Kết quả" dưới 480px là lưới 2×2 (không để 3+1 lệch), không cuộn ngang, không cắt chữ.
- **Màn đầu trang bài** (tác giả chọn 2026-10-18: "Đọc trước"): ở 320×568 phải thấy tiêu đề, câu định nghĩa nghiêng và ít nhất đoạn mở đầu phía trên reading-toolbar — nên dưới 1200px không có khối "Trong bài này" hay nút "Sao chép bài" trong thân bài, đầu bài dồn khoảng cách, và dưới 480px dòng thông tin là MỘT dòng "Cập nhật lần cuối: … · Còn N phút" (không JS: "N phút đọc" thay "Còn N phút"); dấu "·" luôn đi cùng mục sau, không bao giờ đứng đầu dòng.
- **Màn thấp** (`max-height: 600px`, SE 1 và điện thoại xoay ngang) và mọi màn dưới 1200px: top-nav không dính; mép trên chỉ có thanh tiến độ 3px, mép dưới chỉ có reading-toolbar (tự ẩn khi cuộn xuống); không có hai thanh dính chồng nhau. Ngoại lệ có tên: **thông báo cookie** (ADR-30) lần đầu vào site nằm ngay trên reading-toolbar — không chồng lên thanh, mất hẳn khi người đọc bấm ×.
- **Ô email** giữ một hàng kể cả ở 320px: ô gạch chân, nút chữ "Gửi →" trong ô; không xếp dọc, không nút đen rộng 100%.
- **Cài đặt đọc, Trong bài này, mục lục, ghi chú, popup ủng hộ** mở toàn màn hình hoặc tấm trượt từ đáy; nút đóng luôn thấy, 44px.
- **Popover chọn chữ** đặt dưới đoạn chọn để không đè menu hệ thống của iOS/Android.
- Dùng `dvh` cho chiều cao toàn màn (có dự phòng `vh` cho trình duyệt cũ), không dùng `100vh` thuần.

## Máy cũ và mạng chậm

- **Nâng cấp dần:** HTML tĩnh đọc được trọn bài, mục lục, Trước/Tiếp khi JS tắt hoặc lỗi (ADR-28). JS chỉ thêm: tiến độ, cài đặt đọc, đánh dấu, ghi chú, ô email.
- **Mốc hỗ trợ đầy đủ:** iOS Safari 15.4+, Chrome Android 100+ (xem `compatibility`). Dưới mốc: vẫn đọc được.
- **Ngân sách:** JS trang bài ≤ 30 KB gzip; font màn đầu ≤ 150 KB; LCP ≤ 2.5s trên 4G chậm; CLS < 0.1 — đo trong build, vượt thì báo.
- Không hiệu ứng nặng (blur, bóng lớn, animation liên tục); cuộn không gắn xử lý nặng (dùng `requestAnimationFrame`/`IntersectionObserver`, cập nhật tiến độ có throttle).
- Ảnh: WebP/AVIF có dự phòng, `srcset`, khai kích thước, `loading="lazy"` dưới màn đầu.
- iOS Safari xoá dữ liệu trang web của site không được mở trong 7 ngày (khi chưa cài PWA) — chỗ đọc, ghi chú có thể mất; khung "Ghi chú của tôi" nói rõ "Chỉ lưu trên trình duyệt này" (UC-210).

## PWA

- Cài được lên màn hình chính: manifest, biểu tượng (gồm maskable và apple-touch-icon), `theme-color` theo nền đang dùng.
- **Chế độ standalone** không có nút Back và thanh địa chỉ của trình duyệt → top-nav hiện nút "←" và "Sao chép link bài".
- **Vùng an toàn:** `viewport-fit=cover`; mọi thanh dính, tấm trượt, chân trang cộng `env(safe-area-inset-*)` (tai thỏ, Dynamic Island, thanh vuốt).
- iOS: PWA đã cài có kho dữ liệu **riêng** với Safari — chỗ đọc, ghi chú trong Safari không tự sang PWA và ngược lại (UC-210 cần ghi rõ).
- **Ngoại tuyến:** bản đầu KHÔNG làm (tác giả chọn "để sau", `docs/ideas.md`); không có service worker. Giao diện banner/trang "Bạn đang ngoại tuyến" giữ sẵn trong token cho lần sau.
- Không hiện lời mời cài app dạng popup (BR: không popup — ngoại lệ có tên duy nhất là thông báo cookie, ADR-30); nếu có, chỉ là một dòng trong menu.

## Tương tác và trợ năng

- Mọi thứ bấm được ≥ 44×44px với cảm ứng và mọi màn dưới 1200px (chuột/trackpad từ 1200px: ≥ 32px — `touch.target-min.fine`), cách nhau ≥ 8px; `touch-action: manipulation`.
- Vòng focus thấy rõ (`:focus-visible`, 2px + offset 2px); link "Bỏ qua, tới nội dung" đầu trang.
- Tiêu đề đúng thứ bậc h1 → h2 → h3; mã neo `##` theo ADR-28.
- Nút chỉ có biểu tượng có `aria-label` tiếng Việt; biểu tượng SVG (Lucide), không emoji.
- Biểu mẫu: nhãn hiện trên ô; lỗi ngay dưới ô, nói nguyên nhân + cách sửa, `role="alert"`; nút gửi có trạng thái "Đang gửi…".
- Ngăn kéo/popup: bẫy focus, Esc đóng, focus trả về nút mở; nền `scrim`.
- `prefers-reduced-motion: reduce` → tắt mọi chuyển động, kể cả cuộn mượt tới mục.

## Thành phần

Token ở `components`. Ghi chú:
- **Thẻ nỗi đau:** viền đen 1px, chữ serif, chữ phụ tên trụ tiếng Anh (`title_en`), không icon. 1/2/3 cột theo bề rộng.
- **Menu "Chủ đề ▾"** (ADR-31): ba nhóm Nguyên lý · Trụ cột · Nền tảng, luôn đủ 9 mục; mục chưa có bài chữ `muted` kèm "sắp ra" nhưng vẫn bấm được; cuối menu "Xem tất cả chủ đề →". Máy tính: ba cột trong một khung nổi; điện thoại: cùng ba nhóm trong ☰.
- **"Xem theo chủ đề" ở trang chủ:** mỗi nhóm một hàng — nhãn nhóm `muted` rồi các chip trụ có bài (768–1199px nhãn bên trái; dưới 768px và từ 1200px — khi khối ở cột phải hẹp, `home-layout` — nhãn ở trên); nhóm không có trụ nào có bài thì không có hàng.
- **Ô "Từ thực tế":** khối duy nhất trong thân bài có nền.
- **Thanh tiến độ:** 3px đen; "Còn N phút" ở dòng thông tin bài, số dạng tabular.
- **Popover chọn chữ:** "Đánh dấu · Ghi chú · Chia sẻ câu này" (không có "Chia sẻ câu này" trong phần How-to dưới gate); không có "Sao chép" (ADR-21) — câu chỉ ra ngoài thành ảnh có nguồn (UC-115).
- **Ảnh chia sẻ** (UC-115): thẻ bài và ảnh trích dẫn là hình vuông 1080×1080, cùng một khổ; nền là `share-backgrounds` (Trắng · Giấy · Đen · Chấm · Tím), chữ là font của trang; ảnh xem trước khi dán link giữ 1200×630. Mẫu sống ở trang `/design-system/`.
- **Ô "Nhận bài mới":** nhẹ, nằm yên cuối bài — một câu mời + ô gạch chân một hàng với nút chữ "Gửi →" ở đầu phải + một dòng đồng ý. Không dính, không nổi, không popup, không nút đen tô kín; người không quan tâm cuộn qua như qua một dòng chữ (tác giả 2026-10-18: bản nút đen "chắn và tạo cảm giác không thân thiện"). Không có nút "Tiếp tục với Google" (tác giả 2026-10-20).
- **Cuối bài** (`end-of-post`): Trước/Tiếp → nút ☕ "Mời tác giả một ly cà phê" → ô "Nhận bài mới" → "Câu hỏi & bình luận", mỗi khối cách nhau `spacing.xl`; bài có gate không có ô "Nhận bài mới".
- **Form bình luận** (`comment-form`): khung viền mảnh; khi chưa có phiên có ô tên, ô email, ô đánh dấu bản tin và nút phụ "Tiếp tục với Google" ngay dưới; có phiên thì chỉ còn "Bạn đang bình luận với tên …".
- **Popup ủng hộ:** QR ≥ 200px trên nền trắng; toàn màn hình trên điện thoại.
- **Dòng "Đọc tiếp"** (reader UC-210 bước 4, 4b): một dòng chữ nhãn màu phụ ở đầu nội dung trang chủ, trong khung trang — không phải thanh, không nền, không viền; chỉ tên bài là link; nút × nhỏ (vùng bấm theo `touch`) ẩn dòng tới khi người đọc đọc dở bài khác.
- **Chân trang:** hai hàng trên nền `surface-soft` — cột thương hiệu + ba cột link "Chủ đề · Đọc · Về mza" với tiêu đề nhãn nhỏ ("Về mza" mở đầu bằng "Design system"), dòng cuối "© <năm> mza.vn" + Chính sách quyền riêng tư · RSS (biểu tượng RSS nhỏ trước chữ) · llms.txt. Không hàng "Theo dõi bài mới", không nút RSS, không ô email. Không dùng nền `ink` đảo màu: ở nền Tối `ink` là màu sáng, khối cuối trang sẽ chói; `surface-soft` lệch nhẹ khỏi `canvas` ở cả ba nền.
- **Khối "Liên hệ"** (`contact-list`, publishing UC-103 trang giới thiệu bước 6, UC-116): cuối trang Giới thiệu và Portfolio, các dòng Email · Facebook · Zalo kẻ mảnh, mỗi dòng là một link cả dòng với biểu tượng đơn sắc, tên kênh, giá trị. Chỉ hiện kênh tác giả đã khai ở quản trị; không kênh nào thì không có khối. Chân trang chỉ có link chữ "Liên hệ" tới khối này.
- **Thông báo cookie** (ADR-30, reader UC-209): hộp nhỏ nền `ink` chữ `on-primary` ở góc dưới (điện thoại: sát đáy, trên reading-toolbar), câu "Website có sử dụng cookie, tìm hiểu thêm về chính sách quyền riêng tư." với link gạch chân và nút × 44px. Chỉ hiện tới khi người đọc bấm ×; không nút đồng ý/từ chối, không scrim, không lấy focus, không khoá cuộn. Đây là hộp tự hiện duy nhất được phép.

## Nên

- Để chữ làm việc: mỗi màn một cỡ tiêu đề, một cỡ thân bài, một cỡ nhãn.
- Dùng token trong code; nền khối phụ chỉ `surface-soft`.
- Kiểm trước khi giao: 320 · 375 · 768 · 1024 · 1440px và các khung MacBook 1440×900 · 1470×956 · 1512×982 · 1728×1117; xoay ngang; ba nền; chữ hệ thống 200%; tự giãn dòng/chữ; reduced-motion; JS tắt; PWA standalone trên iPhone có tai thỏ; ảnh và sơ đồ ở nền Tối; bảng `reading-standard`.

## Không nên

- Không màu thương hiệu thứ hai; tím chỉ bật khi có lý do đo được.
- Không gradient, bóng lớn, ảnh nền, icon trang trí, emoji làm biểu tượng, ảnh stock, ảnh minh hoạ không mang thông tin.
- Không căn đều hai bên, không ngắt từ, không sơ đồ dùng màu để phân biệt.
- Không chữ không chân cho thân bài; không chữ dưới 13px; không chặn phóng to.
- Không chữ "sách", "chương", "mode", "How-to", "Case study" trên giao diện (BR-01, ADR-28).
- Không tải font, icon, script từ máy chủ bên thứ ba — hai ngoại lệ có tên: script Cloudflare Turnstile (`challenges.cloudflare.com`) phục vụ kiểm tra chống máy tự động (reader UC-201 AC-10, ADR-32), và CHỈ tải lười khi người đọc bấm/focus vào ô nhập email hoặc form bình luận; khung trình phát YouTube của ô video (publishing UC-110 AC-1, `pages/privacy.md`: "chỉ tải khi bạn bấm vào video"), CHỈ tạo khi người đọc bấm nút phát — trước đó trang không có khung nhúng hay script nào của YouTube (nguồn ảnh bìa video còn `[OPEN]` ở UC-110; tới khi tác giả chọn, ô video là khung 16:9 site tự vẽ) (ADR-32 sửa đổi 2026-10-20).
- Không cuộn ngang trang; không hai thanh dính chồng nhau trên màn thấp; không `100vh` thuần.
- Không khối mời email dính/nổi/trượt vào khi cuộn; không nút đen tô kín cho ô email.
- Không popup, không hộp hay dải tự hiện, không lớp phủ che chữ — **ngoại lệ có tên duy nhất: `cookie-notice`** (ADR-30: thông báo, không xin đồng ý; không scrim, không lấy focus, đóng × là không hiện lại). Không thêm hộp tự hiện nào khác "theo cùng kiểu".

## Ghi chú cho Astro

- Token xuất thành biến CSS (`--color-ink`, `--font-serif`, `--read-size`, …) trong một file CSS gốc; ba nền là ba bộ biến dưới `[data-theme="light|paper|dark"]` trên `<html>`, mặc định theo `prefers-color-scheme`.
- Một đoạn script nhỏ nội tuyến trong `<head>` áp cài đặt đọc (nền, cỡ chữ, độ rộng) trước khi vẽ để không nháy; cập nhật `<meta name="theme-color">` theo nền.
- Font trong `public/fonts/`, `@font-face` có `unicode-range`; chỉ preload Literata 400.
- Tính năng JS là island nhỏ, tải sau (`client:idle`/`client:visible`); trang bài không cần framework UI.
- Manifest và biểu tượng PWA trong `public/`; service worker chỉ thêm khi spec có hành vi ngoại tuyến.
- Trang "Design system" `/design-system/` (publishing UC-114) dựng lúc build từ chính tệp này (front matter + phần thân) và `src/styles/tokens.css`; tệp được phục vụ nguyên văn ở `/design-system/DESIGN.md`. Đổi token ở đây và trong `tokens.css` thì trang đổi theo; front matter YAML lỗi thì build dừng.
- Trang "Design system" là trang tham khảo, không phải bài: cột nội dung dùng hết khung chung — từ 1200px là phần còn lại của `reading.container` bên phải mục lục, mép phải trùng mép phải cụm top-nav và chân trang; dưới 1200px một cột rộng hết khung giữa hai lề `reading.gutter`. Tiêu đề, phần đầu trang (tiêu đề, câu nghiêng, dòng phiên bản, dòng nguồn) và mọi khối minh hoạ/bảng (ba thẻ nền đọc, bảng màu, mẫu chữ, bảng token, thành phần) trải hết bề ngang cột, lưới tự chia cột theo bề rộng; chỉ đoạn văn (câu mô tả mỗi phần, đoạn trích) giữ `reading.measure` (≤ 75 ký tự/dòng — Tiêu chuẩn trải nghiệm đọc). Tác giả 2026-10-19: "sao nó không full cả ra màn bên này mà tôi cảm giác bị bó lại ấy nhỉ ?".
