terra · pattern library59 elements — viewing aid, nguồn sự thật là brands/terra/patterns/

← Brands

backgrounds/

Nền section

dark-band experimental atom ▼ chi tiết

Một dải tối đặc cắt ngang mạch trang sáng, kèm một vệt sáng rất nhẹ ở giữa để mặt phẳng không chết. Ngắt nhịp đọc, báo "sang chương khác", và làm mọi thứ đặt lên trên tự động nặng ký hơn. Dùng nhiều lần trên một trang thì mất tác dụng ngay.

Ràng buộc: Nền = var(--bg-dark). Mỗi trang tối đa 2 dải tối, và một trong hai thường là CTA cuối trang (iron rule 6 về nhịp nền). Chữ trên nền này dùng trắng và rgba trắng, không dùng --text-body. Motion: T-A.
Hợp cặp: decor/corner-circles, cards/card-treatments
Nguồn gốc: new 2026-08, chuẩn hoá từ nền các section tối rải rác trong final-cta và các trang production
File: brands/terra/patterns/backgrounds/dark-band.md
Khung: Nền: Mở riêng ↗
HTML
<section class="bg-dark">
  <div class="bg-dark-glow" aria-hidden="true"></div>
  <div class="wrap">[nội dung section]</div>
</section>
CSS
.terra-page .bg-dark {
  position: relative; overflow: hidden; isolation: isolate;
  background: var(--bg-dark); color: var(--white);
}
.terra-page .bg-dark-glow {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: radial-gradient(70% 90% at 50% 0%, rgba(20,121,108,.30) 0%, transparent 65%);
}
.terra-page .bg-dark > .wrap { position: relative; z-index: 1; }
.terra-page .bg-dark h2, .terra-page .bg-dark h3 { color: var(--white); }
.terra-page .bg-dark p { color: rgba(255,255,255,.82); }
grid-mask validated atom ▼ chi tiết

Lưới ô vuông mảnh mờ dần ra rìa, đậm nhất ở giữa rồi tan hẳn. Gợi thứ gì đó có hệ thống, có kỷ luật, kiểu bản vẽ kỹ thuật, mà không cắt vụn vùng nhìn. Đặt dưới chữ thì chữ trông như nằm trên một mặt phẳng có toạ độ chứ không lơ lửng.

Ràng buộc: Chỉ trên nền sáng (iron rule 6). Ô 64px, viền teal ở độ mờ 0.06, đậm hơn là thành kẻ caro. Mask radial bắt buộc, bỏ mask thì lưới chạy tới rìa và trông rẻ tiền. Motion: không.
Hợp cặp: backgrounds/light-gradient-mesh, decor/corner-circles
Nguồn gốc: § 4.3 của PATTERN_LIBRARY cũ, trước 08/2026 không có file riêng mà nằm lẫn trong hero-homepage-light
File: brands/terra/patterns/backgrounds/grid-mask.md
Khung: Nền: Mở riêng ↗
HTML
<section class="bg-grid">
  <div class="bg-grid-layer" aria-hidden="true"></div>
  <div class="wrap">[nội dung section]</div>
</section>
CSS
.terra-page .bg-grid { position: relative; overflow: hidden; isolation: isolate; }
.terra-page .bg-grid-layer {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background-image:
    linear-gradient(rgba(20,121,108,.06) 1px, transparent 1px),
    linear-gradient(90deg, rgba(20,121,108,.06) 1px, transparent 1px);
  background-size: 64px 64px;
  -webkit-mask-image: radial-gradient(ellipse at 50% 50%, rgba(0,0,0,.4) 0%, transparent 75%);
  mask-image: radial-gradient(ellipse at 50% 50%, rgba(0,0,0,.4) 0%, transparent 75%);
}
.terra-page .bg-grid > .wrap { position: relative; z-index: 1; }
light-gradient-mesh validated atom ▼ chi tiết

Ba mảng màu rất nhạt loang chồng nhau ở ba góc, tạo chiều sâu mà mắt gần như không nhận ra là có nền. Làm vùng sáng bớt phẳng, đọc ra dáng thương hiệu chứ không ra dáng trang dịch vụ. Không kéo mắt về phía nó, chữ vẫn là thứ nổi nhất.

Ràng buộc: Chỉ đặt trên nền sáng (--white / --bg-light) theo iron rule 6. Ba mảng dùng mint, teal và cam ở độ mờ rất thấp, tăng độ mờ lên là hỏng ngay cảm giác thoáng. Không đổi trên mobile. Motion: không.
Hợp cặp: backgrounds/grid-mask, media/photo-wash, structures/hero-split-5050
Nguồn gốc: tách ra từ compositions/hero-homepage-light khi cải tổ store 08/2026
File: brands/terra/patterns/backgrounds/light-gradient-mesh.md
Khung: Nền: Mở riêng ↗
HTML
<section class="bg-mesh">
  <div class="bg-mesh-layer" aria-hidden="true"></div>
  <div class="wrap">[nội dung section]</div>
</section>
CSS
.terra-page .bg-mesh {
  position: relative; overflow: hidden; isolation: isolate;
  background: linear-gradient(135deg, #EAFCFA 0%, #F0FBF8 50%, var(--white) 100%);
}
.terra-page .bg-mesh-layer {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background:
    radial-gradient(60% 80% at 80% 20%, rgba(170,239,219,.40) 0%, transparent 60%),
    radial-gradient(50% 60% at 15% 70%, rgba(20,121,108,.10) 0%, transparent 60%),
    radial-gradient(40% 50% at 90% 80%, rgba(228,144,37,.10) 0%, transparent 60%);
}
.terra-page .bg-mesh > .wrap { position: relative; z-index: 1; }
radial-blob validated atom ▼ chi tiết

Một mảng màu loang mờ làm nền cho cả vùng, dạng đốm sáng tỏa tròn. Không có viền, không nổi lên - chỉ làm nền ấm hơn, đỡ trống. Dùng khi một vùng nền phẳng trông trơ quá.

Ràng buộc: Chỉ dùng trên nền section được phép (iron rule 6). Không bắt chuột, không chuyển động.
Hợp cặp: compositions/hero-teal-photo, cards/card-treatments
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/backgrounds/radial-blob.md
Khung: Nền: Mở riêng ↗
HTML
<div style="position:relative;min-height:260px;background:var(--white);border-radius:12px;overflow:hidden;padding:48px">
  <div class="blob-tr-teal"></div>
  <div class="blob-bl-orange"></div>
  <div style="position:relative;max-width:420px">
    <h3 style="margin:0 0 10px">Section content sits above the blobs</h3>
    <p style="color:var(--text-body);margin:0">The eye reads this text; the blobs only tint the mood of the corners.</p>
  </div>
</div>
CSS
/* Top-right teal blob (use on light sections or dark CTA) */
.blob-tr-teal::before {
  content: ''; position: absolute;
  top: -200px; right: -100px;
  width: 460px; height: 460px;
  background: radial-gradient(circle, rgba(170,239,219,0.18), transparent 70%);
  pointer-events: none;
  z-index: 1;
}

/* Bottom-left orange warmth (use on dark CTA only) */
.blob-bl-orange::after {
  content: ''; position: absolute;
  bottom: -220px; left: -80px;
  width: 500px; height: 500px;
  background: radial-gradient(circle, rgba(228,144,37,0.16), transparent 65%);
  pointer-events: none;
  z-index: 1;
}
teal-photo-overlay locked atom ▼ chi tiết

Ảnh thật phủ kín section nhưng bị dìm dần từ trái sang: bên trái tối đặc đủ để chữ trắng đọc thoải mái, bên phải hé ra cho thấy ảnh là ảnh thật. Cho cảm giác có bối cảnh, có người thật, mà không hy sinh chữ. Mắt bám cột trái rồi mới trôi sang phải.

Ràng buộc: Nền dưới ảnh = var(--teal) #14796C (locked 06/2026), KHÔNG dùng teal-dark. Thẻ ảnh KHÓA (CORE_RULES § 3): loading=eager fetchpriority=high data-no-lazy=1 alt="". Nội dung đặt lên trên phải có z-index >= 4. Chữ chìm thì tăng độ đậm nhanh của chặng gradient bên trái, không đổi màu nền.
Hợp cặp: structures/hero-text-left, decor/corner-circles
Nguồn gốc: tách ra từ compositions/hero-teal-photo khi cải tổ store 08/2026
File: brands/terra/patterns/backgrounds/teal-photo-overlay.md
Khung: Nền: Mở riêng ↗
HTML
<section class="bg-tealphoto">
  <img src="[Pexels URL w=1600]" alt="" class="bg-tealphoto-img" loading="eager" fetchpriority="high" data-no-lazy="1">
  <div class="bg-tealphoto-blur" aria-hidden="true"></div>
  <div class="bg-tealphoto-overlay" aria-hidden="true"></div>
  <div class="wrap">
    [nội dung section — nhớ z-index >= 4]
  </div>
</section>
CSS
.terra-page .bg-tealphoto { position: relative; overflow: hidden; background: var(--teal); color: #fff; }
.terra-page .bg-tealphoto-img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; z-index: 0; pointer-events: none;
}
.terra-page .bg-tealphoto-blur {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
  -webkit-mask-image: linear-gradient(105deg, #000 0%, rgba(0,0,0,.85) 22%, rgba(0,0,0,.35) 42%, transparent 58%);
  mask-image: linear-gradient(105deg, #000 0%, rgba(0,0,0,.85) 22%, rgba(0,0,0,.35) 42%, transparent 58%);
}
.terra-page .bg-tealphoto-overlay {
  position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background: linear-gradient(105deg,
    rgba(4,34,33,.97) 0%, rgba(7,55,50,.86) 32%,
    rgba(14,90,80,.36) 64%, rgba(20,121,108,.06) 100%);
}
.terra-page .bg-tealphoto .wrap { position: relative; z-index: 4; }
tint-panel experimental atom ▼ chi tiết

Nửa trên section giữ nền cũ, nửa dưới chuyển sang một sắc mint rất nhạt, ranh giới nằm ngang mềm. Tách một vùng ra khỏi mạch đọc mà không cần viền, không cần thẻ, không cần đổi hẳn màu section. Đủ để mắt biết "chỗ này khác" rồi thôi.

Ràng buộc: Chỉ trên nền sáng (iron rule 6). Dùng --teal-tint ở độ mờ thấp, không dùng --teal đặc. Ranh giới phải mềm, cạnh cứng là thành hai section rời. Motion: không.
Hợp cặp: structures/grid-3-2, cards/card-treatments, forms/cf7-form-kit
Nguồn gốc: new 2026-08
File: brands/terra/patterns/backgrounds/tint-panel.md
Khung: Nền: Mở riêng ↗
HTML
<section class="bg-tint">
  <div class="bg-tint-layer" aria-hidden="true"></div>
  <div class="wrap">[nội dung section]</div>
</section>
CSS
.terra-page .bg-tint { position: relative; overflow: hidden; isolation: isolate; background: var(--white); }
.terra-page .bg-tint-layer {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: linear-gradient(180deg, transparent 0%, transparent 38%,
    rgba(223,255,254,.55) 62%, rgba(223,255,254,.85) 100%);
}
.terra-page .bg-tint > .wrap { position: relative; z-index: 1; }

cards/

Thẻ (cards)

authority-stack validated block ▼ chi tiết

Một cụm thẻ xếp dọc, mỗi thẻ có tên viết tắt to, tên đầy đủ, và một dòng giải thích. Cách xếp dọc và lặp lại đúng một khuôn làm người xem đọc như một danh mục chính thức chứ không phải lời quảng cáo. Sức nặng đến từ sự khô khan có chủ đích.

Ràng buộc: Tên cơ quan ghi đúng nguyên văn, không bịa cơ quan (iron rule 7).
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/cards/authority-stack.md
Khung: Nền: Mở riêng ↗
HTML
<div class="authority-stack" style="max-width:520px">
  <div class="authority-card"><span class="authority-acronym">VSS</span><div><div class="authority-full-name">Vietnam Social Security</div><span class="authority-badge">Social Insurance</span></div></div>
  <div class="authority-card"><span class="authority-acronym">GDT</span><div><div class="authority-full-name">General Department of Taxation</div><span class="authority-badge">Personal Income Tax (PIT)</span></div></div>
  <div class="authority-card"><span class="authority-acronym">DOLISA</span><div><div class="authority-full-name">Department of Labor</div><span class="authority-badge">Labor reporting</span></div></div>
</div>
CSS
.authority-split{display:grid;grid-template-columns:58% 1fr;gap:60px;align-items:start}
.authority-stack{display:flex;flex-direction:column;gap:14px}
.authority-card{background:var(--white);border:1px solid var(--border);border-radius:14px;padding:22px;transition:border-color .2s,box-shadow .2s}
.authority-card:hover{border-color:var(--teal);box-shadow:0 4px 16px rgba(20,121,108,0.1)}
.authority-badge{display:inline-flex;align-items:center;gap:8px;margin-bottom:10px}
.authority-acronym{font-size:16px;font-weight:800;color:var(--teal)}
.authority-full-name{font-size:14px;font-weight:600;color:var(--text-muted);letter-spacing:0.04em}

.reg-update-box{background:linear-gradient(135deg,var(--teal-dark),#0D6B5E);border-radius:12px;padding:20px 22px;margin-top:16px}
.reg-update-box .reg-label{font-size:14px;font-weight:700;letter-spacing:0.12em;text-transform:uppercase;color:var(--green-light);margin-bottom:8px;display:block}
.reg-update-box p{font-size:16px;color:rgba(255,255,255,0.82);line-height:1.65;margin:0}
card-numbered experimental unit ▼ chi tiết

Một ô có con số mờ to ở góc trên, đọc như một mục trong danh sách có thứ tự mà không cần đánh dấu đầu dòng. Số đủ mờ để không tranh chỗ với tiêu đề, nhưng đủ to để mắt biết đây là mục thứ mấy. Vạch màu mảnh trên đỉnh giữ ô không bị trôi khi đặt cạnh nhau nhiều ô.

Ràng buộc: Là `unit`, phải cắm vào một element `body` (lưới, băng chuyền), không tự đứng làm section. Hover chỉ đổi viền và bóng, không nhấc (motion T-A). Số ghi 2 chữ số, không ghi "Bước".
Hợp cặp: structures/grid-3-2, structures/grid-N, cards/card-treatments
Nguồn gốc: tách ra từ structures/grid-3-2 khi cải tổ store 08/2026
File: brands/terra/patterns/cards/card-numbered.md
Khung: Nền: Mở riêng ↗
HTML
<div class="cardnum">
  <span class="cardnum-n">[01]</span>
  <h4>[tiêu đề ngắn 3-6 từ]</h4>
  <p>[một câu nêu vấn đề hoặc ý chính]</p>
</div>
CSS
.terra-page .cardnum {
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 26px 24px;
  position: relative;
  overflow: hidden;
  height: 100%;
  transition: border-color .2s, box-shadow .2s;
}
.terra-page .cardnum::before {
  content: '';
  position: absolute; top: 0; left: 0; right: 0; height: 3px;
  background: linear-gradient(90deg, var(--teal), var(--green-medium));
}
.terra-page .cardnum:hover {
  border-color: var(--green-medium);
  box-shadow: var(--shadow-1-base);
}
.terra-page .cardnum-n {
  display: block;
  font-size: 32px; font-weight: 800; line-height: 1;
  color: var(--green-medium); opacity: .25;
  margin-bottom: 8px;
}
.terra-page .cardnum h4 { color: var(--text-primary); font-size: 16px; margin-bottom: 8px; }
.terra-page .cardnum p { font-size: 16px; color: var(--text-body); line-height: 1.65; margin: 0; }
card-treatments validated block ▼ chi tiết

Bốn kiểu cho cùng một dạng thẻ: feature (thẻ thường, nhấc lên khi rê chuột), highlight (thẻ sáng ấm để làm nổi), dark (thẻ tối, nặng, ra dáng quan trọng), stat (một con số to làm chủ đạo). Chọn kiểu theo vai trò của thẻ trong section.

Ràng buộc: Một section không trộn quá 2 kiểu thẻ (GUIDELINE § 5 Axis 2). Đổ bóng lấy từ foundations/shadow-ladder.
Hợp cặp: foundations/shadow-ladder, structures/grid-3-2
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/cards/card-treatments.md
Khung: Nền: Mở riêng ↗
HTML
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:20px">
  <div class="card card-feature"><h4 style="margin:0 0 8px">Feature</h4><p style="margin:0;font-size:14px;color:var(--text-body)">Neutral workhorse. Hover to see the lift.</p></div>
  <div class="card card-highlight"><h4 style="margin:0 0 8px">Highlight</h4><p style="margin:0;font-size:14px;color:var(--text-body)">Warm spotlight without weight.</p></div>
  <div class="card card-dark"><h4 style="margin:0 0 8px">Dark</h4><p style="margin:0;font-size:14px;opacity:.85">Gravity and authority.</p></div>
  <div class="card card-feature card-stat"><div class="stat-num">5,000+</div><div class="stat-label">payslips processed monthly</div></div>
</div>
CSS
.card {
  border-radius: 10px;
  padding: 24px;
}
.card-feature {
  background: var(--white);
  border: 1px solid var(--border);
  box-shadow: 0 2px 8px rgba(7,49,48,0.05);
  transition: transform .2s ease, box-shadow .2s ease;
}
.card-feature:hover {
  transform: translateY(-4px);
  box-shadow: 0 12px 28px rgba(7,49,48,0.10);
}
.card-highlight {
  background: var(--teal-tint);
  border: 1px solid rgba(20,121,108,0.15);
}
.card-dark {
  background: var(--teal-dark);
  color: var(--white);
}
.card-dark h3, .card-dark h4 { color: var(--white); }
.card-stat { text-align: left; }
.card-stat .stat-num {
  font-size: 40px;
  font-weight: 700;
  line-height: 1.1;
  color: var(--teal);
}
.card-dark .stat-num { color: var(--green-light); }
.card-stat .stat-label {
  font-size: 13px;
  color: var(--text-body);
  margin-top: 6px;
}
diff-card-image-hover validated block ▼ chi tiết

Thẻ có ảnh nền, tiêu đề nằm trên ảnh. Rê chuột vào thì ảnh phóng to nhẹ và hiện thêm phần mô tả bên dưới tiêu đề. Dùng khi muốn khoe ảnh đẹp mà vẫn có chữ. Mobile: bỏ hiệu ứng, mô tả hiện sẵn luôn.

Ràng buộc: Cần ít nhất 3 thẻ và ảnh thật (trang mới dùng ảnh Pexels - iron rule 8). Đã kèm sẵn bản mobile. Lớp phủ ảnh dùng màu var(--teal-dark).
Nguồn gốc: service-page-terra-payroll-vi.html (production, harvested 2026-06-11)
File: brands/terra/patterns/cards/diff-card-image-hover.md
Khung: Nền: Mở riêng ↗
HTML
<div class="diff-grid">

      <article class="diff-card">
        <!-- ELEMENTOR NOTE: Replace with licensed business/finance image.
             Visual direction: Tax documents, calculator, professional desk. -->
        <img src="https://images.pexels.com/photos/6863183/pexels-photo-6863183.jpeg?auto=compress&cs=tinysrgb&w=900"
             alt="Tax documents and calculator on workspace" class="diff-card-img" loading="lazy">
        <span class="diff-icon-corner" aria-hidden="true">
          <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <rect x="4" y="3" width="16" height="18" rx="2"/>
            <line x1="8" y1="9" x2="16" y2="9"/>
            <line x1="8" y1="13" x2="13" y2="13"/>
            <text x="16" y="17" font-family="Montserrat" font-size="7" font-weight="700" fill="currentColor" stroke="none">%</text>
          </svg>
        </span>
        <div class="diff-card-overlay">
          <div class="diff-num">01</div>
          <h3>Quản lý thuế thu nhập cá nhân</h3>
          <ul class="diff-card-desc">
            <li>Tính thuế theo biểu lũy tiến và mức giảm trừ gia cảnh mới nhất</li>
            <li>Cập nhật tự động theo Thông tư và văn bản sửa đổi</li>
            <li>Xuất tờ khai theo mẫu Tổng cục Thuế, sẵn sàng nộp điện tử</li>
          </ul>
        </div>
      </article>

      <article class="diff-card">
        <!-- ELEMENTOR NOTE: Replace with licensed business team image. -->
        <img src="https://images.pexels.com/photos/3184291/pexels-photo-3184291.jpeg?auto=compress&cs=tinysrgb&w=900"
             alt="Business team collaboration in modern office" class="diff-card-img" loading="lazy">
        <span class="diff-icon-corner" aria-hidden="true">
          <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <rect x="3" y="5" width="18" height="16" rx="2"/>
            <line x1="3" y1="10" x2="21" y2="10"/>
            <line x1="8" y1="3" x2="8" y2="7"/>
            <line x1="16" y1="3" x2="16" y2="7"/>
            <path d="M7 15l2 2 5-5"/>
          </svg>
        </span>
        <div class="diff-card-overlay">
          <div class="diff-num">02</div>
          <h3>Quản lý tiền lương</h3>
          <ul class="diff-card-desc">
            <li>Xử lý bảng lương theo cơ cấu lương gộp, lương thực nhận, thưởng và phụ cấp</li>
            <li>Kết nối dữ liệu chấm công từ hệ thống hiện có, không yêu cầu thay đổi quy trình</li>
            <li>Phiếu lương điện tử gửi tự động đến từng nhân viên</li>
          </ul>
        </div>
      </article>

      <article class="diff-card">
        <!-- ELEMENTOR NOTE: Replace with licensed HR/professional image. -->
        <img src="https://images.pexels.com/photos/3184339/pexels-photo-3184339.jpeg?auto=compress&cs=tinysrgb&w=900"
             alt="HR professionals reviewing documents" class="diff-card-img" loading="eager">
        <span class="diff-icon-corner" aria-hidden="true">
          <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
            <path d="M9 12l2 2 4-4"/>
          </svg>
        </span>
        <div class="diff-card-overlay">
          <div class="diff-num">03</div>
          <h3>Quản lý bảo hiểm xã hội</h3>
          <ul class="diff-card-desc">
            <li>Tính mức đóng BHXH, BHYT, BHTN</li>
            <li>Lập và nộp hồ sơ BHXH hàng tháng cho cơ quan bảo hiểm</li>
            <li>Quản lý biến động nhân sự ảnh hưởng đến bảo hiểm: vào, ra và thay đổi mức lương</li>
          </ul>
        </div>
      </article>

    </div>
CSS
/* ==========================================================================
   SECTION 5 - MODULES (canonical image-hover diff-cards § 4.2)
   ========================================================================== */
.diff-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; }
.diff-card {
  position: relative; border-radius: 14px; overflow: hidden;
  min-height: 440px; cursor: default;
  background: var(--teal-dark);   /* v22 - fallback bg if image fails or loads slowly */
  transition: box-shadow .3s;
}
.diff-card:hover { box-shadow: 0 16px 48px rgba(10,86,76,0.22); }
.diff-card-img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; transition: transform .6s ease;
}
.diff-card:hover .diff-card-img { transform: scale(1.06); }
.diff-card-overlay {
  position: absolute; inset: 0;
  background: linear-gradient(to top, var(--teal-dark) 0%, var(--teal-dark) 28%, rgba(10,86,76,0.55) 58%, transparent 100%);
  transition: background .45s ease;
  display: flex; flex-direction: column; justify-content: flex-end;
  padding: 28px;
}
.diff-card:hover .diff-card-overlay {
  background: linear-gradient(to top, var(--teal-dark) 0%, var(--teal-dark) 48%, rgba(10,86,76,0.90) 82%, rgba(10,86,76,0.80) 100%);
}
.diff-icon-corner {
  position: absolute; top: 18px; right: 18px;
  width: 44px; height: 44px; border-radius: 10px;
  background: rgba(170,239,219,0.20); backdrop-filter: blur(8px);
  display: flex; align-items: center; justify-content: center;
  color: var(--green-light);
  z-index: 1;
}
.diff-num {
  font-size: 38px; font-weight: 700; color: var(--green-light);
  line-height: 1; margin-bottom: 12px; opacity: 0.7;
}
.diff-card h3 {
  font-size: 19px; font-weight: 700; color: var(--white);
  margin-bottom: 0; transition: margin-bottom .3s ease;
  letter-spacing: -0.01em; text-wrap: balance;
}
.diff-card:hover h3 { margin-bottom: 14px; }
.diff-card-desc {
  max-height: 0; opacity: 0; overflow: hidden;
  transition: max-height .45s ease, opacity .3s ease .1s;
  display: flex; flex-direction: column; gap: 8px;
}
.diff-card:hover .diff-card-desc { max-height: 280px; opacity: 1; }
.diff-card-desc li {
  display: flex; align-items: flex-start; gap: 8px;
  font-size: 13.5px; line-height: 1.55;
  color: rgba(255,255,255,0.92);
}
.diff-card-desc li::before {
  content: ''; width: 6px; height: 6px; border-radius: 50%;
  background: var(--green-light); flex-shrink: 0; margin-top: 7px;
}
@media (hover: none) {
.diff-card-overlay {
    background: linear-gradient(to top, var(--teal-dark) 0%, var(--teal-dark) 48%, rgba(10,86,76,0.88) 82%, rgba(10,86,76,0.75) 100%);
  }
.diff-card h3 { margin-bottom: 14px; }
.diff-card-desc { max-height: 280px; opacity: 1; }
}
insights-card validated block ▼ chi tiết

Ảnh bìa trên cùng, dưới là một dòng thông tin nhỏ rồi mới tới tiêu đề. Nhịp đó làm nội dung trông được biên tập chỉn chu chứ không phải liệt kê tính năng. Mắt dừng ở ảnh trước, đọc tiêu đề sau, nên tiêu đề không cần cố gắng nhiều.

Ràng buộc: Bắt buộc có ảnh bìa thật (ảnh Pexels, iron rule 8). Đã kiểm trên homepage.
Hợp cặp: media/logo-bar
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/cards/insights-card.md
Khung: Nền: Mở riêng ↗
HTML
<article class="insight-card t-case">
  <div class="insight-cover">
    <span class="insight-label insight-label--case">Case Study</span>
    <img src="/wp-content/uploads/case-fmcg-japan.jpg" alt="" loading="lazy">
  </div>
  <div class="insight-body">
    <div class="insight-meta">Tháng 05/2026</div>
    <h3 class="insight-title">FMCG Nhật Bản 250 nhân viên chuyển sang BPaaS</h3>
    <p class="insight-desc">Trước: 4 ngày/tháng. Sau: 30 phút phê duyệt. Migration 6 tuần.</p>
    <a class="insight-link" href="#">
      Đọc case study
      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
        <path d="M5 12h14M13 5l7 7-7 7"/>
      </svg>
    </a>
  </div>
</article>
CSS
.insights-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 22px;
}

.insight-card {
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: 16px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transition: transform 0.22s, box-shadow 0.25s, border-color 0.22s;
  cursor: pointer;
}
.insight-card:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-2-lift);
  border-color: var(--teal);
}

/* Cover image — aspect 5:3 with gradient overlay */
.insight-cover {
  position: relative;
  aspect-ratio: 5 / 3;
  overflow: hidden;
}
.insight-cover img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform 0.5s ease;
}
.insight-card:hover .insight-cover img { transform: scale(1.04); }

.insight-cover::after {
  content: '';
  position: absolute; inset: 0;
  background: linear-gradient(180deg,
    transparent 0%,
    transparent 60%,
    rgba(7,49,48,0.50) 100%);
  pointer-events: none;
}

/* Label pill — top-left, absolute */
.insight-label {
  position: absolute;
  top: 14px; left: 14px;
  padding: 5px 12px;
  border-radius: 999px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  z-index: 1;
}

/* Type variants */
.insight-label--whitepaper { background: var(--teal-dark); color: var(--white); }
.insight-label--case       { background: var(--orange); color: var(--white); }
.insight-label--update     { background: var(--teal); color: var(--white); }
.insight-label--blog       { background: var(--green-light); color: var(--teal-dark); }

.insight-card.t-case:hover { border-color: var(--orange); }

.insight-body {
  padding: 18px 20px 22px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  flex: 1;
}
.insight-meta {
  font-size: 11px;
  color: var(--text-muted);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.insight-title {
  font-size: 16px;
  font-weight: 700;
  color: var(--text-primary);
  line-height: 1.32;
  letter-spacing: -0.015em;
}
.insight-desc {
  font-size: 13px;
  color: var(--text-body);
  line-height: 1.55;
  flex: 1;
}
.insight-link {
  font-size: 13px;
  font-weight: 600;
  color: var(--teal);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  transition: gap 0.2s;
}
.insight-card:hover .insight-link { gap: 10px; }
.insight-card.t-case .insight-link { color: var(--orange); }

@media (max-width: 1024px) { .insights-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 600px)  { .insights-grid { grid-template-columns: 1fr; } }
ribbon-pills-kit validated block ▼ chi tiết

Bộ phụ kiện làm nổi đúng một thẻ giữa nhiều thẻ ngang hàng: ruy băng ở góc cộng vài pill ghi điểm khác biệt. Mắt biết ngay nên nhìn cái nào, nhưng các thẻ còn lại vẫn giữ được phẩm giá chứ không bị dìm thành nền.

Ràng buộc: Mỗi bảng so sánh chỉ một thẻ được làm nổi. Pill viết IN HOA (iron rule 2). Thẻ cha phải có position:relative; overflow:visible.
Hợp cặp: structures/compare-split
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/cards/ribbon-pills-kit.md
Khung: Nền: Mở riêng ↗
HTML
<div style="max-width:380px">
  <div class="card" style="position:relative;background:var(--white);border:1px solid var(--border);padding:32px 24px 24px">
    <div class="option-ribbon">Recommended</div>
    <h4 style="margin:10px 0 8px">terra Payroll full-scope</h4>
    <p style="font-size:14px;color:var(--text-body)">Software + operations in one contract.</p>
    <div class="option-pills"><span class="option-pill">BPaaS</span><span class="option-pill">ISO/IEC 27001:2022</span><span class="option-pill">VI EN JA</span></div>
  </div>
</div>
CSS
.option-ribbon {
  position: absolute;
  top: -14px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--orange);
  color: var(--white);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  padding: 7px 18px;
  border-radius: 999px;
  z-index: 3;
  box-shadow: 0 6px 16px rgba(228,144,37,0.40);
  white-space: nowrap;
  width: auto;
  display: inline-flex;
}
/* Specificity guard: if parent has `.card > * { position: relative; }`,
   it overrides the ribbon (same 0,1,0, later in source). Re-assert: */
.card .option-ribbon { position: absolute; }

.option-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  margin: 14px 0 18px;
}
.option-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  background: rgba(170,239,219,0.10);
  border: 1px solid rgba(170,239,219,0.25);
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  color: var(--green-light);
}
.option-pill svg { width: 12px; height: 12px; }

compositions/

Section hoàn chỉnh

bpaas-flow-svg locked composition ▼ chi tiết

Một sơ đồ duy nhất mang trọn lập luận, không cần đoạn văn nào đi kèm. Các chấm cam chạy dọc mũi tên làm cái sơ đồ tĩnh trông như đang vận hành thật. Người xem hiểu mô hình trong vài giây, và nhớ được hình lâu hơn nhớ chữ.

Ràng buộc: KHÓA - copy nguyên cả SVG desktop VÀ mobile, không dựng lại bằng div, không vẽ lại, không cắt bỏ các chấm chạy (CORE_RULES § 3). Desktop hiện >=768px, mobile có bản dọc bên dưới. Chỉ được sửa chữ trên nhãn cho từng trang.
Nguồn gốc: service-page-terra-payroll-vi.html (production, harvested 2026-06-11)
File: brands/terra/patterns/compositions/bpaas-flow-svg.md
Khung: Nền: Mở riêng ↗
HTML
<div class="bpv20-wrap reveal reveal-delay-1">

      <!-- ===================== DESKTOP version (≥768px) ===================== -->
      <svg class="bpv20-svg bpv20-svg--desktop" viewBox="0 0 1400 520" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Mô hình BPaaS terra: Khách hàng - terra HRM - VPO - Cơ quan nhà nước">

        <defs>
          <!-- Soft drop shadow filter -->
          <filter id="bpv20-shadow" x="-10%" y="-10%" width="120%" height="120%">
            <feDropShadow dx="0" dy="3" stdDeviation="6" flood-color="#073130" flood-opacity="0.08"/>
          </filter>
          <filter id="bpv20-shadow-hrm" x="-30%" y="-30%" width="160%" height="160%">
            <feDropShadow dx="0" dy="4" stdDeviation="10" flood-color="#e49025" flood-opacity="0.25"/>
          </filter>
        </defs>

        <!-- ================================================================= -->
        <!-- LAYER 1: Container backgrounds (rendered FIRST, lowest z-order)  -->
        <!-- ================================================================= -->

        <!-- KHÁCH HÀNG mint container -->
        <rect x="30" y="35" width="280" height="450" fill="#d4f0e0" rx="14" filter="url(#bpv20-shadow)"/>

        <!-- VPO teal-dark container -->
        <rect x="750" y="35" width="320" height="450" fill="#0a564c" rx="14" filter="url(#bpv20-shadow)"/>

        <!-- CƠ QUAN gray container -->
        <rect x="1170" y="35" width="210" height="450" fill="#fbfaf6" stroke="#e5e7eb" stroke-width="1" rx="14" filter="url(#bpv20-shadow)"/>

        <!-- ================================================================= -->
        <!-- LAYER 2: Container internal content                              -->
        <!-- ================================================================= -->

        <!-- KHÁCH HÀNG content -->
        <g transform="translate(30, 35)">
          <!-- Header pill -->
          <rect x="35" y="22" width="210" height="42" fill="#ffffff" rx="10"/>
          <text x="140" y="50" text-anchor="middle" font-family="Montserrat, sans-serif" font-weight="700" font-size="16" fill="#0a564c">Khách hàng</text>

          <!-- Nhân viên sub-card -->
          <rect x="18" y="82" width="244" height="138" fill="#ffffff" stroke="rgba(7,49,48,0.05)" rx="10"/>
          <rect x="34" y="98" width="32" height="32" fill="rgba(20,121,108,0.12)" rx="8"/>
          <g transform="translate(39, 104)" stroke="#0a564c" stroke-width="1.6" fill="none" stroke-linecap="round" stroke-linejoin="round">
            <circle cx="7" cy="6" r="3"/>
            <circle cx="16" cy="6" r="2.5"/>
            <path d="M2 17 a 5 5 0 0 1 10 0 M11 17 a 4 4 0 0 1 8 0"/>
          </g>
          <text x="74" y="118" font-family="Montserrat, sans-serif" font-weight="700" font-size="14" fill="#073130">Nhân viên</text>
          <g font-family="Montserrat, sans-serif" font-size="13" fill="#073130">
            <circle cx="34" cy="152" r="2.5" fill="#14796c"/>
            <text x="44" y="156">Chấm công</text>
            <circle cx="34" cy="177" r="2.5" fill="#14796c"/>
            <text x="44" y="181">Xin đơn</text>
            <circle cx="34" cy="202" r="2.5" fill="#14796c"/>
            <text x="44" y="206">Xem phiếu lương</text>
          </g>

          <!-- Devices (small icons in middle) -->
          <g transform="translate(105, 238)" opacity="0.55" stroke="#0a564c" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round">
            <rect x="0" y="0" width="24" height="14" rx="2"/>
            <line x1="-2" y1="17" x2="26" y2="17"/>
            <rect x="40" y="-1" width="14" height="22" rx="2"/>
            <line x1="44" y1="17" x2="50" y2="17"/>
          </g>

          <!-- Quản lý sub-card -->
          <rect x="18" y="284" width="244" height="138" fill="#ffffff" stroke="rgba(7,49,48,0.05)" rx="10"/>
          <rect x="34" y="300" width="32" height="32" fill="rgba(20,121,108,0.12)" rx="8"/>
          <g transform="translate(39, 306)" stroke="#0a564c" stroke-width="1.6" fill="none" stroke-linecap="round" stroke-linejoin="round">
            <circle cx="11" cy="5" r="4"/>
            <path d="M3 19 a 8 8 0 0 1 16 0"/>
          </g>
          <text x="74" y="320" font-family="Montserrat, sans-serif" font-weight="700" font-size="14" fill="#073130">Quản lý</text>
          <g font-family="Montserrat, sans-serif" font-size="13" fill="#073130">
            <circle cx="34" cy="354" r="2.5" fill="#14796c"/>
            <text x="44" y="358">Quản lý thông tin</text>
            <circle cx="34" cy="379" r="2.5" fill="#14796c"/>
            <text x="44" y="383">Phê duyệt</text>
          </g>
        </g>

        <!-- VPO content -->
        <g transform="translate(750, 35)">
          <!-- Header pill -->
          <rect x="60" y="22" width="200" height="42" fill="rgba(255,255,255,0.10)" stroke="rgba(255,255,255,0.18)" stroke-width="1" rx="10"/>
          <text x="160" y="50" text-anchor="middle" font-family="Montserrat, sans-serif" font-weight="700" font-size="16" fill="#ffffff">VPO</text>

          <!-- Inner panel -->
          <rect x="18" y="82" width="284" height="350" fill="rgba(255,255,255,0.08)" rx="10"/>
          <text x="36" y="110" font-family="Montserrat, sans-serif" font-weight="700" font-size="11" fill="#aaefdb" letter-spacing="1.5">ĐỘI NGŨ CHUYÊN GIA</text>
          <line x1="36" y1="120" x2="284" y2="120" stroke="rgba(255,255,255,0.10)" stroke-width="1"/>

          <g font-family="Montserrat, sans-serif" font-size="14" fill="#ffffff">
            <circle cx="38" cy="160" r="3" fill="#aaefdb"/>
            <text x="50" y="165">Thiết lập hệ thống</text>
            <circle cx="38" cy="215" r="3" fill="#aaefdb"/>
            <text x="50" y="220">Quản lý Tiền lương</text>
            <circle cx="38" cy="270" r="3" fill="#aaefdb"/>
            <text x="50" y="275">Thuế Thu nhập cá nhân</text>
            <circle cx="38" cy="325" r="3" fill="#aaefdb"/>
            <text x="50" y="330">Bảo hiểm Xã hội</text>
            <circle cx="38" cy="380" r="3" fill="#aaefdb"/>
            <text x="50" y="385">Công tác hỗ trợ khác</text>
          </g>
        </g>

        <!-- CƠ QUAN content -->
        <g transform="translate(1170, 35)">
          <!-- Header pill -->
          <rect x="0" y="22" width="210" height="42" fill="#ffffff" stroke="rgba(7,49,48,0.05)" rx="10"/>
          <text x="105" y="50" text-anchor="middle" font-family="Montserrat, sans-serif" font-weight="700" font-size="15" fill="#0a564c">Cơ quan nhà nước</text>

          <!-- Cơ quan Thuế -->
          <rect x="18" y="86" width="174" height="160" fill="#ffffff" stroke="rgba(7,49,48,0.05)" rx="10"/>
          <rect x="34" y="102" width="32" height="32" fill="rgba(228,144,37,0.12)" rx="8"/>
          <g transform="translate(38, 107)" stroke="#e49025" stroke-width="1.6" fill="none" stroke-linecap="round" stroke-linejoin="round">
            <path d="M12 1 L23 8 H1 Z"/>
            <line x1="5" y1="10" x2="5" y2="20"/>
            <line x1="10" y1="10" x2="10" y2="20"/>
            <line x1="15" y1="10" x2="15" y2="20"/>
            <line x1="19" y1="10" x2="19" y2="20"/>
            <line x1="1" y1="22" x2="23" y2="22"/>
          </g>
          <text x="74" y="122" font-family="Montserrat, sans-serif" font-weight="700" font-size="13" fill="#073130">Cơ quan Thuế</text>
          <g font-family="Montserrat, sans-serif" font-size="12" fill="#073130">
            <circle cx="34" cy="162" r="2.5" fill="#14796c"/>
            <text x="44" y="166">Tờ khai thuế TNCN</text>
            <circle cx="34" cy="190" r="2.5" fill="#14796c"/>
            <text x="44" y="194">Quyết toán thuế năm</text>
          </g>

          <!-- Cơ quan BHXH -->
          <rect x="18" y="262" width="174" height="170" fill="#ffffff" stroke="rgba(7,49,48,0.05)" rx="10"/>
          <rect x="34" y="278" width="32" height="32" fill="rgba(228,144,37,0.12)" rx="8"/>
          <g transform="translate(38, 283)" stroke="#e49025" stroke-width="1.6" fill="none" stroke-linecap="round" stroke-linejoin="round">
            <path d="M12 1 L21 4 v6 c0 5 -3 8 -9 9 c-6 -1 -9 -4 -9 -9 V4 z"/>
            <path d="M8 11 l3 3 6 -6"/>
          </g>
          <text x="74" y="296" font-family="Montserrat, sans-serif" font-weight="700" font-size="12.5" fill="#073130">Cơ quan</text>
          <text x="74" y="312" font-family="Montserrat, sans-serif" font-weight="700" font-size="12.5" fill="#073130">Bảo hiểm Xã hội</text>
          <g font-family="Montserrat, sans-serif" font-size="12" fill="#073130">
            <circle cx="34" cy="350" r="2.5" fill="#14796c"/>
            <text x="44" y="354">Báo cáo BHXH,</text>
            <text x="44" y="370">BHYT, BHTN</text>
            <circle cx="34" cy="395" r="2.5" fill="#14796c"/>
            <text x="44" y="399">Nộp đúng deadline</text>
          </g>
        </g>

        <!-- ================================================================= -->
        <!-- LAYER 3: terra HRM (floating bridge, with orange glow shadow)    -->
        <!-- ================================================================= -->
        <g transform="translate(430, 150)" filter="url(#bpv20-shadow-hrm)">
          <rect width="170" height="220" fill="#0a564c" stroke="#e49025" stroke-width="2.5" rx="12"/>

          <!-- Header pill -->
          <rect x="20" y="14" width="130" height="32" fill="rgba(255,255,255,0.10)" stroke="rgba(255,255,255,0.18)" stroke-width="1" rx="7"/>
          <text x="85" y="35" text-anchor="middle" font-family="Montserrat, sans-serif" font-weight="700" font-size="13" fill="#ffffff">Phần mềm</text>

          <!-- terra logo (all white) -->
          <g transform="translate(43, 65) scale(0.50)" fill="#ffffff">
            <ellipse cx="6.55" cy="11.36" rx="2.81" ry="2.73" fill="#ffffff"/>
              <ellipse cx="31.84" cy="4.54" rx="2.81" ry="2.73" fill="#ffffff"/>
              <ellipse cx="40.27" cy="29.99" rx="2.81" ry="2.73" fill="#ffffff"/>
              <path fill="#ffffff" d="M44.48,40.89c-.3,0-.6-.06-.9-.17-13.02-5.23-27.35-5.24-40.36-.02-1.23.49-2.63-.1-3.09-1.32s.25-2.46,1.43-2.93c14.1-5.62,29.61-5.61,43.71.03,1.13.45,1.8,1.66,1.45,2.79-.31,1.01-1.25,1.62-2.25,1.62ZM8.89,30.45v-11.67c0-1.24-.96-2.36-2.24-2.42-1.34-.06-2.45.98-2.45,2.27v11.67c0,1.24.96,2.36,2.24,2.42,1.34.06,2.45-.98,2.45-2.27ZM17.32,28.73V2.42c0-1.24-.96-2.36-2.24-2.42-1.34-.06-2.45.98-2.45,2.27v26.31c0,1.24.96,2.36,2.24,2.42,1.34.06,2.45-.98,2.45-2.27ZM25.75,28.17V9.23c0-1.24-.96-2.36-2.24-2.42-1.34-.06-2.45.98-2.45,2.27v18.93c0,1.24.96,2.36,2.24,2.42,1.34.06,2.45-.98,2.45-2.27ZM34.18,28.73V11.96c0-1.24-.96-2.36-2.24-2.42-1.34-.06-2.45.98-2.45,2.27v16.77c0,1.24.96,2.36,2.24,2.42,1.34.06,2.45-.98,2.45-2.27ZM37.93,9.09v13.49c0,1.24.96,2.36,2.24,2.42,1.34.06,2.45-.98,2.45-2.27v-13.49c0-1.24-.96-2.36-2.24-2.42-1.34-.06-2.45.98-2.45,2.27Z"/>
              <path fill="#ffffff" d="M105.01,31.47c0,.27-.22.48-.5.48h-10.58c-.27,0-.5-.22-.5-.48v-3.33c0-.27.22-.48.5-.48h5.39s.03-.02.02-.03c-.83-2.38-2.94-3.96-5.91-3.96-4.46,0-8.17,4.47-5.89,9.02.95,1.89,3.59,3.42,5.76,3.45,1.79.02,2.86-.39,4.26-1.58.2-.17.5-.17.69.02l2.81,2.81c.19.19.19.51-.01.69-2.02,1.79-4.65,2.85-7.56,2.85-6.59,0-11.38-4.27-11.52-10.6-.14-6.52,5.72-11.51,12.42-11.13,6.18.36,10.22,4.38,10.62,10.85,0,0,0,0,0,0v1.41Z"/>
              <path fill="#ffffff" d="M112.05,39.96c0,.27.22.48.5.48h4.77c.27,0,.5-.22.5-.48v-12.49c0-1.6,1.13-2.6,2.67-2.6h1.15c.27,0,.5-.22.5-.48v-4.23c0-.27-.22-.48-.5-.48h-1.56c-4.63,0-8.02,3.19-8.02,7.78v12.49Z"/>
              <path fill="#ffffff" d="M129.04,39.96c0,.27.22.48.5.48h4.77c.27,0,.5-.22.5-.48v-12.49c0-1.6,1.13-2.6,2.67-2.6h1.15c.27,0,.5-.22.5-.48v-4.23c0-.27-.22-.48-.5-.48h-1.56c-4.63,0-8.02,3.19-8.02,7.78v12.49Z"/>
              <path fill="#ffffff" d="M161.76,19.96c-7.72-2.9-15.73,2.78-15.73,10.1,0,6.49,4.53,10.88,11.01,10.88,2.65,0,5-.88,6.34-2.26.01-.01.04,0,.04.01v1.26c0,.27.22.48.5.48h4.67c.27,0,.5-.22.5-.48v-9.33c0-4.66-2.84-8.99-7.32-10.67ZM163.29,30.64c-.25,2.71-2.25,4.8-5.09,5.07-3.51.34-6.57-2.57-6.39-5.99.15-2.97,2.34-5.14,5.29-5.34,3.56-.25,6.51,2.81,6.19,6.26Z"/>
              <path fill="#ffffff" d="M74.92,24.39v-4.23c0-.27-.22-.48-.5-.48h-3.8s-.02,0-.02-.02v-5.57c0-.27-.22-.48-.5-.48h-4.77c-.27,0-.5.22-.5.48v5.57s-.01.02-.02.02h-3.44c-.27,0-.5.22-.5.48v4.23c0,.27.22.48.5.48h3.44s.02,0,.02.02v7.76c0,4.59,3.39,7.78,8.02,7.78h1.56c.27,0,.5-.22.5-.48v-4.23c0-.27-.22-.48-.5-.48h-1.15c-1.54,0-2.67-1-2.67-2.6v-7.76s.01-.02.02-.02h3.8c.27,0,.5-.22.5-.48Z"/>
          </g>

          <text x="85" y="125" text-anchor="middle" font-family="Montserrat, sans-serif" font-weight="600" font-size="12" fill="#ffffff">Phần mềm quản lý</text>
          <text x="85" y="143" text-anchor="middle" font-family="Montserrat, sans-serif" font-weight="600" font-size="12" fill="#ffffff">chấm công</text>

          <rect x="49" y="163" width="72" height="30" fill="#e49025" rx="15"/>
          <text x="85" y="183" text-anchor="middle" font-family="Montserrat, sans-serif" font-weight="700" font-size="11" fill="#ffffff" letter-spacing="1">MIỄN PHÍ</text>
        </g>

        <!-- ================================================================= -->
        <!-- LAYER 4: ARROWS + LABELS (rendered LAST = on top of everything)
             v21: arrows realigned symmetric around terra HRM center (y=260),
                  static dashes replaced with traveling dot (animateMotion)  -->

        <!-- Gap 1: K to terra HRM (2 arrows + devices) -->
        <g font-family="Montserrat, sans-serif" font-weight="700" font-size="10" fill="#5a716d" letter-spacing="1">
          <!-- TOP arrow @ y=200 -->
          <text x="370" y="185" text-anchor="middle">DỮ LIỆU</text>
          <line x1="325" y1="200" x2="412" y2="200" stroke="#14796c" stroke-width="2" stroke-linecap="round"/>
          <polygon points="404,196 415,200 404,204" fill="#14796c"/>
          <circle r="3" fill="#e49025" stroke="#ffffff" stroke-width="1">
            <animateMotion dur="1.8s" repeatCount="indefinite" path="M325 200 L412 200"/>
          </circle>

          <!-- Devices stack centered at y=250 -->
          <g transform="translate(346, 240)" stroke="#0a564c" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round" opacity="0.65">
            <rect x="0" y="0" width="24" height="14" rx="2"/>
            <line x1="-2" y1="17" x2="26" y2="17"/>
            <rect x="34" y="-1" width="14" height="22" rx="2"/>
            <line x1="38" y1="17" x2="44" y2="17"/>
          </g>

          <!-- BOTTOM arrow @ y=320 -->
          <text x="370" y="305" text-anchor="middle">DỮ LIỆU</text>
          <line x1="325" y1="320" x2="412" y2="320" stroke="#14796c" stroke-width="2" stroke-linecap="round"/>
          <polygon points="404,316 415,320 404,324" fill="#14796c"/>
          <circle r="3" fill="#e49025" stroke="#ffffff" stroke-width="1">
            <animateMotion dur="1.8s" repeatCount="indefinite" path="M325 320 L412 320" begin="0.9s"/>
          </circle>
        </g>

        <!-- Gap 2: terra HRM bidirectional with VPO (SAME y positions as gap 1: 200 and 320) -->
        <g font-family="Montserrat, sans-serif" font-weight="700" font-size="10" fill="#5a716d" letter-spacing="1">
          <!-- TOP: terra HRM to VPO @ y=200 -->
          <text x="675" y="172" text-anchor="middle">DỮ LIỆU</text>
          <text x="675" y="186" text-anchor="middle">NHÂN SỰ</text>
          <line x1="608" y1="200" x2="740" y2="200" stroke="#14796c" stroke-width="2" stroke-linecap="round"/>
          <polygon points="732,196 743,200 732,204" fill="#14796c"/>
          <circle r="3" fill="#e49025" stroke="#ffffff" stroke-width="1">
            <animateMotion dur="2.2s" repeatCount="indefinite" path="M608 200 L740 200"/>
          </circle>

          <!-- BOTTOM: VPO to terra HRM @ y=320 (LEFT-pointing arrow, dot moves RIGHT TO LEFT) -->
          <text x="675" y="292" text-anchor="middle">DỮ LIỆU</text>
          <text x="675" y="306" text-anchor="middle">BẢNG LƯƠNG</text>
          <line x1="608" y1="320" x2="740" y2="320" stroke="#14796c" stroke-width="2" stroke-linecap="round"/>
          <polygon points="616,316 605,320 616,324" fill="#14796c"/>
          <circle r="3" fill="#e49025" stroke="#ffffff" stroke-width="1">
            <animateMotion dur="2.2s" repeatCount="indefinite" path="M740 320 L608 320"/>
          </circle>
        </g>

        <!-- Gap 3: VPO to Cơ quan (Báo cáo) -->
        <g font-family="Montserrat, sans-serif" font-weight="700" font-size="10" fill="#5a716d" letter-spacing="1">
          <text x="1120" y="245" text-anchor="middle">BÁO CÁO</text>
          <line x1="1078" y1="260" x2="1162" y2="260" stroke="#14796c" stroke-width="2" stroke-linecap="round"/>
          <polygon points="1154,256 1165,260 1154,264" fill="#14796c"/>
          <circle r="3" fill="#e49025" stroke="#ffffff" stroke-width="1">
            <animateMotion dur="1.8s" repeatCount="indefinite" path="M1078 260 L1162 260"/>
          </circle>
        </g>

      </svg>

      <!-- ===================== MOBILE version (<768px) - vertical SVG flow ===================== -->
      <svg class="bpv20-svg bpv20-svg--mobile" viewBox="0 0 400 1340" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet" role="img" aria-label="Mô hình BPaaS terra (mobile)">

        <defs>
          <filter id="bpv20m-shadow" x="-10%" y="-10%" width="120%" height="120%">
            <feDropShadow dx="0" dy="3" stdDeviation="6" flood-color="#073130" flood-opacity="0.08"/>
          </filter>
          <filter id="bpv20m-shadow-hrm" x="-30%" y="-30%" width="160%" height="160%">
            <feDropShadow dx="0" dy="4" stdDeviation="10" flood-color="#e49025" flood-opacity="0.25"/>
          </filter>
        </defs>

        <!-- LAYER 1: Container backgrounds -->
        <rect x="20" y="20" width="360" height="240" fill="#d4f0e0" rx="14" filter="url(#bpv20m-shadow)"/>
        <rect x="20" y="620" width="360" height="290" fill="#0a564c" rx="14" filter="url(#bpv20m-shadow)"/>
        <rect x="20" y="1000" width="360" height="320" fill="#fbfaf6" stroke="#e5e7eb" stroke-width="1" rx="14" filter="url(#bpv20m-shadow)"/>

        <!-- LAYER 2: Container content -->
        <!-- KHÁCH HÀNG -->
        <g transform="translate(20, 20)">
          <rect x="80" y="16" width="200" height="36" fill="#ffffff" rx="9"/>
          <text x="180" y="40" text-anchor="middle" font-family="Montserrat, sans-serif" font-weight="700" font-size="15" fill="#0a564c">Khách hàng</text>

          <rect x="14" y="68" width="166" height="158" fill="#ffffff" stroke="rgba(7,49,48,0.05)" rx="9"/>
          <rect x="26" y="80" width="28" height="28" fill="rgba(20,121,108,0.12)" rx="7"/>
          <g transform="translate(30, 86)" stroke="#0a564c" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round">
            <circle cx="6" cy="5" r="2.5"/>
            <circle cx="14" cy="5" r="2"/>
            <path d="M2 15 a 4 4 0 0 1 8 0 M10 15 a 3 3 0 0 1 7 0"/>
          </g>
          <text x="60" y="99" font-family="Montserrat, sans-serif" font-weight="700" font-size="13" fill="#073130">Nhân viên</text>
          <g font-family="Montserrat, sans-serif" font-size="11.5" fill="#073130">
            <circle cx="26" cy="130" r="2" fill="#14796c"/>
            <text x="34" y="134">Chấm công</text>
            <circle cx="26" cy="152" r="2" fill="#14796c"/>
            <text x="34" y="156">Xin đơn</text>
            <circle cx="26" cy="174" r="2" fill="#14796c"/>
            <text x="34" y="178">Xem phiếu lương</text>
          </g>

          <rect x="190" y="68" width="156" height="158" fill="#ffffff" stroke="rgba(7,49,48,0.05)" rx="9"/>
          <rect x="202" y="80" width="28" height="28" fill="rgba(20,121,108,0.12)" rx="7"/>
          <g transform="translate(206, 86)" stroke="#0a564c" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round">
            <circle cx="10" cy="4" r="3.5"/>
            <path d="M2 17 a 7 7 0 0 1 14 0"/>
          </g>
          <text x="236" y="99" font-family="Montserrat, sans-serif" font-weight="700" font-size="13" fill="#073130">Quản lý</text>
          <g font-family="Montserrat, sans-serif" font-size="11.5" fill="#073130">
            <circle cx="202" cy="130" r="2" fill="#14796c"/>
            <text x="210" y="134">Quản lý thông tin</text>
            <circle cx="202" cy="152" r="2" fill="#14796c"/>
            <text x="210" y="156">Phê duyệt</text>
          </g>
        </g>

        <!-- VPO -->
        <g transform="translate(20, 620)">
          <rect x="80" y="16" width="200" height="36" fill="rgba(255,255,255,0.10)" stroke="rgba(255,255,255,0.18)" stroke-width="1" rx="9"/>
          <text x="180" y="40" text-anchor="middle" font-family="Montserrat, sans-serif" font-weight="700" font-size="15" fill="#ffffff">VPO</text>
          <rect x="14" y="68" width="332" height="208" fill="rgba(255,255,255,0.08)" rx="9"/>
          <text x="30" y="92" font-family="Montserrat, sans-serif" font-weight="700" font-size="10.5" fill="#aaefdb" letter-spacing="1.5">ĐỘI NGŨ CHUYÊN GIA</text>
          <line x1="30" y1="100" x2="330" y2="100" stroke="rgba(255,255,255,0.10)" stroke-width="1"/>
          <g font-family="Montserrat, sans-serif" font-size="13" fill="#ffffff">
            <circle cx="32" cy="128" r="2.5" fill="#aaefdb"/>
            <text x="42" y="132">Thiết lập hệ thống</text>
            <circle cx="32" cy="158" r="2.5" fill="#aaefdb"/>
            <text x="42" y="162">Quản lý Tiền lương</text>
            <circle cx="32" cy="188" r="2.5" fill="#aaefdb"/>
            <text x="42" y="192">Thuế Thu nhập cá nhân</text>
            <circle cx="32" cy="218" r="2.5" fill="#aaefdb"/>
            <text x="42" y="222">Bảo hiểm Xã hội</text>
            <circle cx="32" cy="248" r="2.5" fill="#aaefdb"/>
            <text x="42" y="252">Công tác hỗ trợ khác</text>
          </g>
        </g>

        <!-- CƠ QUAN -->
        <g transform="translate(20, 1000)">
          <rect x="75" y="16" width="210" height="36" fill="#ffffff" stroke="rgba(7,49,48,0.05)" rx="9"/>
          <text x="180" y="40" text-anchor="middle" font-family="Montserrat, sans-serif" font-weight="700" font-size="15" fill="#0a564c">Cơ quan nhà nước</text>

          <rect x="14" y="68" width="166" height="232" fill="#ffffff" stroke="rgba(7,49,48,0.05)" rx="9"/>
          <rect x="26" y="80" width="28" height="28" fill="rgba(228,144,37,0.12)" rx="7"/>
          <g transform="translate(30, 85)" stroke="#e49025" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round">
            <path d="M10 1 L19 7 H1 Z"/>
            <line x1="4" y1="9" x2="4" y2="17"/>
            <line x1="8" y1="9" x2="8" y2="17"/>
            <line x1="12" y1="9" x2="12" y2="17"/>
            <line x1="16" y1="9" x2="16" y2="17"/>
          </g>
          <text x="60" y="99" font-family="Montserrat, sans-serif" font-weight="700" font-size="13" fill="#073130">Cơ quan Thuế</text>
          <g font-family="Montserrat, sans-serif" font-size="11.5" fill="#073130">
            <circle cx="26" cy="138" r="2" fill="#14796c"/>
            <text x="34" y="142">Tờ khai thuế TNCN</text>
            <circle cx="26" cy="162" r="2" fill="#14796c"/>
            <text x="34" y="166">Quyết toán thuế</text>
          </g>

          <rect x="190" y="68" width="156" height="232" fill="#ffffff" stroke="rgba(7,49,48,0.05)" rx="9"/>
          <rect x="202" y="80" width="28" height="28" fill="rgba(228,144,37,0.12)" rx="7"/>
          <g transform="translate(206, 85)" stroke="#e49025" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round">
            <path d="M10 1 L18 3 v5 c0 4 -3 7 -8 8 c-5 -1 -8 -4 -8 -8 V3 z"/>
            <path d="M7 9 l2 2 4 -4"/>
          </g>
          <text x="236" y="99" font-family="Montserrat, sans-serif" font-weight="700" font-size="13" fill="#073130">Cơ quan</text>
          <text x="236" y="115" font-family="Montserrat, sans-serif" font-weight="700" font-size="13" fill="#073130">BHXH</text>
          <g font-family="Montserrat, sans-serif" font-size="11.5" fill="#073130">
            <circle cx="202" cy="155" r="2" fill="#14796c"/>
            <text x="210" y="159">Báo cáo BHXH,</text>
            <text x="210" y="173">BHYT, BHTN</text>
            <circle cx="202" cy="200" r="2" fill="#14796c"/>
            <text x="210" y="204">Nộp đúng deadline</text>
          </g>
        </g>

        <!-- LAYER 3: terra HRM floating bridge -->
        <g transform="translate(120, 330)" filter="url(#bpv20m-shadow-hrm)">
          <rect width="160" height="200" fill="#0a564c" stroke="#e49025" stroke-width="2.5" rx="12"/>
          <rect x="20" y="14" width="120" height="30" fill="rgba(255,255,255,0.10)" stroke="rgba(255,255,255,0.18)" stroke-width="1" rx="7"/>
          <text x="80" y="34" text-anchor="middle" font-family="Montserrat, sans-serif" font-weight="700" font-size="12" fill="#ffffff">Phần mềm</text>
          <g transform="translate(38, 58) scale(0.50)" fill="#ffffff">
            <ellipse cx="6.55" cy="11.36" rx="2.81" ry="2.73" fill="#ffffff"/>
              <ellipse cx="31.84" cy="4.54" rx="2.81" ry="2.73" fill="#ffffff"/>
              <ellipse cx="40.27" cy="29.99" rx="2.81" ry="2.73" fill="#ffffff"/>
              <path fill="#ffffff" d="M44.48,40.89c-.3,0-.6-.06-.9-.17-13.02-5.23-27.35-5.24-40.36-.02-1.23.49-2.63-.1-3.09-1.32s.25-2.46,1.43-2.93c14.1-5.62,29.61-5.61,43.71.03,1.13.45,1.8,1.66,1.45,2.79-.31,1.01-1.25,1.62-2.25,1.62ZM8.89,30.45v-11.67c0-1.24-.96-2.36-2.24-2.42-1.34-.06-2.45.98-2.45,2.27v11.67c0,1.24.96,2.36,2.24,2.42,1.34.06,2.45-.98,2.45-2.27ZM17.32,28.73V2.42c0-1.24-.96-2.36-2.24-2.42-1.34-.06-2.45.98-2.45,2.27v26.31c0,1.24.96,2.36,2.24,2.42,1.34.06,2.45-.98,2.45-2.27ZM25.75,28.17V9.23c0-1.24-.96-2.36-2.24-2.42-1.34-.06-2.45.98-2.45,2.27v18.93c0,1.24.96,2.36,2.24,2.42,1.34.06,2.45-.98,2.45-2.27ZM34.18,28.73V11.96c0-1.24-.96-2.36-2.24-2.42-1.34-.06-2.45.98-2.45,2.27v16.77c0,1.24.96,2.36,2.24,2.42,1.34.06,2.45-.98,2.45-2.27ZM37.93,9.09v13.49c0,1.24.96,2.36,2.24,2.42,1.34.06,2.45-.98,2.45-2.27v-13.49c0-1.24-.96-2.36-2.24-2.42-1.34-.06-2.45.98-2.45,2.27Z"/>
              <path fill="#ffffff" d="M105.01,31.47c0,.27-.22.48-.5.48h-10.58c-.27,0-.5-.22-.5-.48v-3.33c0-.27.22-.48.5-.48h5.39s.03-.02.02-.03c-.83-2.38-2.94-3.96-5.91-3.96-4.46,0-8.17,4.47-5.89,9.02.95,1.89,3.59,3.42,5.76,3.45,1.79.02,2.86-.39,4.26-1.58.2-.17.5-.17.69.02l2.81,2.81c.19.19.19.51-.01.69-2.02,1.79-4.65,2.85-7.56,2.85-6.59,0-11.38-4.27-11.52-10.6-.14-6.52,5.72-11.51,12.42-11.13,6.18.36,10.22,4.38,10.62,10.85,0,0,0,0,0,0v1.41Z"/>
              <path fill="#ffffff" d="M112.05,39.96c0,.27.22.48.5.48h4.77c.27,0,.5-.22.5-.48v-12.49c0-1.6,1.13-2.6,2.67-2.6h1.15c.27,0,.5-.22.5-.48v-4.23c0-.27-.22-.48-.5-.48h-1.56c-4.63,0-8.02,3.19-8.02,7.78v12.49Z"/>
              <path fill="#ffffff" d="M129.04,39.96c0,.27.22.48.5.48h4.77c.27,0,.5-.22.5-.48v-12.49c0-1.6,1.13-2.6,2.67-2.6h1.15c.27,0,.5-.22.5-.48v-4.23c0-.27-.22-.48-.5-.48h-1.56c-4.63,0-8.02,3.19-8.02,7.78v12.49Z"/>
              <path fill="#ffffff" d="M161.76,19.96c-7.72-2.9-15.73,2.78-15.73,10.1,0,6.49,4.53,10.88,11.01,10.88,2.65,0,5-.88,6.34-2.26.01-.01.04,0,.04.01v1.26c0,.27.22.48.5.48h4.67c.27,0,.5-.22.5-.48v-9.33c0-4.66-2.84-8.99-7.32-10.67ZM163.29,30.64c-.25,2.71-2.25,4.8-5.09,5.07-3.51.34-6.57-2.57-6.39-5.99.15-2.97,2.34-5.14,5.29-5.34,3.56-.25,6.51,2.81,6.19,6.26Z"/>
              <path fill="#ffffff" d="M74.92,24.39v-4.23c0-.27-.22-.48-.5-.48h-3.8s-.02,0-.02-.02v-5.57c0-.27-.22-.48-.5-.48h-4.77c-.27,0-.5.22-.5.48v5.57s-.01.02-.02.02h-3.44c-.27,0-.5.22-.5.48v4.23c0,.27.22.48.5.48h3.44s.02,0,.02.02v7.76c0,4.59,3.39,7.78,8.02,7.78h1.56c.27,0,.5-.22.5-.48v-4.23c0-.27-.22-.48-.5-.48h-1.15c-1.54,0-2.67-1-2.67-2.6v-7.76s.01-.02.02-.02h3.8c.27,0,.5-.22.5-.48Z"/>
          </g>
          <text x="80" y="118" text-anchor="middle" font-family="Montserrat, sans-serif" font-weight="600" font-size="11" fill="#ffffff">Phần mềm quản lý</text>
          <text x="80" y="134" text-anchor="middle" font-family="Montserrat, sans-serif" font-weight="600" font-size="11" fill="#ffffff">chấm công</text>
          <rect x="48" y="152" width="64" height="28" fill="#e49025" rx="14"/>
          <text x="80" y="170" text-anchor="middle" font-family="Montserrat, sans-serif" font-weight="700" font-size="10.5" fill="#ffffff" letter-spacing="1">MIỄN PHÍ</text>
        </g>

        <!-- LAYER 4: Arrows + labels on top (v21: moving dots) -->
        <g font-family="Montserrat, sans-serif" font-weight="700" font-size="10" fill="#5a716d" letter-spacing="1">
          <!-- K to terra HRM (down) -->
          <text x="200" y="285" text-anchor="middle">DỮ LIỆU</text>
          <line x1="200" y1="294" x2="200" y2="322" stroke="#14796c" stroke-width="2" stroke-linecap="round"/>
          <polygon points="196,314 200,325 204,314" fill="#14796c"/>
          <circle r="3" fill="#e49025" stroke="#ffffff" stroke-width="1">
            <animateMotion dur="1.5s" repeatCount="indefinite" path="M200 294 L200 322"/>
          </circle>

          <!-- terra HRM to VPO (down) - left side -->
          <text x="140" y="558" text-anchor="middle">DỮ LIỆU</text>
          <text x="140" y="572" text-anchor="middle">NHÂN SỰ</text>
          <line x1="140" y1="582" x2="140" y2="610" stroke="#14796c" stroke-width="2" stroke-linecap="round"/>
          <polygon points="136,602 140,613 144,602" fill="#14796c"/>
          <circle r="3" fill="#e49025" stroke="#ffffff" stroke-width="1">
            <animateMotion dur="1.5s" repeatCount="indefinite" path="M140 582 L140 610"/>
          </circle>

          <!-- VPO to terra HRM (up) - right side, dot moves BOTTOM TO TOP -->
          <text x="260" y="558" text-anchor="middle">DỮ LIỆU</text>
          <text x="260" y="572" text-anchor="middle">BẢNG LƯƠNG</text>
          <line x1="260" y1="582" x2="260" y2="610" stroke="#14796c" stroke-width="2" stroke-linecap="round"/>
          <polygon points="256,590 260,579 264,590" fill="#14796c"/>
          <circle r="3" fill="#e49025" stroke="#ffffff" stroke-width="1">
            <animateMotion dur="1.5s" repeatCount="indefinite" path="M260 610 L260 582"/>
          </circle>

          <!-- VPO to Cơ quan (down) -->
          <text x="200" y="940" text-anchor="middle">BÁO CÁO</text>
          <line x1="200" y1="950" x2="200" y2="978" stroke="#14796c" stroke-width="2" stroke-linecap="round"/>
          <polygon points="196,970 200,981 204,970" fill="#14796c"/>
          <circle r="3" fill="#e49025" stroke="#ffffff" stroke-width="1">
            <animateMotion dur="1.5s" repeatCount="indefinite" path="M200 950 L200 978"/>
          </circle>
        </g>

      </svg>

    </div>
CSS
/* ==========================================================================
   v19 - BPaaS WORKFLOW (clean SVG, no wrapper boxes)
   Desktop: horizontal SVG (≥768px), Mobile: vertical SVG (<768px)
   ========================================================================== */
.bpv20-wrap {
  width: 100%;
}
/* SVG variants visibility */
.bpv20-svg {
  display: block;
  width: 100%;
  height: auto;
  max-width: 1400px;
  margin: 0 auto;
}
.bpv20-svg--desktop { display: block; }
.bpv20-svg--mobile { display: none; }
@media (max-width: 768px) {
.bpv20-svg--desktop { display: none; }
.bpv20-svg--mobile {
    display: block;
    max-width: 420px;
  }
}

/* chấm dữ liệu to + phát sáng (dùng cho cả light và strong) */
.terra-page .bp-wow-light .bpv20-svg circle[fill="#e49025"],
.terra-page .bp-wow-strong .bpv20-svg circle[fill="#e49025"] {
  filter: drop-shadow(0 0 5px rgba(228,144,37,0.9)); r: 5;
}
/* mũi tên đậm hơn để thấy dòng chảy từ xa */
.terra-page .bp-wow-light .bpv20-svg line[stroke="#14796c"],
.terra-page .bp-wow-strong .bpv20-svg line[stroke="#14796c"] { stroke-width: 2; }

/* --- chỉ STRONG --- */
/* viền cam của node terra HRM "thở" */
.terra-page .bp-wow-strong .bpv20-svg g[filter="url(#bpv20-shadow-hrm)"] > rect:first-of-type,
.terra-page .bp-wow-strong .bpv20-svg g[filter="url(#bpv20m-shadow-hrm)"] > rect:first-of-type {
  animation: bpBreathe 2.6s ease-in-out infinite; transform-box: fill-box; transform-origin: center;
}
@keyframes bpBreathe { 0%, 100% { stroke-width: 4; } 50% { stroke-width: 7.5; } }
/* ba trụ cột phập phồng lệch nhịp khi dữ liệu chảy */
.terra-page .bp-wow-strong .bpv20-svg > rect:nth-of-type(1),
.terra-page .bp-wow-strong .bpv20-svg > rect:nth-of-type(2),
.terra-page .bp-wow-strong .bpv20-svg > rect:nth-of-type(3) {
  transform-box: fill-box; transform-origin: center; animation: bpPillar 4.4s ease-in-out infinite;
}
.terra-page .bp-wow-strong .bpv20-svg > rect:nth-of-type(2) { animation-delay: .5s; }
.terra-page .bp-wow-strong .bpv20-svg > rect:nth-of-type(3) { animation-delay: 1s; }
@keyframes bpPillar { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.012); } }
/* draw-in khi cuộn tới: cả sơ đồ mờ + nhấc lên */
.terra-page .bp-wow-strong .bpv20-wrap {
  opacity: 0; transform: translateY(24px);
  transition: opacity .7s ease, transform .7s cubic-bezier(.2,.7,.2,1);
}
.terra-page .wmd-banner.is-in .bp-wow-strong .bpv20-wrap { opacity: 1; transform: translateY(0); }

@media (prefers-reduced-motion: reduce) {
  .terra-page .bp-wow-strong .bpv20-svg g[filter="url(#bpv20-shadow-hrm)"] > rect:first-of-type,
  .terra-page .bp-wow-strong .bpv20-svg > rect:nth-of-type(1),
  .terra-page .bp-wow-strong .bpv20-svg > rect:nth-of-type(2),
  .terra-page .bp-wow-strong .bpv20-svg > rect:nth-of-type(3) { animation: none; }
  .terra-page .bp-wow-strong .bpv20-wrap { opacity: 1; transform: none; }
}
final-cta-split validated composition ▼ chi tiết

Khối chốt chia đôi: một bên bằng chứng, một bên ô điền. Người còn do dự nhìn thấy lý do ngay tại khoảnh khắc tay đưa lên bấm, nên không phải cuộn ngược lên tìm. Giảm quãng đường giữa nghi ngờ và hành động xuống còn một cái liếc mắt.

Ràng buộc: Là biến thể của final-cta cho trang dịch vụ; cùng các iron rule; form theo forms/cf7-form-kit.
Hợp cặp: forms/cf7-form-kit
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/compositions/final-cta-split.md
Khung: Nền: Mở riêng ↗
HTML
<div class="final-section" style="background:var(--teal-dark);border-radius:12px;padding:40px">
  <div class="final-inner" style="display:grid;grid-template-columns:1fr 1fr;gap:32px;align-items:start">
    <div class="final-left">
      <h3 style="color:#fff;margin:0 0 14px">Japanese manufacturer, 250 staff</h3>
      <div class="case-box" style="background:rgba(255,255,255,.08);border-radius:10px;padding:20px">
        <div class="case-stats" style="display:flex;gap:28px">
          <div><div class="case-stat-num" style="color:var(--green-light);font-size:30px;font-weight:700">6 tuần</div><div class="case-stat-label" style="color:rgba(255,255,255,.7);font-size:12px">transition time</div></div>
          <div><div class="case-stat-num" style="color:var(--green-light);font-size:30px;font-weight:700">0</div><div class="case-stat-label" style="color:rgba(255,255,255,.7);font-size:12px">missed filings since</div></div>
        </div>
      </div>
    </div>
    <div class="form-card" style="background:#fff;border-radius:12px;padding:24px">
      <p class="form-sub" style="font-size:13px;color:var(--text-body);margin:0 0 14px">Đánh giá miễn phí quy trình hiện tại</p>
      <!-- CONTACT_FORM -->
      <form><input type="email" placeholder="Email công ty" style="width:100%;padding:11px;border:1px solid var(--border);border-radius:8px;margin-bottom:10px;font-family:inherit"><button type="submit" style="width:100%;border-radius:999px;padding:12px;background:var(--orange);color:#fff;border:none;font-weight:600;font-family:inherit">Đăng ký trao đổi</button></form>
    </div>
  </div>
</div>
CSS
.final-section{background:linear-gradient(135deg,#14796C 0%,#0A564C 55%,#073130 100%);position:relative;overflow:hidden;padding:clamp(56px,8vh,80px) 0}
.final-inner{position:relative;z-index:1;display:grid;grid-template-columns:1fr 1fr;gap:clamp(32px,5vw,72px);align-items:start}

.final-left .eyebrow{color:var(--green-light)}
.final-left h2{color:#fff!important;margin-bottom:12px}
.final-left p{color:rgba(255,255,255,0.7);margin-bottom:28px}

.case-box{background:rgba(255,255,255,0.07);border:1px solid rgba(170,239,219,0.15);border-radius:14px;padding:24px}
.case-box p{font-size:16px;color:rgba(255,255,255,0.72);line-height:1.7;margin-bottom:18px}
.case-stats{display:flex;gap:20px;flex-wrap:wrap}
.case-stat-num{font-size:22px;font-weight:800;color:var(--green-light);display:block;line-height:1}
.case-stat-label{font-size:14px;color:rgba(255,255,255,0.5);font-weight:500;margin-top:4px;display:block}

.form-card{background:var(--white);border-radius:16px;padding:clamp(24px,3vw,36px);box-shadow:0 16px 48px rgba(7,49,48,0.2)}
.form-card h3{color:var(--teal)!important;margin-bottom:6px}
.form-sub{font-size:16px;color:var(--text-muted);margin-bottom:24px}

@media(max-width:1024px){.final-inner{grid-template-columns:1fr}}
final-cta validated composition ▼ chi tiết

Khối kêu gọi hành động ở cuối trang. Nền màu tối, ở giữa có một nút cam duy nhất để người xem bấm liên hệ. Đây là điểm chốt cuối cùng của trang.

Ràng buộc: Nền dùng màu teal-dark (iron rule 6). Dòng chữ tin cậy (ISO, số khách hàng...) giữ nguyên văn theo CORE_RULES § 1. Nút cam duy nhất của cả trang thường đặt ở đây.
Hợp cặp: decor/corner-circles, forms/cf7-form-kit
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/compositions/final-cta.md
Khung: Nền: Mở riêng ↗
HTML
<section class="cta">
  <img src="[atmospheric photo URL]" alt="" class="cta-bg-photo" loading="lazy">
  <div class="container">
    <div class="cta-layout">
      <div class="cta-content">
        <h2>[CTA H2]</h2>
        <p>[supporting sub-headline]</p>
        <ul class="cta-trust-list">
          <li>[trust point 1]</li>
          <li>[trust point 2]</li>
          <li>[trust point 3]</li>
        </ul>
        <p class="cta-trust-line">Part of I-GLOCAL Group · ISO/IEC 27001:2022 · 250+ FDI clients</p>
      </div>
      <form class="cta-form">
        <!-- Contact form -->
      </form>
    </div>
  </div>
</section>
CSS
.cta {
  background: var(--teal);
  color: var(--white);
  padding: clamp(80px, 10vh, 120px) 0;
  position: relative;
  overflow: hidden;
}
.cta-bg-photo {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0.18;
  mix-blend-mode: luminosity;
  pointer-events: none;
  z-index: 0;
}
/* Use decorative blobs from § 4.2 */
.cta::before {  /* blob-tr-teal */
  content: ''; position: absolute;
  top: -200px; right: -100px;
  width: 460px; height: 460px;
  background: radial-gradient(circle, rgba(170,239,219,0.18), transparent 70%);
  pointer-events: none;
  z-index: 1;
}
.cta::after {  /* blob-bl-orange */
  content: ''; position: absolute;
  bottom: -220px; left: -80px;
  width: 500px; height: 500px;
  background: radial-gradient(circle, rgba(228,144,37,0.16), transparent 65%);
  pointer-events: none;
  z-index: 1;
}
.cta-layout {
  position: relative;
  z-index: 2;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 64px;
  align-items: center;
}
.cta-content h2 {
  color: var(--white);
  margin-bottom: 22px;
  max-width: 480px;
}
.cta-content > p {
  font-size: 16px;
  color: rgba(255,255,255,0.85);
  line-height: 1.7;
  margin-bottom: 24px;
  max-width: 460px;
}
.cta-form {
  background: var(--white);
  color: var(--text-primary);
  border-radius: 20px;
  padding: 36px 32px;
  box-shadow: var(--shadow-4-premium);
}
.cta-trust-line {
  margin-top: 30px;
  padding-top: 22px;
  border-top: 1px solid rgba(170,239,219,0.22);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.65);
}
@media (max-width: 1024px) {
  .cta-layout { grid-template-columns: 1fr; gap: 36px; }
}
hero-homepage-light locked composition ▼ chi tiết

Vùng đầu trang sáng và thoáng, ba lớp nền chồng nhau tạo chiều sâu mà không lớp nào giành chỗ với chữ. Hai nửa cân bằng, chữ một bên và sản phẩm một bên. Đọc ra dáng thương hiệu chứ không ra dáng trang bán dịch vụ.

Ràng buộc: CHỈ dùng cho homepage (CORE_RULES § 4). Khung tối đa 1280px. Thiếu bất kỳ lớp nền nào là trang trông phẳng ngay. Sóng thoát ở cuối phải cùng màu với nền section kế tiếp, và section đó KHÔNG được có border-top. Ràng buộc chi tiết nằm ở từng element thành phần.
Nguồn gốc: homepage v5 / v6 canonical
File: brands/terra/patterns/compositions/hero-homepage-light.md
Khung: Nền: Mở riêng ↗
HTML
<section class="bg-mesh" style="padding: 48px 0 96px">
  <img src="[Pexels URL w=1600]" alt="" class="photo-wash is-visible" data-slide="0" data-no-lazy="1">
  <div class="bg-mesh-layer" aria-hidden="true"></div>
  <div class="bg-grid-layer" aria-hidden="true"></div>

  <div class="wrap">
    <div class="hero-55">
      <div class="hero-55-text">
        <span class="hero-55-eyebrow"><span class="pulse-dot"></span>[nhãn ngắn]</span>
        <h1>[H1 sentence case]</h1>
        <p class="hero-55-sub">[1-2 câu dẫn]</p>
        <div class="hero-55-cta">
          <a class="btn bo">[CTA chính]</a>
          <a class="btn bg">[CTA phụ]</a>
        </div>
      </div>
      <div class="hero-55-media">[unit — mockup, minh hoạ, thẻ tính năng]</div>
    </div>
  </div>

  <svg class="hero-wave" viewBox="0 0 1440 80" preserveAspectRatio="none" aria-hidden="true">
    <path d="M0,40 C360,80 1080,0 1440,40 L1440,80 L0,80 Z"></path>
  </svg>
</section>
CSS
/* CSS nằm ở các element thành phần. Ở đây chỉ còn phần riêng của bản lắp homepage. */
.terra-page .bg-mesh { padding: 48px 0 96px; }
.terra-page .bg-mesh h1 { font-size: clamp(30px, 4.2vw, 52px); line-height: 1.08; font-weight: 700; letter-spacing: -.022em; }
.terra-page .hero-55-sub { font-size: 17px; line-height: 1.7; color: var(--text-body); max-width: 520px; }

/* Sóng thoát: fill phải BẰNG ĐÚNG màu nền section kế tiếp, và section đó không border-top */
.terra-page .hero-wave { display: block; width: 100%; height: 80px; }
.terra-page .hero-wave path { fill: var(--bg-light); }
.terra-page .section--after-wave { border-top: none; }

@media (max-width: 900px) {
  .terra-page .bg-mesh { padding: 64px 0 80px; }
  .terra-page .hero-wave { height: 48px; }
}
hero-teal-photo locked composition ▼ chi tiết

Ảnh thật phủ kín, dìm tối bên trái để chữ đọc rõ, hé ảnh bên phải. Cột chữ hẹp neo trái, mắt vào thẳng dòng đầu rồi chạy một mạch xuống nút. Vào đề ngay, không rào đón, không nhãn mở đầu.

Ràng buộc: Mặc định cho MỌI trang trừ homepage (CORE_RULES § 4). Bỏ eyebrow. Ràng buộc chi tiết nằm ở từng element thành phần, xem `backgrounds/teal-photo-overlay` (công thức overlay + thẻ ảnh KHÓA) và `structures/hero-text-left` (cột 620px).
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH page 2026-07 (overlay variant)
File: brands/terra/patterns/compositions/hero-teal-photo.md
Khung: Nền: Mở riêng ↗
HTML
<section class="bg-tealphoto" style="padding: clamp(64px, 8vw, 100px) 0">
  <img src="[Pexels URL w=1600]" alt="" class="bg-tealphoto-img" loading="eager" fetchpriority="high" data-no-lazy="1">
  <div class="bg-tealphoto-blur" aria-hidden="true"></div>
  <div class="bg-tealphoto-overlay" aria-hidden="true"></div>
  <div class="corner-circles" aria-hidden="true"></div>
  <div class="wrap">
    <div class="hero-tl">
      <h1>[H1 sentence case] <span class="h2-accent">[cụm nhấn]</span></h1>
      <div class="hero-tl-ans"><p>[định nghĩa hoặc câu trả lời 1-2 câu]</p></div>
      <div class="hero-tl-cta">
        <a href="#[anchor]" class="btn bo">[CTA chính]</a>
        <a href="#[anchor]" class="btn bg">[CTA phụ]</a>
      </div>
    </div>
  </div>
</section>
CSS
/* CSS nằm ở các element thành phần. Ở đây chỉ còn phần riêng của bản lắp hero. */
.terra-page .bg-tealphoto h1 { color: #fff; }
.terra-page .bg-tealphoto .h2-accent { color: var(--green-light); }
strength-2banner experimental composition ▼ chi tiết

Hai banner lớn xếp dọc và lật ngược nhau, chữ trái hình phải rồi hình trái chữ phải. Nhịp so le đó ép mắt đổi hướng giữa chừng nên không lướt qua được, và mỗi banner được cả một khoảng riêng nên trông quan trọng ngang nhau. Nhấc nhẹ khi rê chuột cho biết bấm được.

Ràng buộc: Mỗi banner là thẻ <a> dẫn sang trang dịch vụ. Phần hình bên trong lắp từ element khác: banner 1 thường dùng bpaas-flow-svg (kèm layer bp-wow-strong), banner 2 dùng terra-hrm-showcase. JS reveal (.is-in) đặt cuối body. Mức T-B (hình bên trong có thể lên T-C nếu là HRM showcase). Mobile: cả hai banner xếp 1 cột, bỏ so le.
Nguồn gốc: terra homepage VI/EN ("Thế mạnh của terra", 2026-06-22)
File: brands/terra/patterns/compositions/strength-2banner.md
Khung: Nền: Mở riêng ↗
HTML
<section class="bg-white" id="strengths">
  <div class="container">
    <h2>Thế mạnh của terra</h2>
    <div class="wmd-stack">
      <!-- Banner 1: chữ trái, sơ đồ BPaaS phải -->
      <a class="wmd-banner" href="/terra-payroll">
        <div class="wmd-text">
          <h3>Tiêu đề thế mạnh 1</h3>
          <p>Mô tả ngắn một câu.</p>
          <span class="wmd-link">Tìm hiểu thêm
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
          </span>
        </div>
        <div class="wmd-visual bp-wow-strong" data-reveal>
          <!-- chèn bpaas-flow-svg .bpv20-wrap ở đây (xem compositions/bpaas-flow-svg) -->
        </div>
      </a>
      <!-- Banner 2: lật lại - mockup HRM trái, chữ phải -->
      <a class="wmd-banner wmd-banner--flip" href="/terra-hr-system">
        <div class="wmd-text">
          <h3>Tiêu đề thế mạnh 2</h3>
          <p>Mô tả ngắn một câu.</p>
          <span class="wmd-link">Tìm hiểu thêm
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
          </span>
        </div>
        <div class="wmd-visual wmd-visual--hrm">
          <!-- chèn terra-hrm-showcase (desktop + mobile) ở đây -->
        </div>
      </a>
    </div>
  </div>
</section>
CSS
.terra-page .wmd-stack { display: flex; flex-direction: column; gap: 28px; }
.terra-page .wmd-banner {
  display: grid; grid-template-columns: 1fr 1.15fr; align-items: center; gap: 0;
  border-radius: 20px; overflow: hidden; position: relative;
  background: var(--white); box-shadow: var(--shadow-2-lift);
  transition: transform .4s cubic-bezier(.2,.7,.2,1), box-shadow .4s;
}
.terra-page .wmd-banner:hover { transform: translateY(-4px); box-shadow: var(--shadow-3-float); }
.terra-page .wmd-banner--flip { grid-template-columns: 1.15fr 1fr; }
.terra-page .wmd-banner--flip .wmd-text { order: 2; }
.terra-page .wmd-banner--flip .wmd-visual { order: 1; }
.terra-page .wmd-text { padding: 44px 48px; position: relative; z-index: 2; }
.terra-page .wmd-text h3 { margin-bottom: 14px; }
.terra-page .wmd-text p { font-size: 15.5px; line-height: 1.65; color: var(--text-body); margin-bottom: 24px; }
.terra-page .wmd-link { display: inline-flex; align-items: center; gap: 8px; font-size: 14.5px; font-weight: 700; color: var(--teal-dark); }
.terra-page .wmd-link svg { transition: transform .3s; }
.terra-page .wmd-banner:hover .wmd-link svg { transform: translateX(4px); }
.terra-page .wmd-visual {
  position: relative; min-height: 320px; display: flex; align-items: center; justify-content: center;
  padding: 28px; background: linear-gradient(135deg, #f8fbfa 0%, #eef6f4 100%);
}
/* phủ mờ về phía chữ để hình hòa vào nền */
.terra-page .wmd-banner .wmd-visual::before {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, var(--white) 0%, rgba(255,255,255,0) 22%);
  z-index: 1; pointer-events: none;
}
.terra-page .wmd-banner--flip .wmd-visual::before { background: linear-gradient(270deg, var(--white) 0%, rgba(255,255,255,0) 22%); }
.terra-page .wmd-visual .bpv20-wrap { position: relative; z-index: 0; width: 100%; }
.terra-page .wmd-visual--hrm { background: linear-gradient(135deg, #eef6f4 0%, #dff3ef 100%); padding: 24px; }
.terra-page .wmd-visual--hrm .hrm-mockup { transform: scale(0.92); transform-origin: center; width: 100%; }
.terra-page .wmd-visual--hrm .hrm-app { min-height: 360px; }

@media (max-width: 767px) {
  .terra-page .wmd-banner, .terra-page .wmd-banner--flip { grid-template-columns: 1fr; }
  .terra-page .wmd-banner--flip .wmd-text { order: 1; }
  .terra-page .wmd-banner--flip .wmd-visual { order: 2; }
}
JS
/* reveal draw-in cho banner: thêm .is-in khi cuộn tới */
var wmdObs = new IntersectionObserver(function (entries) {
  entries.forEach(function (e) {
    if (e.isIntersecting) {
      var banner = e.target.closest('.wmd-banner');
      if (banner) banner.classList.add('is-in');
      wmdObs.unobserve(e.target);
    }
  });
}, { threshold: 0.25 });
root.querySelectorAll('[data-reveal]').forEach(function (el) { wmdObs.observe(el); });
terra-hrm-showcase-desktop locked composition ▼ chi tiết

Mô phỏng phần mềm terra tự chạy trong khung trình duyệt: con trỏ giả tự di tới mục "Lương & Bảo hiểm", bấm vào, dashboard đổi sang bảng phiếu lương, lặp lại sau 8 giây. Bằng chứng "đây là phần mềm thật" mạnh nhất mà không cần người demo.

Ràng buộc: KHÓA - copy nguyên (CORE_RULES § 3). Mức T-C: chỉ dùng ở trang lấy mô phỏng sản phẩm làm bằng chứng chính; lặp 8s; ẩn <=768px (bản mobile thay vào - xem terra-hrm-showcase-mobile); khi giảm chuyển động thì đứng yên ở dashboard. Logo sidebar = logo terra ngang từ BRAND_ASSETS, giữ nguyên chấm màu.
Nguồn gốc: service-page-terra-payroll-vi.html (production, harvested 2026-06-11)
File: brands/terra/patterns/compositions/terra-hrm-showcase-desktop.md
Khung: Nền: Mở riêng ↗
HTML
<div class="hrm-mockup hrm-mockup--desktop reveal reveal-delay-1">
      <!-- Browser chrome -->
      <div class="hrm-chrome">
        <div class="hrm-dots"><i></i><i></i><i></i></div>
        <div class="hrm-url">client.terra-plat.vn</div>
      </div>

      <!-- App body -->
      <div class="hrm-app">

        <!-- Sidebar -->
        <aside class="hrm-sidebar">
          <div class="hrm-logo">
            <!-- terra-logo-horizontal from BRAND_ASSETS.md -->
            <svg viewBox="0 0 169.08 40.94" xmlns="http://www.w3.org/2000/svg" class="hrm-logo-svg">
              <g>
                <ellipse cx="6.55" cy="11.36" rx="2.81" ry="2.73" fill="#78ae45"/>
                <ellipse cx="31.84" cy="4.54" rx="2.81" ry="2.73" fill="#0099da"/>
                <ellipse cx="40.27" cy="29.99" rx="2.81" ry="2.73" fill="#e48f25"/>
                <path fill="#16796d" d="M44.48,40.89c-.3,0-.6-.06-.9-.17-13.02-5.23-27.35-5.24-40.36-.02-1.23.49-2.63-.1-3.09-1.32s.25-2.46,1.43-2.93c14.1-5.62,29.61-5.61,43.71.03,1.13.45,1.8,1.66,1.45,2.79-.31,1.01-1.25,1.62-2.25,1.62ZM8.89,30.45v-11.67c0-1.24-.96-2.36-2.24-2.42-1.34-.06-2.45.98-2.45,2.27v11.67c0,1.24.96,2.36,2.24,2.42,1.34.06,2.45-.98,2.45-2.27ZM17.32,28.73V2.42c0-1.24-.96-2.36-2.24-2.42-1.34-.06-2.45.98-2.45,2.27v26.31c0,1.24.96,2.36,2.24,2.42,1.34.06,2.45-.98,2.45-2.27ZM25.75,28.17V9.23c0-1.24-.96-2.36-2.24-2.42-1.34-.06-2.45.98-2.45,2.27v18.93c0,1.24.96,2.36,2.24,2.42,1.34.06,2.45-.98,2.45-2.27ZM34.18,28.73V11.96c0-1.24-.96-2.36-2.24-2.42-1.34-.06-2.45.98-2.45,2.27v16.77c0,1.24.96,2.36,2.24,2.42,1.34.06,2.45-.98,2.45-2.27ZM37.93,9.09v13.49c0,1.24.96,2.36,2.24,2.42,1.34.06,2.45-.98,2.45-2.27v-13.49c0-1.24-.96-2.36-2.24-2.42-1.34-.06-2.45.98-2.45,2.27Z"/>
                <path fill="#58595b" d="M105.01,31.47c0,.27-.22.48-.5.48h-10.58c-.27,0-.5-.22-.5-.48v-3.33c0-.27.22-.48.5-.48h5.39s.03-.02.02-.03c-.83-2.38-2.94-3.96-5.91-3.96-4.46,0-8.17,4.47-5.89,9.02.95,1.89,3.59,3.42,5.76,3.45,1.79.02,2.86-.39,4.26-1.58.2-.17.5-.17.69.02l2.81,2.81c.19.19.19.51-.01.69-2.02,1.79-4.65,2.85-7.56,2.85-6.59,0-11.38-4.27-11.52-10.6-.14-6.52,5.72-11.51,12.42-11.13,6.18.36,10.22,4.38,10.62,10.85,0,0,0,0,0,0v1.41Z"/>
                <path fill="#58595b" d="M112.05,39.96c0,.27.22.48.5.48h4.77c.27,0,.5-.22.5-.48v-12.49c0-1.6,1.13-2.6,2.67-2.6h1.15c.27,0,.5-.22.5-.48v-4.23c0-.27-.22-.48-.5-.48h-1.56c-4.63,0-8.02,3.19-8.02,7.78v12.49Z"/>
                <path fill="#58595b" d="M129.04,39.96c0,.27.22.48.5.48h4.77c.27,0,.5-.22.5-.48v-12.49c0-1.6,1.13-2.6,2.67-2.6h1.15c.27,0,.5-.22.5-.48v-4.23c0-.27-.22-.48-.5-.48h-1.56c-4.63,0-8.02,3.19-8.02,7.78v12.49Z"/>
                <path fill="#58595b" d="M161.76,19.96c-7.72-2.9-15.73,2.78-15.73,10.1,0,6.49,4.53,10.88,11.01,10.88,2.65,0,5-.88,6.34-2.26.01-.01.04,0,.04.01v1.26c0,.27.22.48.5.48h4.67c.27,0,.5-.22.5-.48v-9.33c0-4.66-2.84-8.99-7.32-10.67ZM163.29,30.64c-.25,2.71-2.25,4.8-5.09,5.07-3.51.34-6.57-2.57-6.39-5.99.15-2.97,2.34-5.14,5.29-5.34,3.56-.25,6.51,2.81,6.19,6.26Z"/>
                <path fill="#58595b" d="M74.92,24.39v-4.23c0-.27-.22-.48-.5-.48h-3.8s-.02,0-.02-.02v-5.57c0-.27-.22-.48-.5-.48h-4.77c-.27,0-.5.22-.5.48v5.57s-.01.02-.02.02h-3.44c-.27,0-.5.22-.5.48v4.23c0,.27.22.48.5.48h3.44s.02,0,.02.02v7.76c0,4.59,3.39,7.78,8.02,7.78h1.56c.27,0,.5-.22.5-.48v-4.23c0-.27-.22-.48-.5-.48h-1.15c-1.54,0-2.67-1-2.67-2.6v-7.76s.01-.02.02-.02h3.8c.27,0,.5-.22.5-.48Z"/>
              </g>
            </svg>
          </div>
          <div class="hrm-nav-section">Individual</div>
          <nav class="hrm-nav">
            <div class="hrm-nav-item">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="7" r="4"/><path d="M5 21v-2a4 4 0 014-4h6a4 4 0 014 4v2"/></svg>
              Personal data
            </div>
            <div class="hrm-nav-item">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M16 3v4M8 3v4M3 11h18"/></svg>
              Working &amp; Leave
            </div>
            <div class="hrm-nav-item hrm-nav-item--target" id="hrm-target-salary">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="20" height="12" rx="2"/><circle cx="12" cy="12" r="3"/></svg>
              Salary &amp; Insurance
            </div>
            <div class="hrm-nav-item">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M22 10v6M2 10l10-5 10 5-10 5z"/><path d="M6 12v5c0 1.66 2.69 3 6 3s6-1.34 6-3v-5"/></svg>
              Training
            </div>
            <div class="hrm-nav-item">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2l3 6 7 1-5 5 1 7-6-3-6 3 1-7-5-5 7-1z"/></svg>
              Evaluation
            </div>
          </nav>
        </aside>

        <!-- Main area -->
        <main class="hrm-main">
          <header class="hrm-header">
            <span class="hrm-header-title">VPO</span>
            <div class="hrm-header-icons">
              <span class="hrm-bell">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8a6 6 0 00-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 01-3.46 0"/></svg>
                <span class="hrm-bell-badge">3</span>
              </span>
              <span class="hrm-flag">🇺🇸</span>
              <span class="hrm-user">NGUYEN VAN A</span>
            </div>
          </header>

          <!-- State 1: Dashboard -->
          <div class="hrm-content hrm-content--dashboard">
            <div class="hrm-working-card">
              <div class="hrm-card-title">WORKING TIME</div>
              <div class="hrm-time-grid">
                <div class="hrm-time-label">Today</div>
                <div class="hrm-time-pill hrm-time-pill--red">
                  <span class="hrm-pill-label">Checkin time</span>
                  <span class="hrm-pill-val">08:39</span>
                </div>
                <div class="hrm-time-pill hrm-time-pill--yellow">
                  <span class="hrm-pill-label">Checkout time</span>
                  <span class="hrm-pill-val">08:39</span>
                </div>
                <div class="hrm-time-pill hrm-time-pill--blue">
                  <span class="hrm-pill-label">Total</span>
                  <span class="hrm-pill-val">0</span>
                </div>
              </div>
            </div>

            <div class="hrm-charts">
              <div class="hrm-chart-card">
                <div class="hrm-chart-title">Timesheet - 05/2026</div>
                <div class="hrm-pie">
                  <svg viewBox="0 0 100 100">
                    <circle cx="50" cy="50" r="40" fill="#0A564C"/>
                    <path d="M50 50 L50 10 A40 40 0 0 1 89 60 Z" fill="#E49025"/>
                    <path d="M50 50 L89 60 A40 40 0 0 1 60 85 Z" fill="#AAEFDB"/>
                    <path d="M50 50 L60 85 A40 40 0 0 1 30 80 Z" fill="#5BBA9F"/>
                    <path d="M50 50 L30 80 A40 40 0 0 1 12 50 Z" fill="#E63946"/>
                  </svg>
                </div>
                <div class="hrm-legend">
                  <span><i style="background:#0A564C"></i>Worked 101.75</span>
                  <span><i style="background:#AAEFDB"></i>Holidays 24</span>
                  <span><i style="background:#5BBA9F"></i>Leave 16</span>
                </div>
              </div>

              <div class="hrm-chart-card">
                <div class="hrm-chart-title">Leave request - 5/2026</div>
                <div class="hrm-donut">
                  <svg viewBox="0 0 100 100">
                    <circle cx="50" cy="50" r="40" fill="none" stroke="#AAEFDB" stroke-width="16"/>
                    <circle cx="50" cy="50" r="40" fill="none" stroke="#0A564C" stroke-width="16" stroke-dasharray="160 100" stroke-dashoffset="0" transform="rotate(-90 50 50)"/>
                  </svg>
                </div>
                <div class="hrm-legend">
                  <span><i style="background:#0A564C"></i>Approved 2</span>
                  <span><i style="background:#AAEFDB"></i>Unapproved 1</span>
                </div>
              </div>
            </div>
          </div>

          <!-- State 2: Salary & Insurance (payslip list) -->
          <div class="hrm-content hrm-content--salary">
            <div class="hrm-tabs">
              <div class="hrm-tab hrm-tab--active">PAYSLIP</div>
              <div class="hrm-tab">SOCIAL INSURANCE</div>
            </div>
            <table class="hrm-table">
              <thead>
                <tr><th>NO.</th><th>Payslip</th><th>Updated date</th><th></th></tr>
              </thead>
              <tbody>
                <tr><td>1</td><td>23033_Payslip Month_4.2026</td><td>24/04/2026</td><td><span class="hrm-table-pdf">PDF</span></td></tr>
                <tr><td>2</td><td>23033_Payslip Month_3.2026</td><td>30/03/2026</td><td><span class="hrm-table-pdf">PDF</span></td></tr>
                <tr><td>3</td><td>23033_Payslip Month_2.2026</td><td>03/03/2026</td><td><span class="hrm-table-pdf">PDF</span></td></tr>
                <tr><td>4</td><td>23033_Payslip Month_1.2026</td><td>30/01/2026</td><td><span class="hrm-table-pdf">PDF</span></td></tr>
                <tr><td>5</td><td>23033_Payslip Month_12.2025</td><td>30/12/2025</td><td><span class="hrm-table-pdf">PDF</span></td></tr>
              </tbody>
            </table>
          </div>
        </main>
      </div>

      <!-- Animated cursor overlay -->
      <div class="hrm-cursor" aria-hidden="true">
        <svg viewBox="0 0 24 24" fill="#073130" stroke="white" stroke-width="1.5">
          <path d="M3 2 L3 20 L8 16 L11 22 L14 21 L11 15 L18 14 Z"/>
        </svg>
        <span class="hrm-cursor-ring"></span>
      </div>
    </div>
CSS
/* ==========================================================================
   HRM Showcase responsive: desktop browser mockup + mobile phone mockup
   (v19 - phone mockup moved from BPaaS to here)
   ========================================================================== */
.hrm-mockup--desktop { display: block; }
@media (max-width: 768px) {
.hrm-mockup--desktop { display: none; }
}
/* (Old vertical barRise animation removed - v11 uses width transition on .gauge-row-fill instead) */

/* ==========================================================================
   v10 - HRM SHOWCASE SECTION (animated mockup with cursor click cycle)
   ========================================================================== */
.hrm-showcase-section { overflow: hidden; }
.hrm-mockup {
  position: relative;
  background: var(--white);
  border-radius: 14px;
  border: 1px solid #E5E7E7;
  box-shadow: 0 30px 60px rgba(7,49,48,0.10), 0 8px 24px rgba(7,49,48,0.06);
  overflow: hidden;
  max-width: 1100px;
  margin: 0 auto;
}
/* Browser chrome */
.hrm-chrome {
  display: flex; align-items: center; gap: 14px;
  padding: 10px 16px;
  background: #F0F2F2;
  border-bottom: 1px solid #E5E7E7;
}
.hrm-dots { display: flex; gap: 6px; }
.hrm-dots i {
  width: 11px; height: 11px; border-radius: 50%;
  background: #D6D7D7; display: inline-block;
}
.hrm-dots i:nth-child(1) { background: #FF5F57; }
.hrm-dots i:nth-child(2) { background: #FEBC2E; }
.hrm-dots i:nth-child(3) { background: #28C840; }
.hrm-url {
  flex: 1; text-align: center;
  font-size: 12px; color: var(--text-muted);
  background: var(--white);
  border-radius: 6px;
  padding: 4px 12px;
  max-width: 280px;
  margin: 0 auto;
}
/* App body: sidebar + main */
.hrm-app {
  display: grid;
  grid-template-columns: 200px 1fr;
  min-height: 480px;
  position: relative;
}
/* Sidebar */
.hrm-sidebar {
  background: var(--white);
  border-right: 1px solid #E5E7E7;
  padding: 18px 12px;
}
.hrm-logo {
  padding: 4px 8px 18px;
  border-bottom: 1px solid #EFF1F1;
  margin-bottom: 14px;
}
.hrm-logo-svg {
  width: 88px;
  height: auto;
  display: block;
}
.hrm-nav-section {
  font-size: 11px; font-weight: 700;
  color: var(--teal-dark);
  text-transform: none;
  padding: 6px 8px 10px;
  border-bottom: 2px solid #E5E7E7;
  margin-bottom: 8px;
  position: relative;
}
.hrm-nav-section::after {
  content: ''; position: absolute; bottom: -2px; left: 0;
  width: 60px; height: 2px; background: var(--text-primary);
}
.hrm-nav {
  display: flex; flex-direction: column; gap: 2px;
}
.hrm-nav-item {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 10px;
  font-size: 12.5px;
  color: var(--text-body);
  border-radius: 6px;
  cursor: pointer;
  position: relative;
  transition: background .25s, color .25s;
}
.hrm-nav-item svg { width: 16px; height: 16px; flex-shrink: 0; }
/* v10 - Salary item active highlight on cursor click (synced to cursor-loop) */
.hrm-nav-item--target {
  animation: salary-highlight 8s ease-in-out infinite;
}
@keyframes salary-highlight {
  0%, 40% {
    background: transparent;
    color: var(--text-body);
  }
  50%, 95% {
    background: linear-gradient(90deg, var(--green-light) 0%, #7BD9B8 100%);
    color: var(--white);
  }
  100% { background: transparent; color: var(--text-body); }
}
/* Main area */
.hrm-main {
  background: #F7F9F9;
  padding: 14px 18px 20px;
  display: flex; flex-direction: column;
  overflow: hidden;
  position: relative;
}
.hrm-header {
  display: flex; justify-content: space-between; align-items: center;
  padding-bottom: 12px;
  border-bottom: 1px solid #E5E7E7;
  margin-bottom: 14px;
}
.hrm-header-title {
  font-size: 14px; font-weight: 700; color: var(--text-primary);
}
.hrm-header-icons {
  display: flex; align-items: center; gap: 12px;
  font-size: 11px;
}
.hrm-bell { position: relative; color: var(--text-muted); }
.hrm-bell svg { width: 16px; height: 16px; display: block; }
.hrm-bell-badge {
  position: absolute; top: -4px; right: -6px;
  background: var(--orange); color: var(--white);
  font-size: 9px; font-weight: 700;
  width: 14px; height: 14px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
}
.hrm-flag { font-size: 14px; }
.hrm-user { font-size: 11px; font-weight: 700; color: var(--text-primary); letter-spacing: 0.04em; }
/* Content states (dashboard vs salary) */
.hrm-content {
  position: absolute;
  top: 60px; left: 18px; right: 18px; bottom: 20px;
  opacity: 0;
  transition: opacity .4s ease;
}
/* v10 - content swap synced with cursor-loop animation */
.hrm-content--dashboard {
  animation: content-dashboard 8s ease-in-out infinite;
}
.hrm-content--salary {
  animation: content-salary 8s ease-in-out infinite;
}
@keyframes content-dashboard {
  0%, 38% { opacity: 1; }
  48%, 95% { opacity: 0; }
  100% { opacity: 1; }
}
@keyframes content-salary {
  0%, 38% { opacity: 0; }
  48%, 95% { opacity: 1; }
  100% { opacity: 0; }
}
/* Dashboard state */
.hrm-working-card {
  background: var(--white);
  border-radius: 8px;
  padding: 12px 14px;
  margin-bottom: 14px;
}
.hrm-card-title {
  font-size: 11px; font-weight: 700;
  letter-spacing: 0.08em;
  text-align: center;
  color: var(--text-primary);
  margin-bottom: 10px;
}
.hrm-time-grid {
  display: grid;
  grid-template-columns: 70px 1fr 1fr 1fr;
  gap: 8px;
}
.hrm-time-label {
  display: flex; align-items: center; justify-content: center;
  border: 1px solid #E5E7E7;
  border-radius: 6px;
  font-size: 11px;
  color: var(--text-muted);
}
.hrm-time-pill {
  padding: 6px 10px;
  border-radius: 6px;
  text-align: center;
}
.hrm-time-pill--red { background: rgba(230,57,70,0.10); }
.hrm-time-pill--yellow { background: rgba(228,144,37,0.12); }
.hrm-time-pill--blue { background: rgba(95,189,217,0.14); }
.hrm-pill-label {
  display: block; font-size: 9px; font-weight: 700;
  letter-spacing: 0.02em;
  margin-bottom: 2px;
}
.hrm-time-pill--red .hrm-pill-label { color: #E63946; }
.hrm-time-pill--yellow .hrm-pill-label { color: var(--orange); }
.hrm-time-pill--blue .hrm-pill-label { color: #4A9DBA; }
.hrm-pill-val {
  display: block; font-size: 15px; font-weight: 700;
  color: var(--text-primary);
}
.hrm-time-pill--red .hrm-pill-val { color: #E63946; }
.hrm-time-pill--yellow .hrm-pill-val { color: var(--orange); }
.hrm-time-pill--blue .hrm-pill-val { color: #4A9DBA; }
.hrm-charts {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}
.hrm-chart-card {
  background: var(--white);
  border-radius: 8px;
  padding: 12px 14px;
}
.hrm-chart-title {
  font-size: 12px; font-weight: 700; color: var(--text-primary);
  text-align: center; margin-bottom: 10px;
}
.hrm-pie, .hrm-donut {
  width: 100px; height: 100px; margin: 0 auto 8px;
}
.hrm-pie svg, .hrm-donut svg { width: 100%; height: 100%; }
.hrm-legend {
  display: flex; flex-direction: column; gap: 3px;
  font-size: 10px; color: var(--text-body);
}
.hrm-legend span { display: flex; align-items: center; gap: 6px; }
.hrm-legend i {
  width: 8px; height: 8px; border-radius: 2px;
  display: inline-block;
}
/* Salary state */
.hrm-tabs {
  display: flex;
  background: var(--white);
  border-radius: 8px 8px 0 0;
  border-bottom: 1px solid #E5E7E7;
  margin-bottom: 0;
}
.hrm-tab {
  flex: 1;
  padding: 12px;
  text-align: center;
  font-size: 11px; font-weight: 700;
  color: var(--text-muted);
  letter-spacing: 0.04em;
  cursor: pointer;
  border-bottom: 2px solid transparent;
}
.hrm-tab--active {
  color: var(--green-medium);
  border-bottom-color: var(--green-medium);
}
.hrm-table {
  width: 100%;
  background: var(--white);
  border-radius: 0 0 8px 8px;
  border-collapse: collapse;
  overflow: hidden;
}
.hrm-table thead tr {
  background: var(--green-medium);
}
.hrm-table th {
  padding: 10px 12px;
  text-align: left;
  font-size: 11px; font-weight: 700;
  color: var(--white);
  letter-spacing: 0.02em;
}
.hrm-table td {
  padding: 9px 12px;
  font-size: 11px;
  color: var(--text-body);
  border-bottom: 1px solid #F0F2F2;
}
.hrm-table tr:last-child td { border-bottom: none; }
.hrm-table-pdf {
  display: inline-block;
  background: var(--green-medium);
  color: var(--white);
  font-size: 9px; font-weight: 700;
  padding: 3px 8px;
  border-radius: 4px;
}
/* v10 - Animated cursor (clicks on Salary & Insurance sidebar item) */
.hrm-cursor {
  position: absolute;
  width: 20px; height: 24px;
  z-index: 10;
  pointer-events: none;
  /* Start position: middle of main content area */
  top: 240px; left: 480px;
  animation: cursor-loop 8s ease-in-out infinite;
}
.hrm-cursor svg {
  width: 100%; height: 100%;
  filter: drop-shadow(0 2px 4px rgba(0,0,0,0.25));
}
/* Click ripple ring */
.hrm-cursor-ring {
  position: absolute;
  top: 50%; left: 30%;
  width: 28px; height: 28px;
  border-radius: 50%;
  border: 2px solid var(--green-medium);
  transform: translate(-50%, -50%) scale(0);
  opacity: 0;
  animation: cursor-click-ring 8s ease-out infinite;
}
@keyframes cursor-loop {
  /* 0-15%: hold initial position (dashboard fully shown) */
  0%, 12% { top: 240px; left: 480px; }
  /* 15-38%: move to salary sidebar item */
  38% { top: 248px; left: 130px; }
  /* 40-46%: click hold */
  42% { top: 248px; left: 130px; transform: scale(0.9); }
  46% { top: 248px; left: 130px; transform: scale(1); }
  /* 48-90%: stay near salary (salary state shows) */
  88% { top: 260px; left: 145px; transform: scale(1); }
  /* 90-100%: return to start */
  100% { top: 240px; left: 480px; transform: scale(1); }
}
@keyframes cursor-click-ring {
  0%, 36% { transform: translate(-50%, -50%) scale(0); opacity: 0; }
  40% { transform: translate(-50%, -50%) scale(0.4); opacity: 0.85; }
  48% { transform: translate(-50%, -50%) scale(1.8); opacity: 0; }
  100% { transform: translate(-50%, -50%) scale(1.8); opacity: 0; }
}
terra-hrm-showcase-mobile locked composition ▼ chi tiết

Bản điện thoại của mô phỏng tự chạy: màn splash có logo terra nhấp nháy -> dashboard với ô Phiếu lương được làm nổi + dấu chạm -> danh sách phiếu lương. 3 màn lặp 9 giây, bên dưới có các pill đánh dấu đang ở bước nào.

Ràng buộc: KHÓA - copy nguyên (CORE_RULES § 3). CHỈ hiện <=768px; luôn đi cặp với terra-hrm-showcase-desktop trong cùng section; khi giảm chuyển động thì đứng yên ở splash. Logo splash = logo terra dạng icon từ BRAND_ASSETS.
Nguồn gốc: service-page-terra-payroll-vi.html (production, harvested 2026-06-11)
File: brands/terra/patterns/compositions/terra-hrm-showcase-mobile.md
Khung: Nền: Mở riêng ↗
HTML
<div class="hrm-mockup-mobile reveal reveal-delay-1">

        <!-- Intro caption for mobile -->
        <p class="hrm-mobile-intro">terra HRM là phần mềm chấm công miễn phí được tích hợp với dịch vụ tính lương terra Payroll. Khách hàng truy cập dữ liệu lương, đơn từ và phiếu lương trực tiếp trên điện thoại.</p>

        <!-- Phone mockup with auto-cycling screens -->
        <div class="hrmphone-wrap">
          <div class="hrmphone-frame">
            <div class="hrmphone-notch" aria-hidden="true"></div>
            <div class="hrmphone-screen">

              <!-- ====== Screen 1: Splash (terra logo) ====== -->
              <div class="hrmscreen hrmscreen--splash">
                <div class="hrmapp-statusbar">
                  <span>15:19</span>
                  <span class="hrmapp-status-right">
                    <svg viewBox="0 0 18 12" fill="currentColor"><rect x="0" y="6" width="3" height="6" rx="0.5"/><rect x="5" y="3" width="3" height="9" rx="0.5"/><rect x="10" y="0" width="3" height="12" rx="0.5" opacity="0.4"/></svg>
                    <svg viewBox="0 0 16 12" fill="none" stroke="currentColor" stroke-width="1.4"><path d="M2 7 a 8 8 0 0 1 12 0 M4 9 a 5 5 0 0 1 8 0 M7 11 a 2 2 0 0 1 2 0"/></svg>
                    <svg viewBox="0 0 24 12" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1" y="2" width="20" height="8" rx="2"/><rect x="3" y="4" width="14" height="4" rx="1" fill="currentColor" opacity="0.4"/><line x1="22" y1="5" x2="22" y2="7"/></svg>
                  </span>
                </div>
                <div class="hrmsplash-content">
                  <svg viewBox="0 0 46.82 40.89" xmlns="http://www.w3.org/2000/svg" class="hrmsplash-logo" aria-label="terra">
                    <ellipse cx="6.55" cy="11.36" rx="2.81" ry="2.73" fill="#78ae45"/>
                    <ellipse cx="31.84" cy="4.54" rx="2.81" ry="2.73" fill="#0099da"/>
                    <ellipse cx="40.27" cy="29.99" rx="2.81" ry="2.73" fill="#e48f25"/>
                    <path fill="#16796d" d="M44.48,40.89c-.3,0-.6-.06-.9-.17-13.02-5.23-27.35-5.24-40.36-.02-1.23.49-2.63-.1-3.09-1.32s.25-2.46,1.43-2.93c14.1-5.62,29.61-5.61,43.71.03,1.13.45,1.8,1.66,1.45,2.79-.31,1.01-1.25,1.62-2.25,1.62ZM8.89,30.45v-11.67c0-1.24-.96-2.36-2.24-2.42-1.34-.06-2.45.98-2.45,2.27v11.67c0,1.24.96,2.36,2.24,2.42,1.34.06,2.45-.98,2.45-2.27ZM17.32,28.73V2.42c0-1.24-.96-2.36-2.24-2.42-1.34-.06-2.45.98-2.45,2.27v26.31c0,1.24.96,2.36,2.24,2.42,1.34.06,2.45-.98,2.45-2.27ZM25.75,28.17V9.23c0-1.24-.96-2.36-2.24-2.42-1.34-.06-2.45.98-2.45,2.27v18.93c0,1.24.96,2.36,2.24,2.42,1.34.06,2.45-.98,2.45-2.27ZM34.18,28.73V11.96c0-1.24-.96-2.36-2.24-2.42-1.34-.06-2.45.98-2.45,2.27v16.77c0,1.24.96,2.36,2.24,2.42,1.34.06,2.45-.98,2.45-2.27ZM37.93,9.09v13.49c0,1.24.96,2.36,2.24,2.42,1.34.06,2.45-.98,2.45-2.27v-13.49c0-1.24-.96-2.36-2.24-2.42-1.34-.06-2.45.98-2.45,2.27Z"/>
                  </svg>
                </div>
              </div>

              <!-- ====== Screen 2: Dashboard ====== -->
              <div class="hrmscreen hrmscreen--dashboard">
                <div class="hrmapp-header">
                  <div class="hrmapp-header-title">NGUYEN VAN A</div>
                </div>
                <div class="hrmapp-checkin">
                  <div class="hrmapp-checkin-row">
                    <div class="hrmapp-checkin-col">
                      <div class="hrmapp-checkin-label">Checkin time</div>
                      <div class="hrmapp-checkin-val">08:39</div>
                    </div>
                    <div class="hrmapp-checkin-col">
                      <div class="hrmapp-checkin-label">Checkout</div>
                      <div class="hrmapp-checkin-val">08:39</div>
                    </div>
                    <div class="hrmapp-checkin-col">
                      <div class="hrmapp-checkin-label">Total (h)</div>
                      <div class="hrmapp-checkin-val">0</div>
                    </div>
                  </div>
                </div>
                <div class="hrmapp-grid">
                  <button class="hrmapp-tile">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8" r="4"/><path d="M4 21v-2a6 6 0 016-6h4a6 6 0 016 6v2"/></svg>
                    <span>Personal<br>data</span>
                  </button>
                  <button class="hrmapp-tile">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="16" y1="2" x2="16" y2="6"/></svg>
                    <span>Working<br>time</span>
                  </button>
                  <button class="hrmapp-tile">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="6" width="9" height="14" rx="1"/><rect x="14" y="3" width="7" height="17" rx="1"/></svg>
                    <span>Notification<br>Company</span>
                  </button>
                  <button class="hrmapp-tile">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M22 10 L12 4 L2 10 L12 16 L22 10 V18"/><path d="M6 13v5c0 1 3 2 6 2s6-1 6-2v-5"/></svg>
                    <span>List of<br>courses</span>
                  </button>
                  <button class="hrmapp-tile hrmapp-tile--highlight">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="20" height="13" rx="2"/><line x1="6" y1="13" x2="10" y2="13"/><circle cx="16" cy="13" r="2"/></svg>
                    <span>Payslip</span>
                  </button>
                  <button class="hrmapp-tile">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16" rx="2"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="8" y1="13" x2="14" y2="13"/><line x1="8" y1="16" x2="12" y2="16"/></svg>
                    <span>Social<br>insurance</span>
                  </button>
                </div>
                <!-- Cursor tap indicator on Payslip tile -->
                <div class="hrmcursor hrmcursor--payslip" aria-hidden="true">
                  <svg viewBox="0 0 24 24" fill="#073130" stroke="#ffffff" stroke-width="1.5" stroke-linejoin="round"><path d="M5 3 L19 12 L12 13 L9 20 Z"/></svg>
                </div>
              </div>

              <!-- ====== Screen 3: Payslip ====== -->
              <div class="hrmscreen hrmscreen--payslip">
                <div class="hrmapp-header hrmapp-header--payslip">
                  <button class="hrmapp-back" aria-label="Back">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg>
                  </button>
                  <div class="hrmapp-header-title">Payslip</div>
                </div>
                <div class="hrmapp-payslip-body">
                  <div class="hrmapp-payslip-label">Working calendar</div>
                  <div class="hrmapp-year-tabs">
                    <button class="hrmapp-year hrmapp-year--active">2026</button>
                    <button class="hrmapp-year">2025</button>
                    <button class="hrmapp-year">2024</button>
                    <button class="hrmapp-year">2023</button>
                  </div>
                  <div class="hrmapp-payslip-head">
                    <span>Name</span>
                    <span>View</span>
                  </div>
                  <div class="hrmapp-payslip-list">
                    <div class="hrmapp-payslip-row">
                      <span>23033_Payslip Month_4.2026</span>
                      <svg viewBox="0 0 24 24" fill="#14b87a" stroke="#ffffff" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" class="hrmapp-payslip-icon"><rect x="5" y="3" width="14" height="18" rx="2"/><line x1="8" y1="9" x2="16" y2="9"/><line x1="8" y1="13" x2="16" y2="13"/><line x1="8" y1="17" x2="13" y2="17"/></svg>
                    </div>
                    <div class="hrmapp-payslip-row">
                      <span>23033_Payslip Month_3.2026</span>
                      <svg viewBox="0 0 24 24" fill="#14b87a" stroke="#ffffff" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" class="hrmapp-payslip-icon"><rect x="5" y="3" width="14" height="18" rx="2"/><line x1="8" y1="9" x2="16" y2="9"/><line x1="8" y1="13" x2="16" y2="13"/><line x1="8" y1="17" x2="13" y2="17"/></svg>
                    </div>
                    <div class="hrmapp-payslip-row">
                      <span>23033_Payslip Month_2.2026</span>
                      <svg viewBox="0 0 24 24" fill="#14b87a" stroke="#ffffff" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" class="hrmapp-payslip-icon"><rect x="5" y="3" width="14" height="18" rx="2"/><line x1="8" y1="9" x2="16" y2="9"/><line x1="8" y1="13" x2="16" y2="13"/><line x1="8" y1="17" x2="13" y2="17"/></svg>
                    </div>
                    <div class="hrmapp-payslip-row">
                      <span>23033_Payslip Month_1.2026</span>
                      <svg viewBox="0 0 24 24" fill="#14b87a" stroke="#ffffff" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" class="hrmapp-payslip-icon"><rect x="5" y="3" width="14" height="18" rx="2"/><line x1="8" y1="9" x2="16" y2="9"/><line x1="8" y1="13" x2="16" y2="13"/><line x1="8" y1="17" x2="13" y2="17"/></svg>
                    </div>
                  </div>
                </div>
              </div>

            </div>
          </div>

          <!-- Step indicators below phone -->
          <div class="hrmphone-steps" aria-hidden="true">
            <div class="hrmphone-step hrmphone-step--1">
              <span class="hrmphone-step-num">1</span>
              <span class="hrmphone-step-label">Mở ứng dụng</span>
            </div>
            <div class="hrmphone-step hrmphone-step--2">
              <span class="hrmphone-step-num">2</span>
              <span class="hrmphone-step-label">Dashboard cá nhân</span>
            </div>
            <div class="hrmphone-step hrmphone-step--3">
              <span class="hrmphone-step-num">3</span>
              <span class="hrmphone-step-label">Xem phiếu lương</span>
            </div>
          </div>
        </div>

      </div>

<style>.hrm-mockup-mobile{display:block!important;max-width:340px;margin:0 auto}</style>
CSS
.hrm-mockup-mobile { display: none; }
@media (max-width: 768px) {
.hrm-mockup-mobile { display: block; }
}
/* Phone mockup styles (renamed from bpv18- to hrm- prefix) */
.hrm-mobile-intro {
  font-size: 14px;
  line-height: 1.55;
  color: var(--text-body);
  text-align: center;
  max-width: 480px;
  margin: 0 auto 24px;
}
.hrmphone-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 22px;
}
.hrmphone-frame {
  position: relative;
  width: 280px;
  height: 580px;
  background: #1a1a1a;
  border-radius: 38px;
  padding: 8px;
  box-shadow:
    0 0 0 2px #2a2a2a,
    0 18px 48px rgba(7,49,48,0.18),
    0 4px 14px rgba(7,49,48,0.10);
}
.hrmphone-notch {
  display: none;   /* v25: removed - was covering interface per Tatsuki feedback */
}
.hrmphone-screen {
  position: relative;
  width: 100%;
  height: 100%;
  background: #ffffff;
  border-radius: 30px;
  overflow: hidden;
}
.hrmscreen {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  opacity: 0;
  animation: hrmphone-cycle 9s infinite;
}
.hrmscreen--splash { animation-delay: 0s; }
.hrmscreen--dashboard { animation-delay: 3s; }
.hrmscreen--payslip { animation-delay: 6s; }
@keyframes hrmphone-cycle {
  0%   { opacity: 0; }
  3%   { opacity: 1; }
  33%  { opacity: 1; }
  36%  { opacity: 0; }
  100% { opacity: 0; }
}
.hrmapp-statusbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 16px 22px 6px;
  font-size: 13px;
  font-weight: 700;
  color: #111;
}
.hrmapp-status-right {
  display: flex;
  align-items: center;
  gap: 5px;
}
.hrmapp-status-right svg {
  width: 16px; height: 12px;
}
.hrmscreen--splash { background: #ffffff; }
.hrmsplash-content {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}
.hrmsplash-logo {
  width: 88px;
  height: auto;
  animation: hrmsplash-pulse 1.8s ease-in-out infinite;
}
@keyframes hrmsplash-pulse {
  0%, 100% { transform: scale(1); opacity: 1; }
  50%      { transform: scale(1.06); opacity: 0.85; }
}
.hrmscreen--dashboard { background: #f5f7fa; }
.hrmscreen--payslip { background: #f5f7fa; }
.hrmapp-header {
  background: #4ddb91;
  padding: 16px 22px 22px;
  display: flex;
  align-items: center;
  gap: 12px;
}
.hrmapp-header--payslip {
  padding: 16px 22px;
  position: relative;
}
.hrmapp-back {
  width: 32px; height: 32px;
  border-radius: 50%;
  background: rgba(255,255,255,0.55);
  border: none;
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  color: #073130;
  flex-shrink: 0;
  padding: 0;
}
.hrmapp-back svg { width: 14px; height: 14px; }
.hrmapp-header-title {
  flex: 1;
  text-align: center;
  font-size: 13.5px;
  font-weight: 700;
  color: #ffffff;
  letter-spacing: 0.02em;
  padding-right: 32px;
}
.hrmapp-header--payslip .hrmapp-header-title {
  font-size: 16px;
}
.hrmapp-checkin {
  background: #ffffff;
  margin: -8px 14px 0;
  padding: 12px 14px 14px;
  border-radius: 10px;
  box-shadow: 0 1px 4px rgba(7,49,48,0.06);
}
.hrmapp-checkin-row {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 6px;
}
.hrmapp-checkin-col {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.hrmapp-checkin-label {
  font-size: 9px;
  color: #6b7280;
  font-weight: 500;
}
.hrmapp-checkin-val {
  font-size: 16px;
  font-weight: 700;
  color: #073130;
  font-feature-settings: "tnum" 1;
}
.hrmapp-grid {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 8px;
  padding: 14px;
}
.hrmapp-tile {
  background: #ffffff;
  border: 1px solid #d9f5e5;
  border-radius: 10px;
  padding: 14px 6px 12px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  cursor: pointer;
  transition: transform 0.2s;
  min-height: 82px;
}
.hrmapp-tile svg {
  width: 24px; height: 24px;
  color: #073130;
}
.hrmapp-tile span {
  font-size: 10.5px;
  font-weight: 700;
  color: #073130;
  line-height: 1.2;
  text-align: center;
}
.hrmapp-tile--highlight {
  border: 2px solid #4ddb91;
  background: #f0fdf4;
  animation: hrmtile-pulse 1.4s ease-in-out infinite;
}
@keyframes hrmtile-pulse {
  0%, 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(77,219,145,0.5); }
  50%      { transform: scale(1.03); box-shadow: 0 0 0 8px rgba(77,219,145,0); }
}
.hrmcursor {
  position: absolute;
  width: 26px;
  height: 26px;
  pointer-events: none;
  filter: drop-shadow(0 2px 4px rgba(0,0,0,0.25));
  opacity: 0;
}
.hrmcursor svg { width: 100%; height: 100%; }
.hrmcursor--payslip {
  left: 56%;
  top: 65%;
  animation: hrmcursor-tap 9s infinite;
  animation-delay: 3s;
}
@keyframes hrmcursor-tap {
  0%   { opacity: 0; transform: scale(1.2); }
  18%  { opacity: 1; transform: scale(1.2); }
  25%  { opacity: 1; transform: scale(0.9); }
  30%  { opacity: 1; transform: scale(1); }
  35%  { opacity: 0; transform: scale(1); }
  100% { opacity: 0; }
}
.hrmapp-payslip-body {
  flex: 1;
  background: #ffffff;
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.hrmapp-payslip-label {
  font-size: 13.5px;
  font-weight: 700;
  color: #073130;
}
.hrmapp-year-tabs {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
}
.hrmapp-year {
  background: #ffffff;
  border: 1px solid #d9f5e5;
  border-radius: 8px;
  padding: 8px 0;
  font-size: 12px;
  font-weight: 700;
  color: #073130;
  cursor: pointer;
}
.hrmapp-year--active {
  background: #4ddb91;
  color: #ffffff;
  border-color: #4ddb91;
}
.hrmapp-payslip-head {
  display: flex;
  justify-content: space-between;
  font-size: 11.5px;
  font-weight: 700;
  color: #073130;
  padding: 0 4px;
  margin-top: 4px;
}
.hrmapp-payslip-list {
  display: flex;
  flex-direction: column;
}
.hrmapp-payslip-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 4px;
  border-bottom: 1px solid #f0f3f5;
  font-size: 11px;
  color: #1f2937;
  font-weight: 500;
}
.hrmapp-payslip-row:last-child { border-bottom: none; }
.hrmapp-payslip-icon {
  width: 22px; height: 22px;
  flex-shrink: 0;
  background: #4ddb91;
  border-radius: 5px;
  padding: 3px;
}
.hrmphone-steps {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  justify-content: center;
  max-width: 400px;
  width: 100%;
}
.hrmphone-step {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 8px 12px;
  background: rgba(20,121,108,0.08);
  border-radius: 999px;
  flex: 1 1 auto;
  min-width: 0;
  justify-content: center;
  transition: all 0.3s;
  animation: hrmstep-highlight 9s infinite;
}
.hrmphone-step-num {
  width: 22px; height: 22px;
  background: var(--teal-dark);
  color: #ffffff;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 11px;
  font-weight: 700;
  flex-shrink: 0;
}
.hrmphone-step-label {
  font-size: 11px;
  font-weight: 600;
  color: var(--text-primary);
  white-space: nowrap;
}
.hrmphone-step--1 { animation-delay: 0s; }
.hrmphone-step--2 { animation-delay: 3s; }
.hrmphone-step--3 { animation-delay: 6s; }
@keyframes hrmstep-highlight {
  0%, 33%   { background: rgba(228,144,37,0.15); transform: scale(1.04); }
  33.01%, 100% { background: rgba(20,121,108,0.08); transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
.hrmscreen, .hrmcursor, .hrmsplash-logo, .hrmapp-tile--highlight, .hrmphone-step {
    animation: none;
  }
.hrmscreen--splash { opacity: 1; }
.hrmscreen--dashboard, .hrmscreen--payslip { opacity: 0; }
}
trust-strip validated composition ▼ chi tiết

Một dải ngang rất mỏng, chữ nhỏ, không tiêu đề. Nó cố tình không đòi sự chú ý, chỉ đứng đó như một dòng chú thích, và chính sự khiêm tốn đó làm nó đáng tin. Trả lời câu "chỗ này có thật không" trước khi người đọc kịp hỏi thành lời.

Ràng buộc: Số liệu ghi nguyên văn từ CORE_RULES § 1; không bao giờ dùng số 70% khách Nhật.
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/compositions/trust-strip.md
Khung: Nền: Mở riêng ↗
HTML
<section class="trust-strip section--after-wave">
  <div class="container">
    <div class="trust-strip-inner">
      <div class="trust-strip-label">
        Được lựa chọn bởi cộng đồng doanh nghiệp FDI hoạt động tại Việt Nam
      </div>
      <div class="trust-markets">
        <span class="trust-market">Nhật Bản</span>
        <span class="trust-divider"></span>
        <span class="trust-market">Hàn Quốc</span>
        <span class="trust-divider"></span>
        <span class="trust-market">Singapore</span>
        <span class="trust-divider"></span>
        <span class="trust-market">Châu Âu</span>
        <span class="trust-divider"></span>
        <span class="trust-market">Hoa Kỳ</span>
        <span class="trust-iso-badge">
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
            <path d="M12 2L4 5v6c0 5 3.5 9.5 8 11 4.5-1.5 8-6 8-11V5l-8-3z"/>
            <path d="M9 12l2 2 4-4"/>
          </svg>
          ISO/IEC 27001:2022
        </span>
      </div>
    </div>
  </div>
</section>
CSS
.trust-strip {
  background: var(--white);
  border-top: none;             /* CRITICAL — seamless from wave above */
  border-bottom: 1px solid var(--border);
  padding: 28px 0;
}
.trust-strip-inner {
  display: grid;
  grid-template-columns: minmax(260px, 1fr) auto;
  gap: 40px;
  align-items: center;
}
.trust-strip-label {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-body);
  line-height: 1.5;
  max-width: 340px;
}
.trust-markets {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  justify-content: flex-end;
}
.trust-market {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-primary);
  padding: 6px 14px;
  border-radius: 999px;
  background: var(--bg-light);
}
.trust-divider {
  width: 4px; height: 4px;
  background: var(--border);
  border-radius: 50%;
}
.trust-iso-badge {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  background: var(--orange-soft);
  color: var(--orange);
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  margin-left: 8px;
}

@media (max-width: 900px) {
  .trust-strip-inner { grid-template-columns: 1fr; gap: 20px; }
  .trust-markets { justify-content: flex-start; }
  .trust-divider { display: none; }
}

decor/

Trang trí

corner-circles validated atom ▼ chi tiết

Vài đường tròn rất mờ tràn ra ngoài mép ở góc. Làm một mảng màu lớn trông như có chủ đích chứ không phải bỏ trống, mà không kéo mắt về phía nó. Cảm giác là vùng này rộng hơn cái khung đang thấy.

Ràng buộc: Chỉ trên section nền tối (teal / teal-dark).
Hợp cặp: compositions/final-cta, compositions/hero-teal-photo
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/decor/corner-circles.md
Khung: Nền: Mở riêng ↗
HTML
<div class="section-corner-circles" style="position:relative;background:var(--teal-dark);border-radius:12px;min-height:220px;padding:48px;overflow:hidden">
  <h3 style="color:var(--white);margin:0 0 8px">Dark section with corner framing</h3>
  <p style="color:rgba(255,255,255,.75);margin:0;max-width:420px">The circles whisper structure from the corners; content stays the focus.</p>
</div>
CSS
.section-corner-circles {
  position: relative;
  overflow: hidden;
}
.section-corner-circles::before {
  content: ''; position: absolute;
  top: -100px; right: -80px;
  width: 460px; height: 460px;
  border-radius: 50%;
  border: 1px solid rgba(170,239,219,0.10);
  pointer-events: none;
}
.section-corner-circles::after {
  content: ''; position: absolute;
  bottom: -160px; left: -80px;
  width: 580px; height: 580px;
  border-radius: 50%;
  border: 1px solid rgba(170,239,219,0.06);
  pointer-events: none;
}
floating-chip validated atom ▼ chi tiết

Một huy hiệu nhỏ nổi đè lên ảnh mockup, làm một tính năng trông cụ thể, "đang chạy thật". Mắt nhảy tới nó sau khi đọc tiêu đề. Dùng trên mockup ở hero.

Ràng buộc: Dùng cho mockup ở hero; trên mobile thì ẩn hoặc ghim cố định (CHECKLIST § D).
Hợp cặp: media/mockup-chrome, motion/tier-b-component
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/decor/floating-chip.md
Khung: Nền: Mở riêng ↗
HTML
<div class="hero-mockup">
  ...
  <div class="hero-chip" style="top: -16px; right: -24px;">
    <div class="hero-chip-icon"><svg>...</svg></div>
    <div class="hero-chip-text">
      <strong>5.000+ payslips</strong>
      <span>chuẩn xác mỗi tháng</span>
    </div>
  </div>
</div>
CSS
.hero-chip {
  position: absolute;
  background: var(--white);
  border-radius: 14px;
  padding: 12px 16px;
  box-shadow: var(--shadow-3-float);
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 13px;
  z-index: 3;
  border: 1px solid rgba(20,121,108,0.10);
}
.hero-chip-icon {
  width: 38px; height: 38px;
  border-radius: 10px;
  display: grid;
  place-items: center;
  flex-shrink: 0;
  color: var(--white);
  background: var(--teal);
}
.hero-chip-icon svg { width: 20px; height: 20px; }
.hero-chip-text strong {
  display: block;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--text-primary);
  line-height: 1.2;
  margin-bottom: 2px;
}
.hero-chip-text span {
  font-size: 11px;
  color: var(--text-muted);
}
orbital-ring validated atom ▼ chi tiết

Các vòng tròn nét đứt bao quanh một mockup thiết bị, gợi cảm giác có hoạt động xoay quanh mà không cần animation. Làm ảnh chụp tĩnh trông sống động hơn.

Ràng buộc: Đi cặp với media/mockup-chrome; tĩnh - để dành ngân sách chuyển động cho T-B/T-C.
Hợp cặp: media/mockup-chrome
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/decor/orbital-ring.md
Khung: Nền: Mở riêng ↗
HTML
<div style="position:relative;min-height:300px;display:flex;align-items:center;justify-content:center;background:var(--bg-light);border-radius:12px;overflow:hidden">
  <div class="orbital-rings"></div>
  <div style="position:relative;width:180px;height:120px;background:var(--white);border-radius:10px;box-shadow:0 12px 28px rgba(7,49,48,.12);display:flex;align-items:center;justify-content:center;font-size:13px;color:var(--text-muted)">device mockup</div>
</div>
CSS
.orbital-rings::before,
.orbital-rings::after {
  content: '';
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  border: 1.5px dashed rgba(20,121,108,0.18);
  pointer-events: none;
}
.orbital-rings::before { width: 90%; aspect-ratio: 1; }
.orbital-rings::after {
  width: 70%; aspect-ratio: 1;
  border-color: rgba(228,144,37,0.22);
  animation: ringRotate 30s linear infinite;
}
@keyframes ringRotate {
  from { transform: translate(-50%, -50%) rotate(0deg); }
  to { transform: translate(-50%, -50%) rotate(360deg); }
}

dividers/

Chuyển tiếp section

diagonal experimental atom ▼ chi tiết

Đường cắt xiên giữa hai section, một bên hụt xuống một bên nhô lên. Nhanh và dứt khoát hơn sóng, đọc như một nhát cắt chứ không phải một chỗ chuyển mềm. Làm section phía dưới trông như trượt vào từ bên cạnh.

Ràng buộc: Một trang tối đa 1 đường xiên, dùng 2 lần trở lên là trang trông rối. Không dùng ngay dưới hero (hero đã có nhịp riêng). Màu của khối xiên phải bằng đúng màu nền section phía dưới. Motion: không.
Hợp cặp: backgrounds/dark-band, backgrounds/tint-panel
Nguồn gốc: new 2026-08
File: brands/terra/patterns/dividers/diagonal.md
Khung: Nền: Mở riêng ↗
HTML
<div class="dv-diagonal" aria-hidden="true"></div>
CSS
.terra-page .dv-diagonal {
  height: 90px; width: 100%;
  background: var(--bg-light);          /* đổi cho khớp nền section phía DƯỚI */
  clip-path: polygon(0 100%, 100% 0, 100% 100%);
}

@media (max-width: 767px) { .terra-page .dv-diagonal { height: 48px; } }
hairline-fade experimental atom ▼ chi tiết

Một sợi chỉ mảnh đậm ở giữa rồi tan dần về hai mép. Gần như không thấy, nhưng mắt biết là vừa hết một ý và sang ý khác. Dùng khi hai section cùng nền và cùng nhịp, cần vạch ranh mà không được làm nặng trang.

Ràng buộc: Chỉ dùng giữa hai section CÙNG màu nền, khác nền thì bản thân màu đã là ranh giới rồi. Dùng thoải mái nhiều lần trên một trang, đây là loại chuyển tiếp rẻ nhất. Motion: không.
Hợp cặp: backgrounds/tint-panel, structures/grid-3-2
Nguồn gốc: new 2026-08
File: brands/terra/patterns/dividers/hairline-fade.md
Khung: Nền: Mở riêng ↗
HTML
<div class="dv-hairline" aria-hidden="true"></div>
CSS
.terra-page .dv-hairline {
  height: 1px; width: 100%; border: 0;
  background: linear-gradient(90deg, transparent 0%, var(--border) 22%, var(--border) 78%, transparent 100%);
}
overlap-lift experimental atom ▼ chi tiết

Khối đầu tiên của section dưới nhô lên đè lên section trên. Hai section dính vào nhau thành một mạch, và cái khối nhô lên tự nhiên thành thứ mắt nhìn thấy trước tiên. Dùng khi muốn nhấn đúng một thứ ở đầu section mà không cần phóng to nó.

Ràng buộc: Chỉ nhô 1 khối, nhô 2 khối trở lên là mất nghĩa. Section trên phải chừa đủ padding dưới để khối nhô không đè lên chữ. Xuống 767px thì bỏ nhô, trả về xếp dọc bình thường. Khối nhô cần bóng từ --shadow-3-float trở lên mới ra hiệu ứng nổi. Motion: T-A.
Hợp cặp: forms/cf7-form-kit, cards/card-treatments, structures/proof-row-3stat
Nguồn gốc: new 2026-08
File: brands/terra/patterns/dividers/overlap-lift.md
Khung: Nền: Mở riêng ↗
HTML
<section style="padding-bottom: 120px">[section trên]</section>
<section class="dv-overlap">
  <div class="wrap">
    <div class="dv-overlap-lift">[khối nhô lên — form, dải số, thẻ nổi]</div>
    [phần còn lại của section dưới]
  </div>
</section>
CSS
.terra-page .dv-overlap { position: relative; }
.terra-page .dv-overlap-lift {
  margin-top: -80px; position: relative; z-index: 3;
  border-radius: 16px; background: var(--white);
  box-shadow: var(--shadow-3-float);
}

@media (max-width: 767px) {
  .terra-page .dv-overlap-lift { margin-top: 0; box-shadow: var(--shadow-1-base); }
}
wave-divider validated atom ▼ chi tiết

Một đường lượn sóng nối giữa hai section, làm trang trôi liền thay vì xếp chồng cứng. Chỗ hai section tương phản mạnh thì bỏ đi cũng là một lựa chọn hợp lý.

Ràng buộc: Màu sóng phải trùng nền section PHÍA SAU; section phía sau KHÔNG có border-top (CHECKLIST § C).
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/dividers/wave-divider.md
Khung: Nền: Mở riêng ↗
HTML
<svg class="hero-wave" viewBox="0 0 1440 80" preserveAspectRatio="none" aria-hidden="true">
  <path d="M0,40 C360,80 1080,0 1440,40 L1440,80 L0,80 Z"></path>
</svg>

<svg class="hero-wave" viewBox="0 0 1440 80" preserveAspectRatio="none" aria-hidden="true">
  <path d="M0,20 C240,60 480,80 720,40 C960,0 1200,40 1440,60 L1440,80 L0,80 Z"></path>
</svg>

<svg class="hero-wave" viewBox="0 0 1440 80" preserveAspectRatio="none" aria-hidden="true">
  <path d="M0,60 C480,20 960,60 1440,20 L1440,80 L0,80 Z"></path>
</svg>
CSS
.hero-wave {
  position: absolute;
  bottom: -2px; left: 0; right: 0;
  width: 100%; height: 80px;
  display: block;
  z-index: 1;
  pointer-events: none;
}
.hero-wave path { fill: var(--bg-light); }  /* MATCH next section's bg */

@media (max-width: 900px) {
  .hero-wave { height: 48px; }
}

forms/

Form

cf7-form-kit locked block ▼ chi tiết

Ô điền nằm trong một khung nổi hẳn lên khỏi nền, có bóng đủ nặng để đọc như một vật thể riêng chứ không phải một phần của trang. Các ô nhập gọn và thưa, nhìn vào thấy điền nhanh, không thấy mệt.

Ràng buộc: Dòng đánh dấu CONTACT_FORM ngay trên thẻ <form>; ô nhập + nút gửi nằm trong form, tiêu đề/phụ đề nằm ngoài; form không phải CTA thì gắn class ppp-keep để bảo vệ (CORE_RULES § 5.5).
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/forms/cf7-form-kit.md
Khung: Nền: Mở riêng ↗
HTML
<div class="hero-form" style="max-width:420px">
  <!-- CONTACT_FORM -->
  <form>
    <div class="form-field"><label class="form-label">Họ và tên</label><input type="text" placeholder="Nguyễn Văn A"></div>
    <div class="form-field"><label class="form-label">Email công ty</label><input type="email" placeholder="ten@congty.com"></div>
    <div class="form-field"><label class="form-label">Số điện thoại</label><input type="tel" placeholder="09xx xxx xxx"></div>
    <button type="submit" class="btn-primary" style="border-radius:999px;width:100%;padding:13px;background:var(--teal);color:#fff;border:none;font-weight:600;font-family:inherit">Đăng ký trao đổi</button>
  </form>
</div>
CSS
.form-field {
  width: 100%;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px 16px;
  font-family: inherit;
  font-size: 14px;
  color: var(--text-primary);
  background: var(--white);
}
.form-field:focus {
  outline: none;
  border-color: var(--teal);
  box-shadow: 0 0 0 3px rgba(20,121,108,0.12);
}
.form-label {
  display: block;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--text-primary);
  margin-bottom: 6px;
}

.hero-form {
  background: var(--white);
  border-radius: 16px;
  padding: 32px;
  box-shadow: 0 8px 40px rgba(7,49,48,0.2);
}
.hero-form h3 {
  font-size: 17px;
  font-weight: 700;
  color: var(--text-primary);
  margin-bottom: 6px;
}
.hero-form .form-sub {
  font-size: 13px;
  color: var(--text-muted);
  margin-bottom: 20px;
}
.hero-form .form-note {
  font-size: 12px;
  color: var(--text-muted);
  margin-top: 12px;
}

foundations/

Nền tảng (nút, tag, icon, bóng đổ)

buttons locked atom ▼ chi tiết

Bộ nút bấm. Một trang chỉ một nút cam duy nhất cho hành động quan trọng nhất, để nó không thể bỏ sót; nút teal lo các hành động thường. Tất cả nút bo tròn viên thuốc, cùng chiều cao.

Ràng buộc: Bo góc 999px (viên thuốc), mọi biến thể cùng chiều cao/padding (CORE_RULES iron rule 5); tối đa 1 nút cam/trang; font kế thừa.
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/foundations/buttons.md
Khung: Nền: Mở riêng ↗
HTML
<a class="btn btn-primary" href="#">Đặt lịch tư vấn</a>
<a class="btn btn-ghost" href="#">Tải brochure</a>
<a class="btn btn-orange" href="#">Nhận báo giá ngay</a>   <!-- max 1/page -->
<a class="btn btn-light" href="#">Liên hệ terra</a>        <!-- dark bg -->
<a class="btn btn-text" href="#">Xem chi tiết dịch vụ</a>
CSS
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 12px 28px;
  border-radius: 999px;
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.005em;
  text-decoration: none;
  cursor: pointer;
  border: 1px solid transparent;
  transition: transform .18s ease, box-shadow .18s ease, background .18s ease;
}
.btn:hover { transform: translateY(-2px); }

.btn-primary {
  background: var(--teal);
  color: var(--white);
  box-shadow: 0 4px 14px rgba(20,121,108,0.25);
}
.btn-primary:hover { box-shadow: 0 8px 24px rgba(20,121,108,0.32); }

.btn-orange {                       /* MAX 1 per page */
  background: var(--orange);
  color: var(--white);
  box-shadow: 0 4px 14px rgba(228,144,37,0.30);
}
.btn-orange:hover { box-shadow: 0 8px 24px rgba(228,144,37,0.38); }

.btn-ghost {
  background: transparent;
  border-color: var(--teal);
  color: var(--teal);
}
.btn-ghost:hover { background: rgba(20,121,108,0.06); }

.btn-light {                        /* on dark backgrounds */
  background: var(--white);
  color: var(--teal);
}
.btn-light--outline {
  background: transparent;
  border-color: rgba(255,255,255,0.55);
  color: var(--white);
}

.btn-text {                         /* arrow link */
  background: none;
  border: none;
  padding: 12px 4px;
  color: var(--teal);
}
.btn-text::after { content: '→'; transition: transform .18s ease; }
.btn-text:hover::after { transform: translateX(4px); }
callout validated atom ▼ chi tiết

Một hộp tô nền tách một đoạn ra khỏi mạch đọc, để mắt vẫn thấy nó khi đang lướt nhanh. Có thêm biến thể "cảnh báo" tạo độ khẩn mà không gắt như báo lỗi đỏ.

Ràng buộc: Chỉ có 2 loại: thường + cảnh báo; chữ không có em dash (iron rule 3).
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/foundations/callout.md
Khung: Nền: Mở riêng ↗
HTML
<div style="display:grid;gap:16px;max-width:560px">
  <div class="callout-box"><strong>Standard callout.</strong> One paragraph pulled out of the reading flow so a scanning eye still lands on it.</div>
  <div class="callout-box callout-box--warning"><strong>Deadline 31/03:</strong> annual PIT finalization. Warning variant adds urgency without panic-red.</div>
</div>
CSS
.callout-box {
  background: var(--teal-tint);
  border-left: 3px solid var(--teal);
  border-radius: 0 10px 10px 0;
  padding: 20px 24px;
  margin-top: 28px;
}
.callout-box .callout-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--teal);
  margin-bottom: 8px;
}
.callout-box p {
  font-size: 14px;
  color: var(--text-primary);
  margin-bottom: 0;
}
.callout-box--warning {
  background: rgba(228,144,37,0.08);
  border-color: rgba(228,144,37,0.35);
}
.callout-box--warning .callout-label { color: var(--orange); }
icons validated atom ▼ chi tiết

Bộ icon vẽ bằng SVG nội tuyến, tự lấy màu theo màu chữ xung quanh nên không bao giờ chỏi với màu của section.

Ràng buộc: Chỉ SVG nội tuyến, dùng currentColor, lệnh path viết HOA tuyệt đối có dấu cách rõ ràng (lưu ý: SVG render khác nhau giữa các môi trường).
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/foundations/icons.md
Khung: Nền: Mở riêng ↗
HTML
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
  <circle cx="8" cy="8" r="7.5" stroke="currentColor"/>
  <path d="M4.5 8l2.5 2.5 4.5-4.5" stroke="currentColor"
        stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
shadow-ladder validated atom ▼ chi tiết

Bộ cấp độ đổ bóng cho thẻ. Dùng nhiều cấp khác nhau trên cùng trang để trang trông có lớp lang, không bị dẹt một mức.

Ràng buộc: Chỉ dùng token có sẵn trong thang - không tự chế giá trị box-shadow (CHECKLIST § C).
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/foundations/shadow-ladder.md
Khung: Nền: Mở riêng ↗
HTML
<div class="about-stat" style="box-shadow: var(--shadow-5-stacked);">
  <div class="about-stat-flag">
    <span class="flag flag-jp"></span>
    <span>FDI Nhật</span>
  </div>
  <span class="n">80%</span>
  <span class="l">khách hàng từ FDI Nhật Bản</span>
</div>
CSS
.shadow-1 { box-shadow: var(--shadow-1-base); }
.shadow-2 { box-shadow: var(--shadow-2-lift); }
.shadow-3 { box-shadow: var(--shadow-3-float); }
.shadow-4 { box-shadow: var(--shadow-4-premium); }
.shadow-5 { box-shadow: var(--shadow-5-stacked); }
.shadow-glow-teal   { box-shadow: var(--shadow-glow-teal); }
.shadow-glow-orange { box-shadow: var(--shadow-glow-orange); }

/* Default card with hover lift */
.card-with-lift {
  transition: transform .25s ease, box-shadow .25s ease;
  box-shadow: var(--shadow-1-base);
}
.card-with-lift:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-2-lift);
}
tags validated atom ▼ chi tiết

Nhãn nhỏ ghi thông tin phân loại mà không giành sự chú ý; nhãn "giai đoạn" làm các bước quy trình trông đồng bộ.

Ràng buộc: Chỉ IN HOA, tối đa 4 chữ, giãn chữ theo iron rule 2.
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/foundations/tags.md
Khung: Nền: Mở riêng ↗
HTML
<div style="display:flex;gap:12px;flex-wrap:wrap;align-items:center">
  <span class="tag">BPAAS</span>
  <span class="tag tag--neutral">HR SYSTEM</span>
  <span class="phase-tag">PHASE 01</span>
  <span class="phase-tag">PHASE 02</span>
</div>
CSS
.tag {
  display: inline-block;
  border-radius: 10px;
  border: 1px solid rgba(20,121,108,0.25);
  padding: 4px 12px;
  font-size: 12px;
  font-weight: 600;
  color: var(--teal);
  background: var(--teal-tint);
}
.tag--neutral {
  color: var(--text-body);
  background: var(--bg-light);
  border-color: var(--border);
}

.phase-tag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: transparent;
  color: var(--teal);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 4px 12px;
  border-radius: 999px;
  border: 1.5px solid var(--teal);
  margin-bottom: 16px;
}

interactive/

Tương tác (tab, slider, quiz)

eligibility-quiz validated block ▼ chi tiết

Bo cau hoi Yes/No + progress bar -> ket qua du/chua du dieu kien + CTA. Loc/dinh huong khach tr(uoc khi vao noi dung. JS cuoi body, T-B.

Ràng buộc: Block HTML+JS custom (khong phai Elementor form). JS cuoi body. Ket qua chi mang tinh tham khao - luon kem disclaimer. Scope .terra-page.
Nguồn gốc: BHXH page 2026-07
File: brands/terra/patterns/interactive/eligibility-quiz.md
Khung: Nền: Mở riêng ↗
HTML
<section class="sec" style="background:var(--bg)" id="eligibility">
  <div class="wrap">
    <div class="ch">
      <h2>Bạn có đủ điều kiện rút BHXH 1 lần không?</h2>
      <p class="lead">Trả lời 5 câu hỏi dưới đây để biết khả năng đủ điều kiện rút BHXH theo Luật BHXH Việt Nam.</p>
    </div>
    <div class="ec-wrap">
      <div class="ec-progress" id="ecProgress">
        <span class="on"></span><span></span><span></span><span></span><span></span>
      </div>
      <div class="ec-question" id="ecQuestion">
        <span class="ec-q-num">Câu 1 / 5</span>
        <p class="ec-q-text">Bạn đã chấm dứt hợp đồng lao động tại Việt Nam chưa?</p>
        <div class="ec-options">
          <button class="ec-opt" onclick="ecAnswer(0,true)"><span class="ec-opt-icon">✓</span> Đã chấm dứt</button>
          <button class="ec-opt" onclick="ecAnswer(0,false)"><span class="ec-opt-icon">✗</span> Chưa chấm dứt</button>
        </div>
      </div>
      <div class="ec-result" id="ecResult">
        <div class="ec-result-icon"></div>
        <h3 id="ecResultTitle"></h3>
        <p class="ec-result-text" id="ecResultText"></p>
        <div class="ec-result-cta">
          <a href="#final-cta" class="btn bt">Tư vấn miễn phí với chuyên gia</a>
          <a href="#" class="btn bout" onclick="ecRestart();return false;">Làm lại</a>
        </div>
      </div>
      <p class="ec-disclaimer">Công cụ này chỉ mang tính tham khảo. Điều kiện cuối cùng được xác định bởi cơ quan BHXH Việt Nam và phụ thuộc hồ sơ thực tế.</p>
    </div>
  </div>
</section>
JS
// ELIGIBILITY CHECKER
const ecQuestions = [
  {
    text: "Bạn đã chấm dứt hợp đồng lao động tại Việt Nam chưa?",
    optYes: "Đã chấm dứt",
    optNo: "Chưa chấm dứt",
    weight: 2
  },
  {
    text: "Bạn đã đóng BHXH bắt buộc tại Việt Nam ít nhất 1 năm?",
    optYes: "Đã đóng từ 1 năm",
    optNo: "Dưới 1 năm",
    weight: 2
  },
  {
    text: "Bạn đã hoặc sẽ rời Việt Nam vĩnh viễn?",
    optYes: "Có",
    optNo: "Chưa rõ",
    weight: 2
  },
  {
    text: "Bạn không có ý định quay lại làm việc tại Việt Nam trong 12 tháng tới?",
    optYes: "Không quay lại",
    optNo: "Có thể quay lại",
    weight: 1
  },
  {
    text: "Bạn cần đại diện thực hiện thủ tục thay vì tự mình?",
    optYes: "Cần đại diện",
    optNo: "Tự thực hiện",
    weight: 1
  }
];
let ecIndex = 0;
let ecScore = 0;
let ecAnswers = [];

function ecAnswer(qIdx, isYes){
  if(qIdx !== ecIndex) return;
  ecAnswers.push(isYes);
  if(isYes) ecScore += ecQuestions[qIdx].weight;
  ecIndex++;
  if(ecIndex < ecQuestions.length){
    ecRender();
  } else {
    ecShowResult();
  }
}

function ecRender(){
  const q = ecQuestions[ecIndex];
  const html = `
    <span class="ec-q-num">Câu ${ecIndex + 1} / ${ecQuestions.length}</span>
    <p class="ec-q-text">${q.text}</p>
    <div class="ec-options">
      <button class="ec-opt" onclick="ecAnswer(${ecIndex},true)"><span class="ec-opt-icon">✓</span> ${q.optYes}</button>
      <button class="ec-opt" onclick="ecAnswer(${ecIndex},false)"><span class="ec-opt-icon">✗</span> ${q.optNo}</button>
    </div>
  `;
  document.getElementById('ecQuestion').innerHTML = html;
  // Update progress
  const bars = document.querySelectorAll('#ecProgress span');
  bars.forEach((b, i) => {
    b.classList.remove('on', 'done');
    if(i < ecIndex) b.classList.add('done');
    else if(i === ecIndex) b.classList.add('on');
  });
}

function ecShowResult(){
  document.getElementById('ecQuestion').style.display = 'none';
  const bars = document.querySelectorAll('#ecProgress span');
  bars.forEach(b => { b.classList.remove('on'); b.classList.add('done'); });
  const result = document.getElementById('ecResult');
  const titleEl = document.getElementById('ecResultTitle');
  const textEl = document.getElementById('ecResultText');
  const iconEl = result.querySelector('.ec-result-icon');

  // Logic: cần ít nhất 6 điểm trên 8 (3 câu đầu mandatory với weight 2 = 6 điểm)
  // Q1, Q2, Q3 cần Yes để eligible
  const coreEligible = ecAnswers[0] && ecAnswers[1] && ecAnswers[2];
  
  if(coreEligible){
    result.classList.add('vis', 'eligible');
    iconEl.innerHTML = '✓';
    titleEl.textContent = 'Bạn có khả năng đủ điều kiện rút BHXH 1 lần';
    textEl.innerHTML = 'Theo các câu trả lời, bạn có thể đáp ứng điều kiện rút BHXH 1 lần theo Luật BHXH Việt Nam. Tư vấn miễn phí với chuyên gia terra để xác nhận chính xác và bắt đầu thủ tục.';
  } else {
    result.classList.add('vis', 'partial');
    iconEl.innerHTML = '!';
    let missingItems = [];
    if(!ecAnswers[0]) missingItems.push('chưa chấm dứt hợp đồng');
    if(!ecAnswers[1]) missingItems.push('thời gian đóng BHXH dưới 1 năm');
    if(!ecAnswers[2]) missingItems.push('chưa rõ kế hoạch rời Việt Nam');
    titleEl.textContent = 'Cần tư vấn thêm cho trường hợp của bạn';
    textEl.innerHTML = 'Trường hợp của bạn cần tư vấn cụ thể vì có yếu tố: <strong>' + missingItems.join(', ') + '</strong>. Chuyên gia terra sẽ đánh giá từng tình huống để đưa ra phương án phù hợp.';
  }
}

function ecRestart(){
  ecIndex = 0;
  ecScore = 0;
  ecAnswers = [];
  document.getElementById('ecQuestion').style.display = 'block';
  document.getElementById('ecResult').classList.remove('vis', 'eligible', 'partial');
  ecRender();
}
estimate-flip-calculator validated block ▼ chi tiết

Nhap vai so -> lat the (flip) ra con so uoc tinh + form thu lead (CF7). Mau tinh-nhanh-doi-lead cho trang co yeu to tien/chi phi. JS cuoi body, T-B.

Ràng buộc: Con so chi la "uoc tinh" - ghi ro. Form CF7 co field an (vd calc-years/calc-salary/calc-estimate) + marker CONTACT_FORM ngay tren <form>. JS cuoi body, scope .terra-page.
Nguồn gốc: BHXH page 2026-07
File: brands/terra/patterns/interactive/estimate-flip-calculator.md
Khung: Nền: Mở riêng ↗
HTML
<section class="sec" style="background:var(--w)" id="calculator">
  <div class="wrap">
    <div class="ch">
      <h2>Ước tính số tiền BHXH bạn có thể rút</h2>
      <p class="lead">Nhập nhanh 2 thông tin để xem <strong>số thực nhận ước tính</strong> sau khi đã trừ phí dịch vụ trọn gói của terra.</p>
    </div>
    <div class="calc-wrap">
      <div class="calc-inputs">
        <div class="calc-field"><label>Số năm đã đóng BHXH tại Việt Nam</label><input type="number" id="calcYears" min="1" max="40" step="0.5" placeholder="VD: 4"></div>
        <div class="calc-field"><label>Lương bình quân tháng đóng BHXH (VND)</label><input type="number" id="calcSalary" min="0" step="1000000" placeholder="VD: 30000000"></div>
        <button type="button" class="btn bt" id="calcBtn" style="width:100%;margin-top:4px">Tính số tiền ước tính</button>
        <p class="calc-note">Đây là con số ước tính dựa trên công thức chuẩn.</p>
      </div>
      <div class="calc-flip" id="calcFlip">
        <div class="calc-flip-inner">
          <div class="calc-face calc-face-front"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"><circle cx="16" cy="7.5" r="3.6"/><path d="M16 6.2v2.6M14.7 7.1h2.6"/><path d="M2 13h3.6v7H2z"/><path d="M5.6 14.2h5.4l4.8-1.45c1.15-.34 2 .95 1.15 1.8l-4.2 3.35c-.63.5-1.45.8-2.3.8H5.6"/></svg></div>
          <div class="calc-face calc-face-back">
            <span class="calc-result-lbl">Số thực nhận ước tính (đã trừ phí terra)</span>
            <div class="calc-result-num" id="calcNet">0 đ</div>
            <!-- CONTACT_FORM -->
            <form class="calc-form" onsubmit="event.preventDefault();return false;" novalidate>
              <input type="hidden" name="calc-years" id="fYears"><input type="hidden" name="calc-salary" id="fSalary"><input type="hidden" name="calc-estimate" id="fEstimate">
              <div class="calc-form-row"><input type="text" name="fullname" placeholder="Họ và tên" required><input type="email" name="email" placeholder="Email" required></div>
              <button type="submit" class="btn bo">Nhận bảng tính chi tiết + thời gian rút</button>
              <p class="calc-form-note">terra gửi bảng tính chính xác và thời gian dự kiến trong 1 ngày làm việc. Bảo mật theo ISO/IEC 27001:2022.</p>
            </form>
          </div>
        </div>
      </div>
    </div>
  </div>
</section>
JS
(function(){
  var btn=document.getElementById('calcBtn'); if(!btn) return;
  function fmt(n){return n.toLocaleString('vi-VN')+' đ';}
  btn.addEventListener('click',function(){
    var y=parseFloat(document.getElementById('calcYears').value)||0;
    var sal=parseFloat(document.getElementById('calcSalary').value)||0;
    if(y<=0||sal<=0){document.getElementById('calcYears').focus();return;}
    var gross=Math.round(2*sal*y);
    var fee=Math.max(Math.round(gross*0.10),15000000);
    var net=Math.max(gross-fee,0);
    document.getElementById('calcNet').textContent=fmt(net);
    document.getElementById('fYears').value=y;
    document.getElementById('fSalary').value=sal;
    document.getElementById('fEstimate').value=gross;
    document.getElementById('calcFlip').classList.add('flipped');
  });
})();
(function(){
  var els=document.querySelectorAll('.stat-item .n[data-count]'); if(!els.length) return;
  var reduce=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  function run(el){var t=parseInt(el.getAttribute('data-count'),10),suf=el.getAttribute('data-suffix')||'';
    if(reduce){el.textContent=t.toLocaleString('vi-VN')+suf;return;}
    var st=null;function tk(ts){if(!st)st=ts;var p=Math.min((ts-st)/1400,1);var e=1-Math.pow(1-p,3);
      el.textContent=Math.floor(e*t).toLocaleString('vi-VN')+suf;if(p<1)requestAnimationFrame(tk);else el.textContent=t.toLocaleString('vi-VN')+suf;}
    requestAnimationFrame(tk);}
  if('IntersectionObserver' in window){var io=new IntersectionObserver(function(en){en.forEach(function(e){if(e.isIntersecting){run(e.target);io.unobserve(e.target);}});},{threshold:0.5});els.forEach(function(el){io.observe(el);});}
  else els.forEach(run);
})();
hero-slider validated block ▼ chi tiết

Nhiều thông điệp thay nhau ở cùng một chỗ, hàng tab nằm dưới cùng như thanh chỉ số. Người xem đọc xong dòng đầu mới phát hiện ra còn slide khác, nên không bị chia trí ngay từ giây đầu. Vạch cam chạy dưới tab đang mở cho biết sắp sang slide kế, tạo cảm giác trang đang sống.

Ràng buộc: Hàng tab BẮT BUỘC nằm DƯỚI phần slide, không được đặt trên (bản v7 đặt trên đã bị bác). Phải khoá min-height ở CẢ BA tầng (slide, cột hình, khối mockup), chỉ khoá tầng ngoài là vẫn giật khi đổi slide. Nút tab phải khai font-family:inherit vì thẻ button không tự thừa kế. Motion: T-B. Xuống 600px thì tab xếp dọc.
Hợp cặp: structures/hero-split-5050, media/photo-wash, media/mockup-chrome, motion/tier-b-component
Nguồn gốc: tách ra từ compositions/hero-homepage-light khi cải tổ store 08/2026 (bản v6 canonical)
File: brands/terra/patterns/interactive/hero-slider.md
Khung: Nền: Mở riêng ↗
HTML
<div class="hslider" data-active="0">
  <div class="hslider-slide is-active" data-slide="0" role="tabpanel">[unit — nội dung slide 1]</div>
  <div class="hslider-slide" data-slide="1" role="tabpanel">[unit — nội dung slide 2]</div>
  <div class="hslider-slide" data-slide="2" role="tabpanel">[unit — nội dung slide 3]</div>

  <div class="hslider-controls">
    <div class="hslider-tablist" role="tablist">
      <button class="hslider-tab is-active" data-slide="0" role="tab" aria-selected="true">
        <span class="hslider-tab-num">01</span><span class="hslider-tab-label">[nhãn slide 1]</span>
      </button>
      <button class="hslider-tab" data-slide="1" role="tab" aria-selected="false">
        <span class="hslider-tab-num">02</span><span class="hslider-tab-label">[nhãn slide 2]</span>
      </button>
      <button class="hslider-tab" data-slide="2" role="tab" aria-selected="false">
        <span class="hslider-tab-num">03</span><span class="hslider-tab-label">[nhãn slide 3]</span>
      </button>
    </div>
    <div class="hslider-counter"><strong>01</strong> / 03</div>
  </div>
</div>
CSS
/* Khoá chiều cao 3 tầng — chống giật khi đổi slide */
.terra-page .hslider-slide { min-height: 480px; }
.terra-page .hslider-slide .hero-55-media { min-height: 460px; display: flex; align-items: center; justify-content: center; }
.terra-page .hslider-mockup { min-height: 380px; display: flex; flex-direction: column; justify-content: flex-start; }
.terra-page .hslider-mockup > *:last-child { margin-bottom: 0; }

.terra-page .hslider-slide:not(.is-active) { display: none; }
.terra-page .hslider-slide.is-active { animation: slideFade .6s ease; }
@keyframes slideFade {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

.terra-page .hslider-controls {
  margin-top: 56px; position: relative; z-index: 2;
  display: flex; justify-content: space-between; align-items: center;
  gap: 24px; flex-wrap: wrap;
}
.terra-page .hslider-tablist { display: flex; gap: 8px; flex: 1; min-width: 0; }
.terra-page .hslider-tab {
  flex: 1; min-width: 0; padding: 14px 16px;
  font-family: inherit;                /* BẮT BUỘC — button không tự thừa kế font */
  background: var(--white); color: var(--text-muted);
  border: 1px solid rgba(20,121,108,.14); border-radius: 12px;
  text-align: left; cursor: pointer; position: relative; overflow: hidden;
  transition: background .25s, color .25s, border-color .25s;
}
.terra-page .hslider-tab:hover { color: var(--text-primary); border-color: var(--teal); }
.terra-page .hslider-tab.is-active { background: var(--teal); color: var(--white); border-color: var(--teal); }
.terra-page .hslider-tab-num {
  display: block; margin-bottom: 4px;
  font-size: 11px; font-weight: 700; letter-spacing: .06em; opacity: .7;
}
.terra-page .hslider-tab-label {
  display: block; font-size: 13px; font-weight: 600; letter-spacing: -.005em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.terra-page .hslider-tab.is-active::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
  background: var(--orange); transform-origin: left;
  animation: tabBar 8s linear forwards;
}
@keyframes tabBar { from { transform: scaleX(0); } to { transform: scaleX(1); } }
.terra-page .hslider-counter {
  font-size: 12px; font-weight: 600; color: var(--text-muted);
  letter-spacing: .04em; white-space: nowrap;
}
.terra-page .hslider-counter strong { color: var(--teal); font-weight: 800; font-size: 14px; }

@media (max-width: 600px) {
  .terra-page .hslider-tablist { flex-direction: column; }
  .terra-page .hslider-tab { padding: 12px 14px; }
  .terra-page .hslider-slide { min-height: 0; }
  .terra-page .hslider-slide .hero-55-media { min-height: 0; }
}
JS
document.querySelectorAll('.hslider').forEach(function (root) {
  var tabs = root.querySelectorAll('.hslider-tab');
  var slides = root.querySelectorAll('.hslider-slide');
  var counter = root.querySelector('.hslider-counter');
  var timer;

  function pad(n) { return (n < 10 ? '0' : '') + n; }

  function go(i) {
    slides.forEach(function (s, k) { s.classList.toggle('is-active', k === i); });
    tabs.forEach(function (t, k) {
      t.classList.toggle('is-active', k === i);
      t.setAttribute('aria-selected', k === i ? 'true' : 'false');
    });
    // Nếu section dùng media/photo-wash thì đổi ảnh nền theo slide luôn
    root.parentNode.querySelectorAll('.photo-wash').forEach(function (img, k) {
      img.classList.toggle('is-visible', k === i);
    });
    if (counter) counter.innerHTML = '<strong>' + pad(i + 1) + '</strong> / ' + pad(slides.length);
    root.dataset.active = i;
    clearTimeout(timer);
    timer = setTimeout(function () { go((i + 1) % slides.length); }, 8000);
  }

  tabs.forEach(function (t, k) { t.addEventListener('click', function () { go(k); }); });
  if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) { go(0); }
});
scroll-stack validated block ▼ chi tiết

Cot the xep chong khi cuon: section PIN dung yen, moi nac cuon lo 1 the truot len xep chong; the cuoi hien luc section con dung yen roi moi nha. Ho tro dieu huong ban phim: 1 phim mui ten / PageUp-Down = 1 the (snap), doi xung 2 chieu. Cuon chuot muot theo he so COEF. Mobile: tat pin, cac the xep doc.

Ràng buộc: JS cuoi body. Pin chi bat >=901px va khi khong reduced-motion. disablePin() phai xoa het inline style (transform/opacity/height/zIndex) tra layout cho CSS. He so COEF (mac dinh 0.24) chinh quang cuon moi the. shownFor() dung 1+floor(p*n) => cards 2..n lo deu, the cuoi co "hold" truoc khi nha. Muc T-B.
Nguồn gốc: terra homepage 2026-06 -> nang cap BHXH page 2026-07 (keyboard-accessible + trailing hold)
File: brands/terra/patterns/interactive/scroll-stack.md
Khung: Nền: Mở riêng ↗
HTML
<section class="sec" id="different" style="background:var(--bg)">
  <div class="diff-pin-outer" id="diffPin">
    <div class="diff-pin-inner">
      <div class="wrap">
        <div class="diff-layout">
          <div class="diff-intro">
            <h2>Tieu de phan</h2>
            <p class="diff-lead">Doan dan tom tat cac the ben phai.</p>
          </div>
          <div class="stack-wrap" id="stackWrap">
            <div class="stack-card stack-card--tint" data-s="0"><h3 class="stack-title">Luan diem 1</h3><div class="stack-detail"><p>...</p><span class="stack-proof">Bang chung</span></div></div>
            <div class="stack-card stack-card--light" data-s="1"><h3 class="stack-title">Luan diem 2</h3><div class="stack-detail"><p>...</p></div></div>
            <div class="stack-card stack-card--tint" data-s="2"><h3 class="stack-title">Luan diem 3</h3><div class="stack-detail"><p>...</p></div></div>
            <div class="stack-card stack-card--teal" data-s="3"><h3 class="stack-title">Luan diem 4 (chot)</h3><div class="stack-detail"><p>...</p></div></div>
          </div>
        </div>
      </div>
    </div>
  </div>
</section>
CSS
.terra-page .diff-pin-outer{position:relative}
.terra-page .diff-pin-inner{position:sticky;top:0;min-height:100vh;display:flex;align-items:flex-start;padding:clamp(40px,6vh,72px) 0}
.terra-page .diff-layout{display:grid;grid-template-columns:1fr 1.05fr;gap:64px;align-items:center;width:100%}
.terra-page .stack-wrap{position:relative;height:420px}
.terra-page .stack-card{position:absolute;left:0;right:0;top:0;border-radius:18px;padding:24px 30px;box-shadow:0 18px 48px rgba(7,49,48,.12);will-change:transform,opacity;transition:transform .55s cubic-bezier(.22,.61,.36,1),opacity .4s ease}
.terra-page .stack-card--light{background:var(--w);border:1px solid var(--bd)}
.terra-page .stack-card--tint{background:var(--tt);border:1px solid rgba(20,121,108,.16)}
.terra-page .stack-card--teal{background:var(--teal);border:1px solid var(--teal);color:var(--w)}
.terra-page #different.no-pin .diff-pin-outer{height:auto!important}
.terra-page #different.no-pin .diff-pin-inner{position:static;min-height:0;padding:64px 0}
.terra-page .stack-wrap.no-pin{height:auto!important}
.terra-page .stack-wrap.no-pin .stack-card{position:relative;transform:none!important;opacity:1;margin-bottom:18px}
.terra-page .stack-wrap.no-pin .stack-card:last-child{margin-bottom:0}
@media(max-width:900px){.terra-page .diff-layout{grid-template-columns:1fr;gap:32px}}
JS
(function(){
  var root=document;
  var diffPin=root.querySelector('#diffPin'), stackWrap=root.querySelector('#stackWrap');
  if(diffPin&&stackWrap){
    var cards=Array.prototype.slice.call(stackWrap.querySelectorAll('.stack-card'));
    var STEP=70;
    var prefersReduced=window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    var pinMQ=window.matchMedia('(min-width: 901px)');
    var pinActive=false, curShown=1;
    var n=cards.length;
    var COEF=0.24; // quang cuon moi the (tang=cham hon)
    function disablePin(){pinActive=false;diffPin.classList.add('no-pin');stackWrap.classList.add('no-pin');root.querySelector('#different').classList.add('no-pin');diffPin.style.height='';stackWrap.style.height='';cards.forEach(function(c){c.style.transform='';c.style.opacity='';c.style.zIndex='';});}
    function enablePin(){pinActive=true;diffPin.classList.remove('no-pin');stackWrap.classList.remove('no-pin');root.querySelector('#different').classList.remove('no-pin');layout();}
    function layout(){if(!pinActive)return;var maxH=0;cards.forEach(function(c){maxH=Math.max(maxH,c.offsetHeight);});var stageH=(n-1)*STEP+maxH;stackWrap.style.height=stageH+'px';var dist=Math.round(window.innerHeight*COEF*(n-1));diffPin.style.height=(window.innerHeight+dist)+'px';diffPin._dist=dist;update();}
    function render(shown){for(var i=0;i<n;i++){var c=cards[i];if(i<shown){c.style.transform='translateY('+(i*STEP)+'px)';c.style.opacity='1';}else{c.style.transform='translateY('+(i*STEP+56)+'px)';c.style.opacity='0';}c.style.zIndex=i+1;}}
    function progress(){var rect=diffPin.getBoundingClientRect();var dist=diffPin._dist||1;var p=Math.min(Math.max(-rect.top/dist,0),1);if(prefersReduced)p=1;return p;}
    function shownFor(p){return Math.min(n,Math.max(1,1+Math.floor(p*n)));}
    function update(){if(!pinActive)return;curShown=shownFor(progress());render(curShown);}
    function pinTop(){return window.pageYOffset+diffPin.getBoundingClientRect().top;}
    function goto(shown){var dist=diffPin._dist||1;var p=(shown-1)/n+0.03;window.scrollTo({top:Math.round(pinTop()+p*dist),behavior:'smooth'});}
    var ticking=false;
    function onScroll(){if(!ticking){ticking=true;requestAnimationFrame(function(){update();ticking=false;});}}
    window.addEventListener('scroll',onScroll,{passive:true});
    window.addEventListener('resize',function(){if(pinActive)layout();});
    document.addEventListener('keydown',function(e){
      if(!pinActive)return;
      var rect=diffPin.getBoundingClientRect(),dist=diffPin._dist||1;
      if(rect.top>2||rect.top<-dist+2)return;
      var dir=0;
      if(e.key==='ArrowDown'||e.key==='PageDown')dir=1;
      else if(e.key==='ArrowUp'||e.key==='PageUp')dir=-1; else return;
      if(dir===1){ if(curShown<n){e.preventDefault();goto(curShown+1);} else {e.preventDefault();window.scrollTo({top:Math.round(pinTop()+dist+40),behavior:'smooth'});} }
      else { if(curShown>1){e.preventDefault();goto(curShown-1);} else {e.preventDefault();window.scrollTo({top:Math.round(pinTop()-window.innerHeight*0.5),behavior:'smooth'});} }
    });
    function applyMode(){if(pinMQ.matches&&!prefersReduced)enablePin();else disablePin();}
    if(pinMQ.addEventListener)pinMQ.addEventListener('change',applyMode);else if(pinMQ.addListener)pinMQ.addListener(applyMode);
    if(document.fonts&&document.fonts.ready)document.fonts.ready.then(function(){if(pinActive)layout();});
    applyMode();
  }
})();
stepper-progress experimental block ▼ chi tiết

Một hàng bước đánh số nằm ngang, bấm vào bước nào thì nội dung bước đó hiện ra bên dưới. Người xem luôn biết mình đang ở đâu và còn bao nhiêu nữa, nên chịu đọc dài hơn. Vạch cam dưới bước đang mở và việc tự chạy sau 7 giây làm trang trông đang sống, dừng ngay khi rê chuột để không giành quyền điều khiển.

Ràng buộc: JS đặt cuối body (CORE_RULES § 5.3). Tự chạy chỉ bật khi cuộn tới (IntersectionObserver), không chạy ngầm từ lúc load. Mức T-B. Mobile flatten đã kèm: số La Mã + connector giữa, bỏ gạch chân từng tab. Số bước nên là 3 (data-stage 0/1/2); thêm bước thì thêm content::after cho .stepper-num.
Nguồn gốc: terra homepage VI/EN (Section 3, 2026-06-22)
File: brands/terra/patterns/interactive/stepper-progress.md
Khung: Nền: Mở riêng ↗
HTML
<div class="reveal" id="stageShow">
  <!-- Stepper rail -->
  <div class="stepper">
    <div class="stepper-tabs" role="tablist" aria-label="Các giai đoạn phát triển">
      <button class="stepper-tab is-active" data-stage="0" role="tab" aria-selected="true">
        <span class="stepper-num">1</span>
        <span class="stepper-label">Giai đoạn 1</span>
      </button>
      <button class="stepper-tab" data-stage="1" role="tab" aria-selected="false">
        <span class="stepper-num">2</span>
        <span class="stepper-label">Giai đoạn 2</span>
      </button>
      <button class="stepper-tab" data-stage="2" role="tab" aria-selected="false">
        <span class="stepper-num">3</span>
        <span class="stepper-label">Giai đoạn 3</span>
      </button>
    </div>
  </div>
  <!-- Panels -->
  <div class="stage-panels">
    <div class="stage-panel is-active" data-stage="0" role="tabpanel">
      <div class="stage-body">
        <h3>Tiêu đề bước 1</h3>
        <p class="stage-pain">Mô tả vấn đề của giai đoạn này.</p>
        <div class="stage-response"><p>terra giải quyết vấn đề đó như thế nào.</p></div>
      </div>
      <div class="stage-media">
        <img src="[Pexels URL]" alt="[mô tả] - [từ khóa]" loading="lazy">
      </div>
    </div>
    <!-- panel data-stage=1, =2 cùng cấu trúc -->
  </div>
</div>
CSS
.terra-page .stepper { margin-bottom: 8px; }
.terra-page .stepper-tabs {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 0; position: relative;
}
.terra-page .stepper-tab {
  position: relative; display: flex; align-items: center; gap: 12px;
  background: none; border: none; font-family: inherit; cursor: pointer;
  padding: 0 8px 18px; text-align: left; color: var(--text-muted); transition: color .25s;
}
.terra-page .stepper-tab:hover { color: var(--text-primary); }
.terra-page .stepper-tab.is-active { color: var(--text-primary); }
.terra-page .stepper-num {
  width: 34px; height: 34px; border-radius: 50%; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 14px; font-weight: 700;
  background: var(--white); border: 1.5px solid var(--border); color: var(--text-muted);
  transition: background .25s, border-color .25s, color .25s, box-shadow .25s;
}
.terra-page .stepper-tab.is-active .stepper-num,
.terra-page .stepper-tab.is-done .stepper-num {
  background: var(--teal); border-color: var(--teal); color: var(--white);
  box-shadow: 0 4px 14px rgba(20,121,108,0.25);
}
.terra-page .stepper-label { font-size: 16px; font-weight: 600; letter-spacing: -0.01em; line-height: 1.3; }
.terra-page .stepper-tab::before {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 3px;
  border-radius: 999px; background: var(--border);
}
.terra-page .stepper-tab::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 3px;
  border-radius: 999px; background: var(--orange);
  opacity: 0; transition: opacity .25s ease;
}
.terra-page .stepper-tab.is-active::after { opacity: 1; }
/* Panels */
.terra-page .stage-panels { margin-top: 44px; }
.terra-page .stage-panel { display: none; }
.terra-page .stage-panel.is-active {
  display: grid; grid-template-columns: 1fr 1fr; gap: 56px; align-items: center;
  animation: stageFade 0.5s ease;
}
@keyframes stageFade { from { opacity: 0; } to { opacity: 1; } }
.terra-page .stage-media { position: relative; border-radius: 16px; overflow: hidden; min-height: 360px; box-shadow: var(--shadow-2-lift); }
.terra-page .stage-media img { width: 100%; height: 100%; object-fit: cover; position: absolute; inset: 0; }
.terra-page .stage-body h3 { font-size: clamp(22px, 2.4vw, 30px); margin-bottom: 16px; }
.terra-page .stage-pain { font-size: 16px; line-height: 1.75; color: var(--text-body); }
.terra-page .stage-response { margin-top: 22px; }
.terra-page .stage-response p { font-size: 17px; font-weight: 700; color: var(--teal); line-height: 1.6; }

@media (max-width: 767px) {
  .terra-page .stepper { margin-bottom: 0; }
  .terra-page .stepper-tabs { grid-template-columns: repeat(3, 1fr); gap: 0; position: relative; padding: 8px 0; }
  /* đường nối giữa chạy sau 3 vòng tròn số */
  .terra-page .stepper-tabs::before {
    content: ''; position: absolute; left: 12%; right: 12%; top: 50%;
    height: 2px; background: var(--border); transform: translateY(-50%); z-index: 0;
  }
  .terra-page .stepper-tab { padding: 4px 0; justify-content: center; gap: 0; z-index: 1; }
  .terra-page .stepper-label { display: none; }
  .terra-page .stepper-num { font-size: 0; }
  .terra-page .stepper-num::after { font-size: 14px; font-weight: 700; }
  .terra-page .stepper-tab[data-stage="0"] .stepper-num::after { content: 'I'; }
  .terra-page .stepper-tab[data-stage="1"] .stepper-num::after { content: 'II'; }
  .terra-page .stepper-tab[data-stage="2"] .stepper-num::after { content: 'III'; }
  .terra-page .stepper-tab::before, .terra-page .stepper-tab::after { display: none; }
  .terra-page .stage-panel.is-active { grid-template-columns: 1fr; gap: 24px; }
}
JS
var stageShow = root.querySelector('#stageShow');
if (stageShow) {
  var stTabs = stageShow.querySelectorAll('.stepper-tab');
  var stPanels = stageShow.querySelectorAll('.stage-panel');
  var stIdx = 0, stTimer = null, ST_MS = 7000, stCount = stPanels.length;
  function stGo(n) {
    stIdx = (n + stCount) % stCount;
    stTabs.forEach(function (t, i) {
      var active = i === stIdx;
      t.classList.toggle('is-active', active);
      t.setAttribute('aria-selected', active ? 'true' : 'false');
    });
    stPanels.forEach(function (p, i) { p.classList.toggle('is-active', i === stIdx); });
  }
  function stStart() { clearInterval(stTimer); stTimer = setInterval(function () { stGo(stIdx + 1); }, ST_MS); }
  function stPause() { clearInterval(stTimer); }
  stTabs.forEach(function (t) {
    t.addEventListener('click', function () { stGo(parseInt(t.getAttribute('data-stage'), 10)); stStart(); });
  });
  stageShow.addEventListener('mouseenter', stPause);
  stageShow.addEventListener('mouseleave', stStart);
  stGo(0);
  /* auto-advance CHỈ chạy khi cuộn tới, dừng khi rời khỏi - tránh chạy ngầm từ lúc load */
  var stSeen = false;
  var stObs = new IntersectionObserver(function (entries) {
    entries.forEach(function (e) {
      if (e.isIntersecting) { stSeen = true; stStart(); }
      else if (stSeen) { stPause(); }
    });
  }, { threshold: 0.35 });
  stObs.observe(stageShow);
}
vertical-tabs validated block ▼ chi tiết

Tab dọc: danh sách mục bên trái, nội dung mục đang chọn hiện bên phải. Nén một phạm vi rộng vào một cột gọn, người đọc tự chọn xem sâu tới đâu. Dùng khi mỗi mục cần từ 2 đoạn nội dung trở lên.

Ràng buộc: JS của tab đặt cuối body (CORE_RULES § 5.3); mobile: tab dồn thành accordion xếp dọc hoặc danh sách đầy đủ.
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/interactive/vertical-tabs.md
Khung: Nền: Mở riêng ↗
HTML
<div class="scope-layout" style="display:grid;grid-template-columns:220px 1fr;gap:24px">
  <div class="scope-tabs">
    <div class="scope-tab" style="background:var(--teal);color:#fff"><span class="scope-tab-icon">●</span><span class="scope-tab-label">Payroll</span></div>
    <div class="scope-tab"><span class="scope-tab-icon">○</span><span class="scope-tab-label">Insurance</span></div>
    <div class="scope-tab"><span class="scope-tab-icon">○</span><span class="scope-tab-label">PIT</span></div>
  </div>
  <div class="scope-panel" style="display:block"><span class="scope-panel-num">01</span><h4 style="margin:6px 0">Monthly payroll run</h4><p style="font-size:14px;color:var(--text-body)">Active panel content - tabs switch via end-of-body JS on production.</p></div>
</div>
CSS
.scope-layout{display:grid;grid-template-columns:260px 1fr;gap:0;background:var(--white);border-radius:16px;overflow:hidden;box-shadow:0 4px 20px rgba(7,49,48,0.08);min-height:380px}
.scope-tabs{background:var(--teal-dark);display:flex;flex-direction:column}
.scope-tab{padding:16px 22px;cursor:pointer;border:none;border-left:3px solid transparent;background:transparent;transition:all .18s;display:flex;align-items:center;gap:12px;text-align:left;width:100%;min-height:70px;color:inherit;font-family:inherit}
.scope-tab:hover{background:rgba(255,255,255,0.05)}
.scope-tab.active{background:rgba(170,239,219,0.08);border-left-color:var(--green-light)}
.scope-tab-icon{width:34px;height:34px;border-radius:8px;background:rgba(255,255,255,0.08);display:flex;align-items:center;justify-content:center;flex-shrink:0}
.scope-tab.active .scope-tab-icon{background:rgba(170,239,219,0.18)}
.scope-tab-label{font-size:14px;font-weight:600;color:rgba(255,255,255,0.62);line-height:1.3}
.scope-tab.active .scope-tab-label{color:var(--white);font-weight:700}

.scope-panels{padding:38px 40px}
.scope-panel{display:none}
.scope-panel.active{display:grid;grid-template-columns:1fr 240px;gap:32px;align-items:start}
.scope-panel-img{border-radius:12px;overflow:hidden;height:280px}
.scope-panel-num{font-size:11px;font-weight:700;letter-spacing:0.14em;text-transform:uppercase;color:var(--green-medium);margin-bottom:8px;display:block}
.scope-panel-badge{display:inline-flex;align-items:center;gap:8px;background:var(--teal-tint);border-radius:8px;padding:8px 14px;margin-top:18px}

@media(max-width:1024px){
  .scope-layout{grid-template-columns:1fr}
  .scope-tabs{display:grid;grid-template-columns:repeat(2,1fr);padding:6px}
  .scope-tab{border-radius:10px}
}
JS
root.querySelectorAll('.scope-tab').forEach(function(tab){
  tab.addEventListener('click', function(){
    root.querySelectorAll('.scope-tab').forEach(t=>{t.classList.remove('active');t.setAttribute('aria-selected','false');});
    root.querySelectorAll('.scope-panel').forEach(p=>p.classList.remove('active'));
    tab.classList.add('active');
    tab.setAttribute('aria-selected','true');
    root.querySelector('#'+tab.dataset.tab).classList.add('active');
  });
});

media/

Media (khung ảnh, logo bar)

logo-bar validated block ▼ chi tiết

Dải logo khách hàng để chứng minh độ tin cậy trong một liếc mắt; khung nét đứt xen kẽ giúp hàng logo không trông như một dãy ô xám trống.

Ràng buộc: Chỉ dùng logo khách hàng thật; xử lý xám theo luật ảnh trong guideline.
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/media/logo-bar.md
Khung: Nền: Mở riêng ↗
HTML
<div class="logo-bar">
  <p class="logo-bar-label">Một số doanh nghiệp FDI đang sử dụng terra</p>
  <div class="logo-grid">
    <div class="logo-cell">Logo · 01</div>
    <div class="logo-cell">Logo · 02</div>  <!-- orange dashed -->
    <div class="logo-cell">Logo · 03</div>
    <div class="logo-cell">Logo · 04</div>
    <div class="logo-cell">Logo · 05</div>  <!-- orange dashed -->
    <div class="logo-cell">Logo · 06</div>
  </div>
</div>
CSS
.logo-bar { padding: 56px 0; text-align: center; }
.logo-bar-label {
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 32px;
}

.logo-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 18px;
  max-width: 1080px;
  margin: 0 auto;
}

.logo-cell {
  aspect-ratio: 5 / 2;
  border: 1.5px dashed var(--border);
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 10.5px;
  color: var(--text-muted);
  font-weight: 600;
  background: var(--white);
  transition: border-color 0.2s, background 0.2s;
}
.logo-cell:hover {
  border-style: solid;
  border-color: var(--teal);
  background: var(--bg-light);
}

/* Every 3rd cell = orange dashed (alternating rhythm) */
.logo-cell:nth-child(3n+2) { border-color: var(--orange); }
.logo-cell:nth-child(3n+2):hover {
  border-color: var(--orange);
  border-style: solid;
}

/* When real logos arrive */
.logo-cell.has-logo {
  border-style: solid;
  border-color: var(--border);
}
.logo-cell.has-logo img {
  max-width: 80%; max-height: 70%;
  filter: grayscale(1);
  opacity: 0.6;
  transition: filter 0.3s, opacity 0.3s;
}
.logo-cell.has-logo:hover img {
  filter: grayscale(0);
  opacity: 1;
}

@media (max-width: 1024px) { .logo-grid { grid-template-columns: repeat(4, 1fr); } }
@media (max-width: 600px)  { .logo-grid { grid-template-columns: repeat(3, 1fr); } }
mockup-chrome validated block ▼ chi tiết

Khung cửa sổ kiểu macOS (3 chấm đèn giao thông + thanh địa chỉ) bọc quanh ảnh chụp màn hình, làm nó trông như "phần mềm thật, màn hình thật". Dùng khi hình ở hero là giao diện sản phẩm.

Ràng buộc: Chỉ đi cặp với T-C trên trang sản phẩm (CORE_RULES § 4 matrix).
Hợp cặp: motion/tier-c-showcase, decor/orbital-ring, decor/floating-chip
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/media/mockup-chrome.md
Khung: Nền: Mở riêng ↗
HTML
<div class="hero-mockup">
  <div class="hero-mockup-chrome">
    <div class="hero-mockup-dots">
      <span></span><span></span><span></span>
    </div>
    <div class="hero-mockup-url">app.terra-plat.vn / payroll</div>
  </div>
  <div class="hero-mockup-body">
    <!-- Dashboard content: stats, chart, table -->
  </div>
</div>
CSS
.hero-mockup {
  position: relative;
  background: var(--white);
  border-radius: 24px;
  padding: 22px;
  box-shadow: var(--shadow-4-premium);
  border: 1px solid rgba(20,121,108,0.10);
}

.hero-mockup-chrome {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 4px 14px;
  border-bottom: 1px solid var(--border-soft);
  margin-bottom: 14px;
}

.hero-mockup-dots { display: flex; gap: 6px; }
.hero-mockup-dots span {
  width: 9px; height: 9px;
  border-radius: 50%;
}
.hero-mockup-dots span:nth-child(1) { background: #F0827E; }   /* red */
.hero-mockup-dots span:nth-child(2) { background: #F0C24E; }   /* yellow */
.hero-mockup-dots span:nth-child(3) { background: #A0D88B; }   /* green */

.hero-mockup-url {
  flex: 1;
  font-family: 'SF Mono', Consolas, monospace;
  font-size: 11px;
  background: var(--bg-light);
  padding: 5px 12px;
  border-radius: 6px;
  color: var(--text-muted);
  text-align: center;
}
photo-wash validated atom ▼ chi tiết

Ảnh phủ kín section nhưng mờ gần như tan hết vào nền sáng, chỉ còn lại bóng dáng. Người xem không đọc được ảnh là ảnh gì, chỉ cảm thấy vùng đó có hơi người. Đổi được ảnh theo từng slide, mỗi slide một không khí, mà nhịp trang không đổi.

Ràng buộc: Chỉ trên nền sáng. Độ mờ 0.10 và blend multiply, đẩy cao hơn là chữ bắt đầu khó đọc. Ảnh Pexels, alt rỗng vì đây là ảnh trang trí. Chuyển slide đổi ảnh bằng class is-visible, không đổi src. Motion: T-B.
Hợp cặp: backgrounds/light-gradient-mesh, structures/hero-split-5050, motion/tier-b-component
Nguồn gốc: tách ra từ compositions/hero-homepage-light khi cải tổ store 08/2026
File: brands/terra/patterns/media/photo-wash.md
Khung: Nền: Mở riêng ↗
HTML
<img src="[Pexels URL w=1600]" alt="" class="photo-wash is-visible" data-slide="0" data-no-lazy="1">
<img src="[Pexels URL w=1600]" alt="" class="photo-wash" data-slide="1" data-no-lazy="1">
CSS
.terra-page .photo-wash {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; z-index: 0; pointer-events: none;
  opacity: 0; mix-blend-mode: multiply; transition: opacity .6s ease;
}
.terra-page .photo-wash.is-visible { opacity: .10; }
JS
// Đổi ảnh theo slide: bật is-visible cho đúng ảnh, không đụng vào src.
function setWashSlide(root, idx) {
  root.querySelectorAll('.photo-wash').forEach(function (img, i) {
    img.classList.toggle('is-visible', i === idx);
  });
}

motion/

Chuyển động

tier-a-foundational locked atom ▼ chi tiết

Chuyển động nền cơ bản mọi trang đều có: section hiện dần khi cuộn tới, thẻ nhấc nhẹ khi rê chuột, số tự đếm tăng. Cảm nhận như "chất lượng", không bị để ý là animation.

Ràng buộc: Được phép trên mọi loại trang (CORE_RULES § 4); bắt buộc có khối prefers-reduced-motion (CHECKLIST § D).
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/motion/tier-a-foundational.md
Khung: Nền: Mở riêng ↗
HTML
<div style="display:flex;gap:28px;align-items:center;flex-wrap:wrap">
  <div style="display:flex;gap:8px;align-items:center"><span class="pulse-dot"></span><span style="font-size:13px;color:var(--text-body)">pulse dot - "live system"</span></div>
  <div class="card-feature" style="background:#fff;border:1px solid var(--border);border-radius:10px;padding:18px 22px;transition:transform .2s,box-shadow .2s;cursor:default" onmouseover="this.style.transform='translateY(-4px)';this.style.boxShadow='0 12px 28px rgba(7,49,48,.10)'" onmouseout="this.style.transform='';this.style.boxShadow=''">hover-lift card</div>
  <div><span style="font-size:28px;font-weight:700;color:var(--teal)">5,000+</span><span style="font-size:12px;color:var(--text-muted);display:block">count-up number (animates on scroll)</span></div>
</div>
CSS
.reveal {
  opacity: 0;
  transform: translateY(24px);
  transition:
    opacity 0.7s cubic-bezier(0.16, 1, 0.3, 1),
    transform 0.7s cubic-bezier(0.16, 1, 0.3, 1);
}
.reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}
.reveal-d1 { transition-delay: 0.05s; }
.reveal-d2 { transition-delay: 0.12s; }
.reveal-d3 { transition-delay: 0.19s; }
.reveal-d4 { transition-delay: 0.26s; }
.reveal-d5 { transition-delay: 0.33s; }

.pulse-dot {
  display: inline-block;
  width: 8px; height: 8px;
  background: currentColor;  /* inherit color from parent context */
  border-radius: 50%;
  position: relative;
  flex-shrink: 0;
}
.pulse-dot::after {
  content: '';
  position: absolute;
  inset: -3px;
  border-radius: 50%;
  border: 1px solid currentColor;
  opacity: 0;
  animation: pulseRing 2.2s ease-out infinite;
}
@keyframes pulseRing {
  0% { opacity: 0.8; transform: scale(0.6); }
  100% { opacity: 0; transform: scale(2.4); }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
  .reveal { opacity: 1; transform: none; }
}
JS
const obs = new IntersectionObserver((entries) => {
  entries.forEach(e => {
    if (e.isIntersecting) {
      e.target.classList.add('is-visible');
      obs.unobserve(e.target);
    }
  });
}, { threshold: 0.15, rootMargin: '0px 0px -80px 0px' });
document.querySelectorAll('.reveal').forEach(el => obs.observe(el));
tier-b-component validated atom ▼ chi tiết

Chuyển động gắn với một component cụ thể: slider tự chạy, băng chuyền logo, tab chuyển cảnh. Báo hiệu sản phẩm "đang sống"; chuyển động gói gọn trong component nên trang vẫn yên tĩnh.

Ràng buộc: Chỉ dùng khi trang thật sự có component đó (CORE_RULES § 4); băng chuyền phải dừng khi rê chuột.
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/motion/tier-b-component.md
Khung: Nền: Mở riêng ↗
HTML
<div style="display:flex;gap:14px;flex-wrap:wrap">
  <div class="hero-tab" style="background:var(--teal);color:#fff;border-radius:999px;padding:8px 18px;font-size:13px">Active tab</div>
  <div class="hero-tab" style="background:var(--bg-light);border-radius:999px;padding:8px 18px;font-size:13px;color:var(--text-body)">Slide 2</div>
  <div class="hero-tab" style="background:var(--bg-light);border-radius:999px;padding:8px 18px;font-size:13px;color:var(--text-body)">Slide 3</div>
  <p style="flex-basis:100%;font-size:12px;color:var(--text-muted);margin:6px 0 0">Slider tab pills - auto-advance + click switching run via end-of-body JS; logo carousel and accordion transitions belong to this tier too.</p>
</div>
CSS
.hero-tab.is-active::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 2px;
  background: var(--orange);
  transform-origin: left;
  animation: tabBar 8s linear forwards;
}
@keyframes tabBar {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

.card-hover-lift {
  transition: transform .3s ease, box-shadow .3s ease, border-color .3s ease;
  border: 1px solid var(--border-soft);
  box-shadow: var(--shadow-1-base);
}
.card-hover-lift:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-2-lift);
  border-color: var(--teal);
}
tier-c-showcase validated atom ▼ chi tiết

Chuyển động nặng nhất: mockup phần mềm tự diễn - con trỏ giả bấm, màn hình chuyển bước, dashboard tự cập nhật. Bằng chứng "phần mềm có thật" mạnh nhất mà không cần người bán.

Ràng buộc: CHỈ khi mockup là bằng chứng bán hàng chính của trang; lặp >=8s; TẮT trên mobile (CORE_RULES § 4, CHECKLIST § D).
Hợp cặp: media/mockup-chrome
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/motion/tier-c-showcase.md
Khung: Nền: Mở riêng ↗
HTML
<div style="display:flex;gap:32px;align-items:center;flex-wrap:wrap">
  <div class="hrmphone" style="width:170px;height:330px;background:var(--bg-dark);border-radius:26px;padding:10px;box-shadow:0 18px 40px rgba(7,49,48,.25)">
    <div class="hrmphone-screen" style="background:#fff;height:100%;border-radius:18px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px">
      <div style="width:46px;height:46px;border-radius:12px;background:var(--teal);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700">t</div>
      <div style="font-size:12px;color:var(--text-muted)">payslip screen</div>
      <div style="width:70%;height:8px;background:var(--bg-light);border-radius:4px"></div>
      <div style="width:55%;height:8px;background:var(--bg-light);border-radius:4px"></div>
    </div>
  </div>
  <p style="font-size:13px;color:var(--text-body);max-width:280px">Static frame of the self-demo. On a product page the full T-C chain runs: fake cursor clicks, screens advance, dashboard numbers update, loop ≥8s, OFF on mobile.</p>
</div>
CSS
.hrmphone {
  position: relative;
  width: 280px;
  aspect-ratio: 9/19;
  background: #000;
  border-radius: 36px;
  padding: 8px;
  overflow: hidden;
}
.hrmphone-screen {
  border-radius: 28px;
  width: 100%; height: 100%;
  position: relative;
  overflow: hidden;
}
.hrmscreen {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  opacity: 0;
  animation: hrmphone-cycle 9s infinite;
}
.hrmscreen--splash    { animation-delay: 0s; }
.hrmscreen--dashboard { animation-delay: 3s; }
.hrmscreen--payslip   { animation-delay: 6s; }

@keyframes hrmphone-cycle {
  0%   { opacity: 0; }
  3%   { opacity: 1; }
  33%  { opacity: 1; }
  36%  { opacity: 0; }
  100% { opacity: 0; }
}

/* Splash logo pulse */
.hrmsplash-logo {
  animation: hrmsplash-pulse 1.8s ease-in-out infinite;
}
@keyframes hrmsplash-pulse {
  0%, 100% { transform: scale(1); opacity: 1; }
  50%      { transform: scale(1.06); opacity: 0.85; }
}

/* Focal tile highlight (e.g. "View payslip" button) */
.hrmapp-tile--highlight {
  border: 2px solid #4ddb91;
  background: #f0fdf4;
  animation: hrmtile-pulse 1.4s ease-in-out infinite;
}
@keyframes hrmtile-pulse {
  0%, 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(77,219,145,0.5); }
  50%      { transform: scale(1.03); box-shadow: 0 0 0 8px rgba(77,219,145,0); }
}

@media (max-width: 768px) {
  .hrmphone-screen .hrmscreen { animation: none; opacity: 1; }
  .hrmphone-screen .hrmscreen:not(.hrmscreen--dashboard) { display: none; }
}

.hrm-cursor {
  position: absolute;
  width: 20px; height: 24px;
  z-index: 10;
  pointer-events: none;
  top: 240px; left: 480px;
  animation: cursor-loop 8s ease-in-out infinite;
}
.hrm-cursor-ring {
  position: absolute;
  top: 50%; left: 30%;
  width: 28px; height: 28px;
  border-radius: 50%;
  border: 2px solid var(--green-medium);
  transform: translate(-50%, -50%) scale(0);
  opacity: 0;
  animation: cursor-click-ring 8s ease-out infinite;
}

@keyframes cursor-loop {
  0%, 12%  { top: 240px; left: 480px; }
  38%      { top: 248px; left: 130px; }
  42%      { top: 248px; left: 130px; transform: scale(0.9); }
  46%      { top: 248px; left: 130px; transform: scale(1); }
  88%      { top: 260px; left: 145px; transform: scale(1); }
  100%     { top: 240px; left: 480px; transform: scale(1); }
}
@keyframes cursor-click-ring {
  0%, 36% { transform: translate(-50%, -50%) scale(0); opacity: 0; }
  40%     { transform: translate(-50%, -50%) scale(0.4); opacity: 0.85; }
  48%     { transform: translate(-50%, -50%) scale(1.8); opacity: 0; }
  100%    { transform: translate(-50%, -50%) scale(1.8); opacity: 0; }
}

/* Content state swap synced with cursor */
.hrm-content--dashboard {
  animation: content-dashboard 8s ease-in-out infinite;
}
.hrm-content--salary {
  animation: content-salary 8s ease-in-out infinite;
}
@keyframes content-dashboard {
  0%, 38%   { opacity: 1; }
  48%, 95%  { opacity: 0; }
  100%      { opacity: 1; }
}
@keyframes content-salary {
  0%, 38%   { opacity: 0; }
  48%, 95%  { opacity: 1; }
  100%      { opacity: 0; }
}

/* Mandatory mobile disable */
@media (max-width: 768px) {
  .hrm-cursor, .hrm-cursor-ring { display: none; }
  .hrm-content--salary { display: none; }
  .hrm-content--dashboard { animation: none; opacity: 1; }
}

.process-grid {
  position: relative;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
}
.process-grid::before {
  content: '';
  position: absolute;
  top: 0;
  left: calc(10% - 38px);
  width: calc(80% + 76px);
  height: 76px;
  border-radius: 38px;
  background: linear-gradient(90deg,
    rgba(20,121,108,0.15) 0%,
    rgba(20,121,108,0.40) 50%,
    rgba(20,121,108,0.15) 100%);
  transform: scaleX(0);
  transform-origin: left center;
  animation: line-grow 5s ease-in-out infinite;
  z-index: 0;
  pointer-events: none;
}
@keyframes line-grow {
  0%   { transform: scaleX(0); opacity: 0; }
  5%   { transform: scaleX(0); opacity: 1; }
  50%  { transform: scaleX(1); opacity: 1; }
  75%  { transform: scaleX(1); opacity: 1; }
  95%  { transform: scaleX(1); opacity: 0; }
  100% { transform: scaleX(0); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .process-grid::before {
    animation: none;
    transform: scaleX(1);
    opacity: 1;
    background: rgba(20,121,108,0.20);
  }
}

.overrun-bar-seg--planned {
  transform: scaleX(0);
  transform-origin: left;
}
.pain-layout.is-visible .overrun-bar-seg--planned {
  animation: barReveal 0.9s cubic-bezier(0.16, 1, 0.3, 1) 0.2s both;
}
.pain-layout.is-visible .overrun-bar-seg--gap {
  animation:
    barReveal 0.9s cubic-bezier(0.16, 1, 0.3, 1) 0.8s both,
    overrun-shimmer 2.4s linear 1.7s infinite;
}
@keyframes barReveal {
  from { transform: scaleX(0); transform-origin: left; opacity: 0; }
  to   { transform: scaleX(1); opacity: 1; }
}
@keyframes overrun-shimmer {
  0%   { background-position: -60% 0, 0 0; }
  100% { background-position: 160% 0, 0 0; }
}

.pain-layout.is-visible .overrun-caption {
  animation: captionFade 0.6s ease-out 1.6s both;
}
@keyframes captionFade {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}

openers/

Mở section

openers-set validated atom ▼ chi tiết

5 cách mở đầu một section, cùng một trục để tạo sự đa dạng: eyebrow (dòng nhãn 2 gạch, trang trọng) / nhãn mờ (khẽ khàng) / số to (thứ tự, tiến trình) / vào thẳng H2 (tự tin, không rào đón) / pill (kiểu thông báo).

Ràng buộc: Cấu trúc eyebrow theo iron rule 4 (2 gạch khi dùng); tối đa 2-3 eyebrow/trang, >=1/3 số section mở đầu không cần opener (GUIDELINE § 5 Axis 1).
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/openers/openers-set.md
Khung: Nền: Mở riêng ↗
HTML
<div class="eyebrow" style="color: var(--green-medium)">Mô hình BPaaS</div>
<div class="section-label">Câu hỏi thường gặp</div>
<div class="sec-num">02</div>
CSS
/* A. Eyebrow — terra signature (double-bar REQUIRED when used) */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin-bottom: 12px;
  /* color: proposed per section at Step 2 — not fixed */
}
.eyebrow::before,
.eyebrow::after {
  content: '';
  width: 24px;
  height: 2px;
  background: currentColor;
  flex-shrink: 0;
}

/* B. Section label — quiet alternative */
.section-label {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 16px;
}

/* C. Numbered marker — sequential content only */
.sec-num {
  font-size: 44px;
  font-weight: 700;
  line-height: 1;
  color: var(--green-medium);
  opacity: 0.4;
  margin-bottom: 8px;
}

/* D. No opener — H2 straight in (no CSS needed) */

/* E. Inline tag/pill — see § 19 .cmp-tag / § 15 eyebrow pill */
pill validated atom ▼ chi tiết

Một pill nhỏ ở ngay trên đầu hero, đọc như "mới / nổi bật", dọn đường cho slider hoặc tiêu đề phía sau.

Ràng buộc: Vùng hero; mỗi hero một cái; IN HOA (iron rule 2).
Hợp cặp: compositions/hero-homepage-light
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/openers/pill.md
Khung: Nền: Mở riêng ↗
HTML
<span class="hero-eyebrow-pill">
  <span class="pulse-dot"></span>
  TÀI LIỆU MỚI · WHITEPAPER
</span>
CSS
.hero-eyebrow-pill {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 8px 16px;
  background: var(--white);
  border: 1px solid var(--orange);
  border-radius: 999px;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--teal-dark);
  width: fit-content;
  box-shadow: 0 4px 14px -6px rgba(228,144,37,0.30);
}

.hero-eyebrow-pill .pulse-dot {
  position: relative;
  display: inline-block;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--orange);
  flex-shrink: 0;
}
.hero-eyebrow-pill .pulse-dot::after {
  content: '';
  position: absolute; inset: 0;
  border-radius: 50%;
  background: var(--orange);
  animation: pulseRing 2.2s ease-out infinite;
}

@keyframes pulseRing {
  0%   { transform: scale(1); opacity: 0.6; }
  100% { transform: scale(2.4); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .hero-eyebrow-pill .pulse-dot::after { animation: none; }
}

structures/

Bố cục (grid, timeline)

compare-split validated block ▼ chi tiết

Hai cột cùng khuôn đặt cạnh nhau, một bên rời rạc nhiều mảnh, một bên gom về một mối. Không câu nào khẳng định bên nào hơn, chính khoảng cách giữa hai cột làm việc đó. Mắt so sánh trước khi kịp đọc, nên kết luận có cảm giác là của mình chứ không phải bị nhét vào.

Ràng buộc: Hai cột phải CÙNG chiều cao và CÙNG khuôn, lệch khuôn là thành thiên vị lộ liễu. Không dùng từ phóng đại ở cả hai cột (iron rule 9). Cột nhấn dùng nền gradient teal, không dùng cam. Xuống 900px thì xếp dọc, cột nhấn xuống dưới. Motion: T-A.
Hợp cặp: cards/ribbon-pills-kit, cards/card-treatments
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/structures/compare-split.md
Khung: Nền: Mở riêng ↗
HTML
<div class="cmp-grid">

  <!-- Cột A: cách rời rạc -->
  <div class="cmp-card">
    <span class="cmp-tag">[nhãn cột A]</span>
    <h3>[tiêu đề cột A]</h3>
    <div class="cmp-list">
      <div class="cmp-row"><span class="cmp-row-k">[bên 1]</span><span class="cmp-row-v">[bên 1 lo gì]</span></div>
      <div class="cmp-row"><span class="cmp-row-k">[bên 2]</span><span class="cmp-row-v">[bên 2 lo gì]</span></div>
      <div class="cmp-row"><span class="cmp-row-k">[bên 3]</span><span class="cmp-row-v">[bên 3 lo gì]</span></div>
    </div>
    <div class="cmp-alert">
      <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 9v4M12 17h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/></svg>
      <p>[một câu nêu cái giá của cách rời rạc]</p>
    </div>
  </div>

  <!-- Cột B: gom về một mối -->
  <div class="cmp-card cmp-accent">
    <span class="cmp-tag">[nhãn cột B]</span>
    <h3>[tiêu đề cột B]</h3>
    <div class="cmp-flow">
      <div class="cmp-stage"><span class="cmp-stage-n">1</span><span>[đầu vào]</span></div>
      <div class="cmp-arrow" aria-hidden="true">
        <svg viewBox="0 0 20 34"><path d="M10 2 L10 26 M3 20 L10 28 L17 20"/></svg>
      </div>
      <div class="cmp-hub">
        <div class="cmp-hub-core">[logo hoặc tên trung tâm]</div>
        <div class="cmp-hub-sides">
          <span class="cmp-pill">[vai trò 1]</span>
          <span class="cmp-pill">[vai trò 2]</span>
        </div>
      </div>
      <div class="cmp-arrow" aria-hidden="true">
        <svg viewBox="0 0 20 34"><path d="M10 2 L10 26 M3 20 L10 28 L17 20"/></svg>
      </div>
      <div class="cmp-stage cmp-stage-out"><span>[kết quả]</span></div>
    </div>
  </div>

</div>
CSS
.terra-page .cmp-grid { display: grid; grid-template-columns: 1fr 1fr; grid-auto-rows: 1fr; gap: 32px; align-items: stretch; }

.terra-page .cmp-card {
  position: relative; overflow: hidden; display: flex; flex-direction: column;
  background: var(--white); border: 1px solid var(--border);
  border-radius: 16px; padding: 38px;
}
.terra-page .cmp-card::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px; background: var(--border); }
.terra-page .cmp-card.cmp-accent {
  background: linear-gradient(135deg, #14796C 0%, #0E655A 55%, #0A564C 100%);
  border-color: var(--teal-dark); color: var(--white);
}
.terra-page .cmp-card.cmp-accent::before { background: linear-gradient(90deg, var(--green-light), var(--green-medium)); }
.terra-page .cmp-card.cmp-accent h3 { color: var(--white); }

.terra-page .cmp-tag {
  display: inline-flex; align-items: center; gap: 8px; margin-bottom: 14px;
  padding: 5px 12px; border-radius: 20px;
  font-size: 12px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase;
  background: rgba(228,144,37,.10); color: var(--orange);
}
.terra-page .cmp-card.cmp-accent .cmp-tag { background: rgba(170,239,219,.14); color: var(--green-light); }

/* Cột A */
.terra-page .cmp-list { display: flex; flex-direction: column; gap: 10px; flex: 1; }
.terra-page .cmp-row {
  display: grid; grid-template-columns: 140px 1fr; gap: 16px; align-items: center;
  padding: 14px 18px; border-radius: 10px;
  background: var(--bg-light); border: 1px solid var(--border);
}
.terra-page .cmp-row-k { font-size: 16px; font-weight: 800; color: var(--text-primary); }
.terra-page .cmp-row-v { font-size: 16px; font-weight: 600; color: var(--text-body); }
.terra-page .cmp-alert {
  display: grid; grid-template-columns: auto 1fr; gap: 13px; align-items: start;
  margin-top: 14px; padding: 13px 16px; border-radius: 8px;
  background: rgba(228,144,37,.05); border: 1px solid rgba(228,144,37,.18);
  border-left: 3px solid var(--orange); color: var(--orange);
}
.terra-page .cmp-alert p { margin: 0; font-size: 15px; color: var(--text-body); }

/* Cột B */
.terra-page .cmp-flow { display: flex; flex-direction: column; flex: 1; gap: 10px; }
.terra-page .cmp-stage {
  display: grid; grid-template-columns: auto 1fr; gap: 14px; align-items: center;
  padding: 14px 18px; border-radius: 12px;
  background: rgba(170,239,219,.08); border: 1px solid rgba(170,239,219,.25);
}
.terra-page .cmp-stage-n {
  width: 26px; height: 26px; border-radius: 50%; display: inline-flex;
  align-items: center; justify-content: center;
  background: rgba(170,239,219,.22); font-size: 13px; font-weight: 800;
}
.terra-page .cmp-stage-out { background: var(--orange); border-color: var(--orange); grid-template-columns: 1fr; text-align: center; font-weight: 700; }
.terra-page .cmp-arrow { display: flex; justify-content: center; }
.terra-page .cmp-arrow svg { width: 20px; height: 34px; stroke: var(--orange); stroke-width: 4; fill: none; }

.terra-page .cmp-hub { display: flex; flex-direction: row; align-items: center; justify-content: center; gap: 14px; padding: 16px 0; }
.terra-page .cmp-hub-core {
  width: 108px; height: 108px; flex: none; padding: 18px; border-radius: 22px;
  background: var(--white); color: var(--teal-dark);
  display: flex; align-items: center; justify-content: center; text-align: center;
  font-size: 15px; font-weight: 800; line-height: 1.25;
  box-shadow: 0 10px 28px rgba(0,0,0,.25);
}
.terra-page .cmp-hub-sides { display: flex; flex-direction: column; gap: 26px; flex: 1; min-width: 0; }
.terra-page .cmp-pill {
  display: inline-flex; align-items: center; justify-content: center;
  width: 180px; max-width: 100%; padding: 11px 16px; border-radius: 24px;
  background: rgba(170,239,219,.30); border: 1px solid #65CCBF;
  color: var(--white); font-size: 16px; font-weight: 700;
}

@media (max-width: 900px) {
  .terra-page .cmp-grid { grid-template-columns: 1fr; gap: 20px; }
  .terra-page .cmp-card { padding: 26px 20px; }
  .terra-page .cmp-row { grid-template-columns: 1fr; gap: 4px; }
  .terra-page .cmp-hub { flex-direction: column; }
  .terra-page .cmp-hub-sides { width: 100%; gap: 12px; }
  .terra-page .cmp-pill { width: 100%; }
}
cost-overrun-gauge validated block ▼ chi tiết

Một thanh ngang chia hai phần màu khác nhau, phần chênh ra nhấp nháy nhẹ. Cái chênh lệch tự nói thay cho câu chữ, và con số to ngay dưới chốt lại. Mạnh hơn viết ra thành câu, vì mắt đo được độ dài trước khi não đọc được số.

Ràng buộc: Con số vượt mức phải lấy từ brief, không được tự bịa (Content Strictness Rule). Ghi nguồn dưới biểu đồ. Animation chạy khi cuộn tới (.is-visible, motion/tier-a); giảm chuyển động thì hiện thẳng trạng thái cuối.
Nguồn gốc: service-page-terra-payroll-vi.html (production, harvested 2026-06-11)
File: brands/terra/patterns/structures/cost-overrun-gauge.md
Khung: Nền: Mở riêng ↗
HTML
<div class="cost-gauge-card">
        <span class="cost-gauge-eyebrow">Khoảng cách thực tế</span>
        <h3>Chi phí ẩn của tự làm payroll nội bộ</h3>
        <p>Khi tính đầy đủ lương nhân sự, phần mềm, đào tạo, kiểm toán và xử lý sai sót - tổng chi phí thường vượt xa con số ban đầu.</p>

        <div class="overrun-chart" role="img" aria-label="Biểu đồ so sánh ngân sách payroll dự kiến và thực tế: 100% ngân sách dự kiến cộng thêm 40 đến 60 phần trăm phát sinh, tổng chi phí thực tế bằng 140 đến 160 phần trăm">
          <!-- v24: Bar moved UP - animates in from left -->
          <div class="overrun-bar">
            <div class="overrun-bar-seg overrun-bar-seg--planned" style="flex: 100;">
              <span class="seg-label">Ngân sách dự kiến</span>
              <span class="seg-value">100%</span>
            </div>
            <div class="overrun-bar-seg overrun-bar-seg--gap" style="flex: 60;">
              <span class="seg-label">Phát sinh</span>
              <span class="seg-value">+40-60%</span>
            </div>
          </div>

          <!-- v24: Combined inline caption note BELOW chart (replacing big number block) -->
          <p class="overrun-caption">
            <span class="overrun-caption-label">Chi phí thực tế khi tự làm:</span>
            <span class="overrun-caption-num">140-160%</span>
            <span class="overrun-caption-trailing">so với ngân sách dự kiến ban đầu</span>
          </p>
          <p class="overrun-source" style="margin-top:10px;font-size:11px;color:var(--text-muted);font-style:italic;line-height:1.4;">Nguồn: Whitepaper Cẩm nang Tuân thủ Bảng lương 2026, Chương 5 - terra-plat.vn</p>
        </div>
      </div>
CSS
/* ==========================================================================
   V5 ADDITION - Pain Points cost gauge (anchor right of 4 pain cards)
   ========================================================================== */
.pain-layout {
  display: grid;
  grid-template-columns: 1fr 1fr;   /* v9: gauge LEFT 50% + rows RIGHT 50% */
  gap: 32px;
  align-items: stretch;
}
.cost-gauge-card {
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 24px 22px;
  display: flex; flex-direction: column;
  position: relative;
  overflow: hidden;
  box-shadow: 0 8px 28px rgba(7,49,48,0.06);
}
.cost-gauge-card::before {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 3px;
  background: linear-gradient(90deg, var(--orange), #D67E0F);
}
.cost-gauge-eyebrow {
  display: inline-block;
  font-size: 11px; font-weight: 700;
  letter-spacing: 0.10em; text-transform: uppercase;
  color: var(--orange);
  background: rgba(228,144,37,0.10);
  padding: 4px 10px; border-radius: 20px;
  margin-bottom: 14px;
  align-self: flex-start;
}
.cost-gauge-card h3 {
  font-size: 18px;
  color: var(--text-primary);
  margin-bottom: 8px;
  letter-spacing: -0.01em;
  line-height: 1.3;
}
.cost-gauge-card > p {
  font-size: 13.5px;
  color: var(--text-body);
  line-height: 1.6;
  margin-bottom: 22px;
}
/* v15 - Combined cost overrun chart (single bar + BIG 140-160% emphasis) */
.overrun-chart {
  margin: 24px 0 0;
}
/* v24 - caption note below chart (replaces overrun-headline big number block) */
.overrun-caption {
  margin: 16px 0 0;
  text-align: center;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--text-body);
}
.overrun-caption-label {
  font-weight: 600;
  color: var(--text-primary);
}
.overrun-caption-num {
  font-weight: 800;
  color: var(--orange);
  font-size: 18px;
  letter-spacing: -0.01em;
  margin: 0 4px;
  font-feature-settings: "tnum" 1;
}
.overrun-caption-trailing {
  display: inline;
}
.overrun-bar {
  display: flex;
  height: 44px;
  border-radius: 10px;
  overflow: hidden;
  box-shadow: 0 4px 12px rgba(7,49,48,0.08);
}
.overrun-bar-seg {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  padding: 4px 10px;
  position: relative;
  color: var(--white);
  text-align: center;
}
.overrun-bar-seg--planned {
  background: linear-gradient(90deg, var(--green-medium), var(--teal));
}
/* v24 - Continuous shimmer gradient on orange "Phát sinh" segment.
   Inspired by process timeline animation. Highlight band travels left-to-right
   continuously, creating sense of "ongoing overrun pressure". */
.overrun-bar-seg--gap {
  background:
    linear-gradient(90deg,
      rgba(255,255,255,0) 0%,
      rgba(255,255,255,0.28) 50%,
      rgba(255,255,255,0) 100%
    ),
    linear-gradient(90deg, var(--orange) 0%, #C67315 100%);
  background-size: 60% 100%, 100% 100%;
  background-repeat: no-repeat;
  background-position: -60% 0, 0 0;
  border-left: 2px dashed rgba(255,255,255,0.55);
  animation: overrun-shimmer 2.4s linear infinite;
}
@keyframes overrun-shimmer {
  0%   { background-position: -60% 0, 0 0; }
  100% { background-position: 160% 0, 0 0; }
}
.overrun-bar-seg .seg-label {
  font-size: 10px;
  font-weight: 500;
  opacity: 0.92;
  line-height: 1.2;
  letter-spacing: 0.01em;
  position: relative; z-index: 1;
}
.overrun-bar-seg .seg-value {
  font-size: 14px;
  font-weight: 800;
  line-height: 1.2;
  letter-spacing: -0.01em;
  margin-top: 2px;
  position: relative; z-index: 1;
}
/* Bar fill animation triggered on scroll reveal - both segments grow from left */
.pain-layout.is-visible .overrun-bar-seg--planned {
  animation: barReveal 0.9s cubic-bezier(0.16, 1, 0.3, 1) 0.2s both;
}
.pain-layout.is-visible .overrun-bar-seg--gap {
  animation:
    barReveal 0.9s cubic-bezier(0.16, 1, 0.3, 1) 0.8s both,
    overrun-shimmer 2.4s linear 1.7s infinite;   /* shimmer kicks in after reveal */
}
@keyframes barReveal {
  from { transform: scaleX(0); transform-origin: left; opacity: 0; }
  to   { transform: scaleX(1); opacity: 1; }
}
/* v24 - caption fades in after bars settle */
.pain-layout.is-visible .overrun-caption {
  animation: captionFade 0.6s ease-out 1.6s both;
}
@keyframes captionFade {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}
.gauge-overflow {
  display: flex; align-items: center; gap: 10px;
  background: rgba(228,144,37,0.08);
  border-left: 3px solid var(--orange);
  border-radius: 0 8px 8px 0;
  padding: 12px 14px;
  margin-top: 8px;
}
.gauge-overflow-icon {
  flex-shrink: 0;
  width: 30px; height: 30px;
  background: var(--orange); color: var(--white);
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 14px; font-weight: 800;
}
.gauge-overflow-text {
  font-size: 13px; font-weight: 600;
  color: var(--text-primary);
  line-height: 1.4;
}
.gauge-overflow-text strong {
  color: var(--orange); font-weight: 800;
}
.gauge-overflow-icon { animation: pulse-soft 2.2s ease-in-out infinite; }
flow-3col-arrows validated block ▼ chi tiết

Ba thẻ ngang nối bằng mũi tên. Mũi tên biến ba thứ rời rạc thành một chuỗi có chiều, nên đọc ra quan hệ chứ không ra danh sách. Mắt đi trái sang phải một mạch, không dừng lại cân nhắc từng thẻ.

Ràng buộc: Mũi tên xoay dọc trên mobile; tối đa 3 bên.
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/structures/flow-3col-arrows.md
Khung: Nền: Mở riêng ↗
HTML
<div class="service-flow" style="display:grid;grid-template-columns:1fr auto 1fr auto 1fr;gap:14px;align-items:center">
  <div class="flow-card"><div class="flow-icon">1</div><h5 style="margin:8px 0 6px">Client HR</h5><p style="font-size:13px;color:var(--text-body);margin:0">Sends monthly changes</p></div>
  <div class="flow-arrow">→</div>
  <div class="flow-card"><div class="flow-icon">2</div><h5 style="margin:8px 0 6px">terra</h5><p style="font-size:13px;color:var(--text-body);margin:0">Calculates, checks, prepares filings</p></div>
  <div class="flow-arrow">→</div>
  <div class="flow-card"><div class="flow-icon">3</div><h5 style="margin:8px 0 6px">Authorities</h5><p style="font-size:13px;color:var(--text-body);margin:0">VSS, GDT, DOLISA submissions</p></div>
</div>
CSS
.service-flow{display:grid;grid-template-columns:1fr 1fr 1fr;gap:24px;margin-top:40px}
.flow-card{background:var(--white);border:1px solid var(--border);border-radius:16px;padding:32px 26px;text-align:center;position:relative;transition:border-color .2s,box-shadow .2s}
.flow-card:hover{border-color:var(--teal);box-shadow:0 8px 28px rgba(20,121,108,0.1)}

.flow-arrow{position:absolute;top:50%;right:-14px;transform:translateY(-50%);width:28px;height:28px;background:var(--teal-tint);border:1px solid var(--teal);border-radius:50%;display:flex;align-items:center;justify-content:center;z-index:2}
.flow-arrow svg{width:12px;height:12px;color:var(--teal)}
.flow-card:last-child .flow-arrow{display:none}

.flow-icon{width:56px;height:56px;border-radius:16px;background:var(--teal-tint);display:flex;align-items:center;justify-content:center;margin:0 auto 16px}
.flow-icon svg{width:26px;height:26px;color:var(--teal)}
.flow-card h4{color:var(--teal)!important;margin-bottom:10px;font-size:16px!important;font-weight:700}

@media(max-width:900px){.service-flow{grid-template-columns:1fr}.flow-arrow{display:none}}

.already-have-box{background:var(--white);border:1px solid var(--border);border-radius:14px;padding:28px 32px;margin-top:28px;display:flex;align-items:flex-start;gap:20px}
.already-have-box .icon-wrap{width:46px;height:46px;border-radius:12px;background:var(--teal-tint);display:flex;align-items:center;justify-content:center;flex-shrink:0}
.already-have-box h4{color:var(--teal)!important;margin-bottom:6px}
grid-3-2 validated block ▼ chi tiết

Lưới 5 ô nhịp 3 trên 2 dưới, hàng dưới thụt vào giữa. Hơi bất đối xứng nên đỡ đơn điệu hơn lưới đều, và mắt dừng lại ở hàng dưới thay vì lướt qua một mạch. Không ô nào mồ côi ở bất kỳ bề rộng nào.

Ràng buộc: Chỉ lo phần lưới, KHÔNG tự tạo kiểu cho ô bên trong — ô là element `unit` cắm vào (nguyên tắc tách container khỏi unit). Không để ô mồ côi (CHECKLIST § D).
Hợp cặp: cards/card-numbered, cards/card-treatments, cards/insights-card
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/structures/grid-3-2.md
Khung: Nền: Mở riêng ↗
HTML
<div class="grid32">
  <div class="grid32-cell">[unit 1]</div>
  <div class="grid32-cell">[unit 2]</div>
  <div class="grid32-cell">[unit 3]</div>
  <div class="grid32-cell">[unit 4]</div>
  <div class="grid32-cell">[unit 5]</div>
  <!-- tuỳ chọn: dòng chốt chiếm trọn hàng -->
  <div class="grid32-full">[dòng chốt / callout]</div>
</div>
CSS
.terra-page .grid32 { display: grid; grid-template-columns: repeat(6, 1fr); gap: 20px; margin-top: 12px; }
.terra-page .grid32-cell { grid-column: span 2; }
.terra-page .grid32-cell:nth-child(4) { grid-column: 2 / span 2; }   /* căn giữa hàng dưới */
.terra-page .grid32-cell:nth-child(5) { grid-column: 4 / span 2; }
.terra-page .grid32-full { grid-column: 1 / -1; margin-top: 8px; }

@media (max-width: 1024px) {
  .terra-page .grid32 { grid-template-columns: 1fr; }
  .terra-page .grid32-cell,
  .terra-page .grid32-cell:nth-child(4),
  .terra-page .grid32-cell:nth-child(5) { grid-column: span 1; }
}
hero-sidebar-cards validated block ▼ chi tiết

Cột phải của hero nền teal, có 4 kiểu: thẻ icon xếp chồng (menu định hướng) / đánh số (hứa hẹn số liệu) / đồng hồ rủi ro ("chúng tôi biết vấn đề ở đâu") / liệt kê nghĩa vụ (cảnh báo có kiểm soát cho ngữ cảnh nhiều rủi ro).

Ràng buộc: Chỉ dùng trên trang hero teal; biến thể "nguy hiểm" màu đỏ là điểm nhấn ở mức nội dung, không phải thêm màu mới vào bảng màu.
Hợp cặp: compositions/hero-teal-photo
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/structures/hero-sidebar-cards.md
Khung: Nền: Mở riêng ↗
HTML
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:24px;background:var(--teal);padding:32px;border-radius:12px">
  <div><p style="color:#fff;font-size:11px;letter-spacing:.1em;text-transform:uppercase;margin:0 0 10px;opacity:.8">A · Icon cards</p>
    <div class="hero-cards"><div class="hero-card"><span class="hero-card-icon">✓</span><div>Payroll run</div></div><div class="hero-card"><span class="hero-card-icon">✓</span><div>Insurance filings</div></div><div class="hero-card"><span class="hero-card-icon">✓</span><div>PIT finalization</div></div></div></div>
  <div><p style="color:#fff;font-size:11px;letter-spacing:.1em;text-transform:uppercase;margin:0 0 10px;opacity:.8">B · Numbered</p>
    <div class="hero-cards"><div class="hero-card"><span class="hero-card-num">01</span><div>Audit current process</div></div><div class="hero-card"><span class="hero-card-num">02</span><div>Parallel run 1 cycle</div></div><div class="hero-card"><span class="hero-card-num">03</span><div>Go live</div></div></div></div>
  <div><p style="color:#fff;font-size:11px;letter-spacing:.1em;text-transform:uppercase;margin:0 0 10px;opacity:.8">C · Risk gauge</p>
    <div class="hero-card" style="display:block"><span class="risk-badge">RISK LEVEL: HIGH</span><p style="font-size:13px;margin:10px 0 0">3 of 5 checked items show compliance gaps</p></div></div>
  <div><p style="color:#fff;font-size:11px;letter-spacing:.1em;text-transform:uppercase;margin:0 0 10px;opacity:.8">D · Liabilities</p>
    <div class="liabilities-card"><div class="liabilities-card-header">Inherited liabilities</div><div class="liability-row"><span class="liability-dot"></span><span class="liability-text">Unpaid insurance arrears</span></div><div class="liability-row"><span class="liability-dot"></span><span class="liability-text">Open labor disputes</span></div></div></div>
</div>
CSS
.hero-cards{display:flex;flex-direction:column;gap:14px}
.hero-card{background:rgba(255,255,255,0.07);border:1px solid rgba(170,239,219,0.14);border-radius:14px;padding:20px 24px;backdrop-filter:blur(6px)}
.hero-card-icon{width:38px;height:38px;border-radius:10px;background:rgba(170,239,219,0.13);display:flex;align-items:center;justify-content:center;margin-bottom:12px}
.hero-card-icon svg{width:18px;height:18px;color:var(--green-light)}
.hero-card h3{font-weight:700;color:#fff!important;margin-bottom:6px}
.hero-card p{font-size:16px;color:rgba(255,255,255,0.55);line-height:1.55;margin:0}

.hero-card-num{font-size:28px;font-weight:800;color:var(--green-light);line-height:1;margin-bottom:3px}
.hero-card h3{font-size:13px!important;font-weight:700!important;color:#fff!important;margin-bottom:2px}
.hero-card p{font-size:12px;color:rgba(255,255,255,0.45);line-height:1.5;margin:0}

.risk-gauge-card{background:rgba(255,255,255,0.07);border:1px solid rgba(170,239,219,0.15);border-radius:16px;padding:28px;backdrop-filter:blur(8px)}
.risk-gauge-card h4{color:#fff!important;font-size:14px!important;font-weight:700!important;letter-spacing:0.06em;text-transform:uppercase;margin-bottom:6px}
.risk-gauge-subtitle{font-size:14px;color:rgba(255,255,255,0.45);margin-bottom:22px;line-height:1.5}
.risk-row{display:flex;align-items:center;justify-content:space-between;padding:10px 0;border-bottom:1px solid rgba(170,239,219,0.1)}
.risk-row:last-child{border-bottom:none}
.risk-label{font-size:14px;font-weight:500;color:rgba(255,255,255,0.82);flex:1}
.risk-badge{font-size:14px;font-weight:700;letter-spacing:0.08em;text-transform:uppercase;padding:3px 10px;border-radius:12px;flex-shrink:0}
.risk-high{background:rgba(228,90,60,0.2);color:#FF8A75;border:1px solid rgba(228,90,60,0.3)}
.risk-medium{background:rgba(228,144,37,0.2);color:#F0C070;border:1px solid rgba(228,144,37,0.3)}
.risk-variable{background:rgba(170,239,219,0.12);color:var(--green-light);border:1px solid rgba(170,239,219,0.25)}
.risk-note{font-size:14px;color:rgba(255,255,255,0.3);margin-top:16px;line-height:1.5;font-style:italic}

.liabilities-card{background:rgba(255,255,255,0.07);border:1px solid rgba(228,90,60,0.25);border-radius:16px;padding:28px;backdrop-filter:blur(8px)}
.liabilities-card-header{display:flex;align-items:center;gap:10px;margin-bottom:8px}
.liabilities-card-header h4{color:#fff!important;font-size:14px!important;font-weight:700!important;letter-spacing:0.06em;text-transform:uppercase;margin:0}
.liabilities-card-header .warn-dot{width:8px;height:8px;border-radius:50%;background:#FF8A75;flex-shrink:0}
.liability-row{display:flex;align-items:flex-start;gap:10px;padding:10px 0;border-bottom:1px solid rgba(255,255,255,0.07)}
.liability-row:last-child{border-bottom:none}
.liability-dot{width:6px;height:6px;border-radius:50%;background:#FF8A75;flex-shrink:0;margin-top:6px}
.liability-text{font-size:16px;color:rgba(255,255,255,0.8);line-height:1.5}
hero-split-5050 validated block ▼ chi tiết

Hai nửa cân bằng, chữ một bên và một khối hình một bên, cùng căn giữa theo chiều dọc. Cho cảm giác cân đối và có sản phẩm thật để khoe, khác hẳn kiểu một cột chữ neo trái. Mắt đi trái sang phải rồi quay lại nút.

Ràng buộc: Chiều cao tối thiểu 540px, thấp hơn thì hai nửa trông hụt. Cột chữ tối đa 620px. Xuống 1024px thì xếp dọc, khối hình đi xuống dưới. Khung tối đa 1280px khi dùng cho homepage (CORE_RULES § 4). Motion: T-A, T-B nếu nửa phải là slider.
Hợp cặp: backgrounds/light-gradient-mesh, backgrounds/grid-mask, media/mockup-chrome, media/photo-wash
Nguồn gốc: tách ra từ compositions/hero-homepage-light khi cải tổ store 08/2026
File: brands/terra/patterns/structures/hero-split-5050.md
Khung: Nền: Mở riêng ↗
HTML
<div class="hero-55">
  <div class="hero-55-text">
    <span class="hero-55-eyebrow">[nhãn ngắn]</span>
    <h1>[H1]</h1>
    <p>[1-2 câu dẫn]</p>
    <div class="hero-55-cta">[nút]</div>
  </div>
  <div class="hero-55-media">[unit — mockup, ảnh, thẻ]</div>
</div>
CSS
.terra-page .hero-55 {
  display: grid; grid-template-columns: 1fr 1fr; gap: 48px;
  align-items: center; min-height: 540px;
}
.terra-page .hero-55-text { display: flex; flex-direction: column; gap: 22px; max-width: 620px; }
.terra-page .hero-55-eyebrow {
  display: inline-flex; align-items: center; gap: 10px; width: fit-content;
  padding: 8px 16px; border-radius: 999px;
  background: var(--white); border: 1px solid rgba(20,121,108,.18);
  font-size: 11.5px; font-weight: 700; letter-spacing: .10em; text-transform: uppercase;
  color: var(--teal); box-shadow: var(--shadow-1-base);
}
.terra-page .hero-55-cta { display: flex; gap: 14px; flex-wrap: wrap; }

@media (max-width: 1024px) {
  .terra-page .hero-55 { grid-template-columns: 1fr; gap: 32px; min-height: 0; }
  .terra-page .hero-55-text { max-width: 100%; }
}
hero-text-left validated block ▼ chi tiết

Một cột chữ hẹp neo sát mép trái, nửa phải để trống hẳn. Mắt vào thẳng dòng đầu, đọc một mạch xuống nút, không bị giằng sang bên kia. Khoảng trống bên phải không phải chỗ thừa, nó là thứ giữ cho cột chữ có sức nặng.

Ràng buộc: Cột chữ tối đa 620px, quá rộng là mất hiệu ứng neo. Nội dung phải có z-index cao hơn mọi lớp nền. Trên nền tối thì chữ trắng, hộp trả lời dùng nền rgba tối, không dùng thẻ trắng. Motion: T-A.
Hợp cặp: backgrounds/teal-photo-overlay, decor/corner-circles, structures/proof-row-3stat
Nguồn gốc: tách ra từ compositions/hero-teal-photo khi cải tổ store 08/2026
File: brands/terra/patterns/structures/hero-text-left.md
Khung: Nền: Mở riêng ↗
HTML
<div class="hero-tl">
  <h1>[H1 sentence case] <span class="h2-accent">[cụm nhấn]</span></h1>
  <div class="hero-tl-ans"><p>[định nghĩa hoặc câu trả lời 1-2 câu]</p></div>
  <div class="hero-tl-cta">
    <a href="#[anchor]" class="btn bo">[CTA chính]</a>
    <a href="#[anchor]" class="btn bg">[CTA phụ]</a>
  </div>
</div>
CSS
.terra-page .hero-tl { position: relative; z-index: 4; max-width: 620px; }
.terra-page .hero-tl h1 { margin-bottom: 16px; }
.terra-page .hero-tl-ans {
  max-width: 560px; border-radius: 10px; padding: 15px 18px;
  background: rgba(4,42,40,.58); border: 1px solid rgba(170,239,219,.24); box-shadow: none;
}
.terra-page .hero-tl-ans p { margin: 0; color: rgba(255,255,255,.9); }
.terra-page .hero-tl-ans strong { color: var(--green-light); }
.terra-page .hero-tl-cta { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 24px; }

@media (max-width: 767px) {
  .terra-page .hero-tl { max-width: 100%; }
  .terra-page .hero-tl-cta a { width: 100%; text-align: center; }
}
process-timeline-5step validated block ▼ chi tiết

5 bước tròn trên một đường ngang: đường nối lớn dần từ bước 1 đến bước 5 rồi lặp, đọc như "quy trình này chạy và chạy đều". Pill thời gian trên mỗi bước trả lời "mất bao lâu". Mobile đổi sang trục dọc nét đứt cùng nội dung.

Ràng buộc: Lưới desktop ẩn <=767px, bản .process-mobile thay vào (có sẵn cả hai - không bao giờ giao thiếu một bản); vòng lặp đường-lớn-dần tôn trọng prefers-reduced-motion (đứng yên ở full); số badge cam là điểm nhấn nội dung.
Nguồn gốc: service-page-terra-payroll-vi.html (production, harvested 2026-06-11)
File: brands/terra/patterns/structures/process-timeline-5step.md
Khung: Nền: Mở riêng ↗
HTML
<div class="process-container reveal">
      <div class="process-grid">

      <div class="process-step">
        <div class="process-icon-wrap">
          <svg width="32" height="32" viewBox="0 0 32 32" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <circle cx="16" cy="16" r="3"/>
            <path d="M16 6v3M16 23v3M6 16h3M23 16h3M9 9l2 2M21 21l2 2M9 23l2-2M21 11l2-2"/>
          </svg>
          <span class="process-num-badge">01</span>
        </div>
        <div class="process-time">Tuần 1 – 2</div>
        <h3>Thiết lập hệ thống</h3>
        <p>Hệ thống được cấu hình theo cơ cấu lương và tích hợp dữ liệu chấm công của doanh nghiệp, sẵn sàng vận hành sau 1 đến 2 tuần.</p>
        <div class="process-step-note">
          <strong>Chuyên gia tư vấn đồng hành xuyên suốt</strong> - hỗ trợ doanh nghiệp setup hệ thống và đào tạo bộ phận nhân sự sử dụng nền tảng.
        </div>
      </div>

      <div class="process-step">
        <div class="process-icon-wrap">
          <svg width="32" height="32" viewBox="0 0 32 32" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <rect x="5" y="4" width="22" height="24" rx="2"/>
            <line x1="10" y1="10" x2="22" y2="10"/>
            <line x1="10" y1="15" x2="22" y2="15"/>
            <line x1="10" y1="20" x2="18" y2="20"/>
          </svg>
          <span class="process-num-badge">02</span>
        </div>
        <div class="process-time">Ngày 1 – 3</div>
        <h3>Xử lý bảng lương</h3>
        <p>Doanh nghiệp nhận bảng lương đã tính, đối soát và sẵn sàng phê duyệt trong 3 ngày.</p>
      </div>

      <div class="process-step">
        <div class="process-icon-wrap">
          <svg width="32" height="32" viewBox="0 0 32 32" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <circle cx="16" cy="16" r="12"/>
            <path d="M10 16l4 4 8-8"/>
          </svg>
          <span class="process-num-badge">03</span>
        </div>
        <div class="process-time">Ngày 4 – 5</div>
        <h3>Phê duyệt</h3>
        <p>Bộ phận nhân sự hoặc CFO phê duyệt qua hệ thống, khoảng 30 phút.</p>
      </div>

      <div class="process-step">
        <div class="process-icon-wrap">
          <svg width="32" height="32" viewBox="0 0 32 32" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <path d="M22 20a6 6 0 0 0-2-11.7 8 8 0 0 0-15.3 1.7A5 5 0 0 0 7 21"/>
            <polyline points="12 17 16 13 20 17"/>
            <line x1="16" y1="13" x2="16" y2="25"/>
          </svg>
          <span class="process-num-badge">04</span>
        </div>
        <div class="process-time">Deadline pháp lý</div>
        <h3>Nộp BHXH và khai thuế</h3>
        <p>Báo cáo BHXH và tờ khai thuế được nộp đúng deadline pháp lý, doanh nghiệp không cần theo dõi thủ tục.</p>
        <div class="process-step-note">
          <strong>terra thay mặt doanh nghiệp</strong> làm việc trực tiếp với cơ quan Thuế và Bảo hiểm xã hội.
        </div>
      </div>

      <div class="process-step">
        <div class="process-icon-wrap">
          <svg width="32" height="32" viewBox="0 0 32 32" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <line x1="6" y1="26" x2="26" y2="26"/>
            <rect x="8" y="18" width="4" height="8"/>
            <rect x="14" y="12" width="4" height="14"/>
            <rect x="20" y="6" width="4" height="20"/>
          </svg>
          <span class="process-num-badge">05</span>
        </div>
        <div class="process-time">Liên tục</div>
        <h3>Báo cáo và hỗ trợ</h3>
        <p>Báo cáo tùy chỉnh cho ban lãnh đạo hoặc kiểm toán. Phản hồi trong ngày.</p>
      </div>

      </div><!-- /.process-grid -->

      <!-- v25 - Mobile-only process timeline. Shown <767px, desktop hidden below 767px. -->
      <div class="process-mobile">

        <div class="process-mobile-step">
          <div class="process-mobile-icon">
            <svg viewBox="0 0 32 32" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <circle cx="16" cy="16" r="3"/>
              <path d="M16 6v3M16 23v3M6 16h3M23 16h3M9 9l2 2M21 21l2 2M9 23l2-2M21 11l2-2"/>
            </svg>
            <span class="process-mobile-num">01</span>
          </div>
          <div class="process-mobile-content">
            <span class="process-mobile-time">Tuần 1 - 2</span>
            <h3>Thiết lập hệ thống</h3>
            <p>Hệ thống được cấu hình theo cơ cấu lương và tích hợp dữ liệu chấm công của doanh nghiệp, sẵn sàng vận hành sau 1 đến 2 tuần.</p>
            <div class="process-mobile-note">
              <strong>Chuyên gia tư vấn đồng hành xuyên suốt</strong> - hỗ trợ doanh nghiệp setup hệ thống và đào tạo bộ phận nhân sự sử dụng nền tảng.
            </div>
          </div>
        </div>

        <div class="process-mobile-step">
          <div class="process-mobile-icon">
            <svg viewBox="0 0 32 32" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <rect x="5" y="4" width="22" height="24" rx="2"/>
              <line x1="10" y1="10" x2="22" y2="10"/>
              <line x1="10" y1="15" x2="22" y2="15"/>
              <line x1="10" y1="20" x2="18" y2="20"/>
            </svg>
            <span class="process-mobile-num">02</span>
          </div>
          <div class="process-mobile-content">
            <span class="process-mobile-time">Ngày 1 - 3</span>
            <h3>Xử lý bảng lương</h3>
            <p>Doanh nghiệp nhận bảng lương đã tính, đối soát và sẵn sàng phê duyệt trong 3 ngày.</p>
          </div>
        </div>

        <div class="process-mobile-step">
          <div class="process-mobile-icon">
            <svg viewBox="0 0 32 32" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <circle cx="16" cy="16" r="12"/>
              <path d="M10 16l4 4 8-8"/>
            </svg>
            <span class="process-mobile-num">03</span>
          </div>
          <div class="process-mobile-content">
            <span class="process-mobile-time">Ngày 4 - 5</span>
            <h3>Phê duyệt</h3>
            <p>Bộ phận nhân sự hoặc CFO phê duyệt qua hệ thống, khoảng 30 phút.</p>
          </div>
        </div>

        <div class="process-mobile-step">
          <div class="process-mobile-icon">
            <svg viewBox="0 0 32 32" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <path d="M22 20a6 6 0 0 0-2-11.7 8 8 0 0 0-15.3 1.7A5 5 0 0 0 7 21"/>
              <polyline points="12 17 16 13 20 17"/>
              <line x1="16" y1="13" x2="16" y2="25"/>
            </svg>
            <span class="process-mobile-num">04</span>
          </div>
          <div class="process-mobile-content">
            <span class="process-mobile-time">Deadline pháp lý</span>
            <h3>Nộp BHXH và khai thuế</h3>
            <p>Báo cáo BHXH và tờ khai thuế được nộp đúng deadline pháp lý, doanh nghiệp không cần theo dõi thủ tục.</p>
            <div class="process-mobile-note">
              <strong>terra thay mặt doanh nghiệp</strong> làm việc trực tiếp với cơ quan Thuế và Bảo hiểm xã hội.
            </div>
          </div>
        </div>

        <div class="process-mobile-step">
          <div class="process-mobile-icon">
            <svg viewBox="0 0 32 32" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <line x1="6" y1="26" x2="26" y2="26"/>
              <rect x="8" y="18" width="4" height="8"/>
              <rect x="14" y="12" width="4" height="14"/>
              <rect x="20" y="6" width="4" height="20"/>
            </svg>
            <span class="process-mobile-num">05</span>
          </div>
          <div class="process-mobile-content">
            <span class="process-mobile-time">Liên tục</span>
            <h3>Báo cáo và hỗ trợ</h3>
            <p>Báo cáo tùy chỉnh cho ban lãnh đạo hoặc kiểm toán. Phản hồi trong ngày.</p>
          </div>
        </div>

      </div><!-- /.process-mobile -->

    </div>
CSS
/* ==========================================================================
   V5 ADDITION - Process: monthly calendar visualization
   ========================================================================== */
.process-layout {
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  gap: 44px;
  align-items: start;
}
/* ==========================================================================
   SECTION 6 - PROCESS TIMELINE (5 steps with icons + connector arrows)
   ========================================================================== */
/* v13/v14 - Process timeline: container with flowing line connector between circles
   v23 - removed outer border + bg-light per Tatsuki feedback */
.process-container {
  position: relative;
  border-radius: 20px;
  padding: 48px 28px 40px;
  overflow: hidden;
}
/* v14 - REMOVED full-container gradient (was ::before with inset:0) - replaced by line below */

.process-grid {
  display: grid; grid-template-columns: repeat(5, 1fr);
  gap: 0; position: relative;
}
/* v16 - Process line: BAND extends to OUTER EDGES of circles 1 and 5 (not just centers).
   left: calc(10% - 38px) = circle 1 outer left edge.
   width: calc(80% + 76px) = spans to circle 5 outer right edge (76px = circle diameter).
   Animation uses transform: scaleX (GPU-accelerated, smoother than width animation).
   Origin: left center - band grows from circle 1 outward, no pre-visible baseline. */
.process-grid::before {
  content: '';
  position: absolute;
  top: 0;
  left: calc(10% - 38px);           /* circle 1 OUTER LEFT edge */
  width: calc(80% + 76px);          /* extends to circle 5 OUTER RIGHT edge */
  height: 76px;                     /* circle diameter */
  border-radius: 38px;              /* fully rounded ends */
  background: linear-gradient(90deg,
    rgba(20,121,108,0.15) 0%,
    rgba(20,121,108,0.40) 50%,
    rgba(20,121,108,0.15) 100%
  );
  transform: scaleX(0);
  transform-origin: left center;
  animation: line-grow 5s ease-in-out infinite;
  z-index: 0;
  pointer-events: none;
}
@keyframes line-grow {
  /* Loop boundary: scaleX=0, opacity=0 -> invisible at start AND end, no jank */
  0%   { transform: scaleX(0); opacity: 0; }
  5%   { transform: scaleX(0); opacity: 1; }   /* fade in at width 0 (invisible) */
  50%  { transform: scaleX(1); opacity: 1; }   /* grow to full from circle 1 outer L -> circle 5 outer R */
  75%  { transform: scaleX(1); opacity: 1; }   /* hold */
  95%  { transform: scaleX(1); opacity: 0; }   /* fade out at full width */
  100% { transform: scaleX(0); opacity: 0; }   /* snap scaleX back to 0 while invisible */
}
.process-step {
  position: relative; padding: 0 12px; text-align: center;
  z-index: 1;   /* above line; circles (with white bg) mask the line at their positions */
}
.process-icon-wrap {
  width: 76px; height: 76px; margin: 0 auto 16px;
  background: var(--white);
  border: 2px solid var(--teal);
  color: var(--teal);
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  position: relative;
  transition: all .3s;
  box-shadow: 0 4px 16px rgba(20,121,108,0.10);
}
.process-step:hover .process-icon-wrap { background: var(--teal); color: var(--white); transform: scale(1.04); }
.process-num-badge {
  position: absolute; top: -6px; right: -6px;
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--orange); color: var(--white);
  font-size: 11px; font-weight: 700;
  display: flex; align-items: center; justify-content: center;
}
.process-time {
  display: inline-block; font-size: 10.5px; font-weight: 700;
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--orange);
  background: rgba(228,144,37,0.10);
  padding: 4px 10px; border-radius: 20px; margin-bottom: 10px;
}
.process-step h3 { font-size: 15px; margin-bottom: 8px; letter-spacing: -0.005em; }
.process-step p { font-size: 13px; line-height: 1.55; color: var(--text-body); }
/* v11 - CS team support note: full rounded green box, NO left border */
.process-step-note {
  margin-top: 10px;
  padding: 10px 12px;
  background: rgba(20,121,108,0.08);
  border-radius: 8px;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--text-body);
  text-align: left;
}
.process-step-note strong { color: var(--teal-dark); }
/* ==========================================================================
   v25 - Process Timeline MOBILE - purpose-built for narrow viewports
   (replaces adapted desktop grid which had excess whitespace on mobile)
   Hidden on desktop, shown on mobile. Desktop process-grid hidden below 767px.
   ========================================================================== */
.process-mobile { display: none; }
@media (max-width: 767px) {
.process-container > .process-grid { display: none; }
.process-mobile { display: block; }
}
.process-mobile-step {
  display: grid;
  grid-template-columns: 54px 1fr;
  gap: 14px;
  position: relative;
  padding-bottom: 22px;
}
.process-mobile-step:last-child { padding-bottom: 0; }
.process-mobile-step:not(:last-child)::after {
  content: '';
  position: absolute;
  top: 56px;
  bottom: 0;
  left: 26px;
  width: 2px;
  background: repeating-linear-gradient(to bottom, var(--border) 0, var(--border) 5px, transparent 5px, transparent 10px);
}
.process-mobile-icon {
  width: 54px; height: 54px;
  border: 2px solid var(--teal);
  border-radius: 50%;
  background: var(--white);
  display: flex; align-items: center; justify-content: center;
  color: var(--teal-dark);
  position: relative;
  z-index: 1;
}
.process-mobile-icon svg { width: 24px; height: 24px; }
.process-mobile-num {
  position: absolute;
  top: -3px; right: -3px;
  background: var(--orange);
  color: var(--white);
  width: 20px; height: 20px;
  border-radius: 50%;
  font-size: 9.5px;
  font-weight: 700;
  display: flex; align-items: center; justify-content: center;
  letter-spacing: 0;
}
.process-mobile-content { padding-top: 2px; min-width: 0; }
.process-mobile-time {
  display: inline-block;
  background: rgba(228,144,37,0.10);
  color: var(--orange);
  padding: 3px 9px;
  border-radius: 12px;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 6px;
}
.process-mobile-content h3 {
  font-size: 15.5px;
  font-weight: 700;
  color: var(--text-primary);
  margin: 0 0 5px;
  line-height: 1.3;
  letter-spacing: -0.005em;
}
.process-mobile-content p {
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-body);
  margin: 0;
}
.process-mobile-note {
  margin-top: 9px;
  background: rgba(20,121,108,0.07);
  padding: 9px 11px;
  border-radius: 8px;
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--text-body);
  text-align: left;
}
.process-mobile-note strong { color: var(--teal-dark); font-weight: 700; }
proof-row-3stat validated block ▼ chi tiết

Một dải ngang 3 con số đặt sát nhau, đọc như một lời phán; đặt dưới tiêu đề hero để trả lời "chứng minh đi" trước cả khi câu hỏi hình thành.

Ràng buộc: Số liệu ghi nguyên văn từ CORE_RULES § 1 - không bao giờ dùng số 70% khách Nhật.
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/structures/proof-row-3stat.md
Khung: Nền: Mở riêng ↗
HTML
<div class="hero-proof">
  <div class="hero-proof-item">
    <span class="n">250+</span>
    <span class="l">FDI clients</span>
  </div>
  <div class="hero-proof-item">
    <span class="n">21+</span>
    <span class="l">Năm I-GLOCAL</span>
  </div>
  <div class="hero-proof-item">
    <span class="n">ISO</span>
    <span class="l">27001:2022</span>
  </div>
</div>
CSS
.hero-proof {
  display: flex;
  gap: 24px;
  flex-wrap: wrap;
  margin-top: 14px;
  padding-top: 22px;
  border-top: 1px solid rgba(20,121,108,0.16);
}

.hero-proof-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.hero-proof-item .n {
  font-size: 24px;
  font-weight: 800;
  color: var(--teal);
  line-height: 1;
  letter-spacing: -0.02em;
}

/* Middle item ORANGE — per Rule 6 color rotation */
.hero-proof-item:nth-child(2) .n { color: var(--orange); }

.hero-proof-item .l {
  font-size: 11px;
  font-weight: 600;
  color: var(--text-muted);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

@media (max-width: 600px) {
  .hero-proof { gap: 16px; }
  .hero-proof-item .n { font-size: 20px; }
}
timeline-variants validated block ▼ chi tiết

5 biến thể dòng thời gian, mỗi biến thể cân giữa lượng chữ và nhịp hình khác nhau (V1 nhiều chữ ... V5 dạng cột lịch sử). Để thể hiện trình tự thời gian theo cách đi được từng bước.

Ràng buộc: Toàn bộ CSS nằm ở design-assets/timeline-patterns.html - copy và chỉnh, đừng dựng lại từ đầu.
Nguồn gốc: homepage v5 / terra Payroll v25 / BHXH demo
File: brands/terra/patterns/structures/timeline-variants.md
Khung: Nền: Mở riêng ↗
HTML
<style>
.tlv-demo{display:grid;grid-template-columns:repeat(4,1fr);gap:0;position:relative;padding-top:26px}
.tlv-demo::before{content:'';position:absolute;top:14px;left:12%;right:12%;height:2px;background:var(--border)}
.tlv-step{text-align:center;padding:0 10px}
.tlv-dot{width:28px;height:28px;border-radius:50%;background:var(--teal);color:#fff;font-size:13px;font-weight:700;display:inline-flex;align-items:center;justify-content:center;position:relative;top:-26px;margin-bottom:-16px}
.tlv-step h6{margin:4px 0;font-size:14px}.tlv-step p{font-size:12px;color:var(--text-body);margin:0}
</style>
<p style="font-size:12px;color:var(--text-muted);margin:0 0 8px">V3 connector-line variant rendered below - all 5 variants live in design-assets/timeline-patterns.html</p>
<div class="tlv-demo">
  <div class="tlv-step"><span class="tlv-dot">1</span><h6>Kick-off</h6><p>Week 1</p></div>
  <div class="tlv-step"><span class="tlv-dot">2</span><h6>Data migration</h6><p>Week 2-3</p></div>
  <div class="tlv-step"><span class="tlv-dot">3</span><h6>Parallel run</h6><p>Week 4-5</p></div>
  <div class="tlv-step"><span class="tlv-dot">4</span><h6>Go live</h6><p>Week 6</p></div>
</div>
JS
(function(){
  var tl=document.querySelector('.tl-v1'); if(!tl) return;
  var rows=Array.prototype.slice.call(tl.querySelectorAll('.tl-v1-row'));
  var reduce=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  function centers(){return rows.map(function(r){var d=r.querySelector('.tl-v1-dot');return r.offsetTop+d.offsetTop+d.offsetHeight/2;});}
  if(reduce){tl.style.setProperty('--tlp',1);rows.forEach(function(r){r.classList.add('in');});return;}
  var ticking=false, cs=[], top0=0, span=1;
  function measure(){cs=centers();top0=cs[0];span=Math.max(1,cs[cs.length-1]-top0);tl.style.setProperty('--tlh',span+'px');}
  function upd(){
    ticking=false;
    var rect=tl.getBoundingClientRect();
    var line=window.innerHeight*0.62;
    var p=(line-(rect.top+top0))/span; p=Math.max(0,Math.min(1,p));
    tl.style.setProperty('--tlp',p.toFixed(4));
    var reached=top0+span*p;
    rows.forEach(function(r,i){if(reached>=cs[i]-1) r.classList.add('in');});
  }
  function onScroll(){if(!ticking){ticking=true;requestAnimationFrame(upd);}}
  measure();
  window.addEventListener('scroll',onScroll,{passive:true});
  window.addEventListener('resize',function(){measure();upd();});
  if(document.fonts&&document.fonts.ready)document.fonts.ready.then(function(){measure();upd();});
  upd();
})();