feat: implement Bento layout support and Editor.js integration for dynamic post lists

This commit is contained in:
2026-08-03 20:00:07 +07:00
parent 48a2cd02f1
commit 141d3a71fd
112 changed files with 4005 additions and 186 deletions
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
:root{--swiper-navigation-size:44px}.swiper-button-next,.swiper-button-prev{position:absolute;top:var(--swiper-navigation-top-offset,50%);width:calc(var(--swiper-navigation-size) / 44 * 27);height:var(--swiper-navigation-size);margin-top:calc(0px - (var(--swiper-navigation-size) / 2));z-index:10;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--swiper-navigation-color,var(--swiper-theme-color))}.swiper-button-next.swiper-button-disabled,.swiper-button-prev.swiper-button-disabled{opacity:.35;cursor:auto;pointer-events:none}.swiper-button-next.swiper-button-hidden,.swiper-button-prev.swiper-button-hidden{opacity:0;cursor:auto;pointer-events:none}.swiper-navigation-disabled .swiper-button-next,.swiper-navigation-disabled .swiper-button-prev{display:none!important}.swiper-button-next svg,.swiper-button-prev svg{width:100%;height:100%;object-fit:contain;transform-origin:center}.swiper-rtl .swiper-button-next svg,.swiper-rtl .swiper-button-prev svg{transform:rotate(180deg)}.swiper-button-prev,.swiper-rtl .swiper-button-next{left:var(--swiper-navigation-sides-offset,10px);right:auto}.swiper-button-lock{display:none}.swiper-button-next:after,.swiper-button-prev:after{font-family:swiper-icons;font-size:var(--swiper-navigation-size);text-transform:none!important;letter-spacing:0;font-variant:normal;line-height:1}.swiper-button-prev:after,.swiper-rtl .swiper-button-next:after{content:"prev"}.swiper-button-next,.swiper-rtl .swiper-button-prev{right:var(--swiper-navigation-sides-offset,10px);left:auto}.swiper-button-next:after,.swiper-rtl .swiper-button-prev:after{content:"next"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 555 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 KiB

@@ -0,0 +1 @@
/* PLEASE DO NOT COPY AND PASTE THIS CODE. */(function(){var w=window,C='___grecaptcha_cfg',cfg=w[C]=w[C]||{},N='grecaptcha';var gr=w[N]=w[N]||{};gr.ready=gr.ready||function(f){(cfg['fns']=cfg['fns']||[]).push(f);};w['__recaptcha_api']='https://www.google.com/recaptcha/api2/';(cfg['render']=cfg['render']||[]).push('onload');(cfg['anchor-ms']=cfg['anchor-ms']||[]).push(20000);(cfg['execute-ms']=cfg['execute-ms']||[]).push(30000);w['__google_recaptcha_client']=true;var d=document,po=d.createElement('script');po.type='text/javascript';po.async=true; po.charset='utf-8';po.src='https://www.gstatic.com/recaptcha/releases/w_Yb7dGGXaKesJ7BMiqFJqBG/recaptcha__en_gb.js';po.crossOrigin='anonymous';po.integrity='sha384-13nH8ZfzCpKgAUReVahVHFp+kkT6FLDSedzZKZYWDWiNB1Y+B+iPhFObeMNXj1G7';var e=d.querySelector('script[nonce]'),n=e&&(e['nonce']||e.getAttribute('nonce'));if(n){po.setAttribute('nonce',n);}var s=d.getElementsByTagName('script')[0];s.parentNode.insertBefore(po, s);})();
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 506 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 917 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 581 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

@@ -0,0 +1 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[4219],{70094:(e,s,n)=>{Promise.resolve().then(n.bind(n,87959))},87959:(e,s,n)=>{"use strict";n.r(s),n.d(s,{default:()=>u});var l=n(45704),r=n(24293),t=n(39774),a=n.n(t);function u(e){return(0,l.jsx)("html",{lang:r.DT.defaultLocale,children:(0,l.jsx)("body",{children:(0,l.jsx)(a(),{statusCode:0})})})}}},e=>{var s=s=>e(e.s=s);e.O(0,[2263,6696,6069,4293,1032,4619,7358],()=>s(70094)),_N_E=e.O()}]);
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

@@ -0,0 +1 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[7358],{24473:(e,s,n)=>{Promise.resolve().then(n.t.bind(n,14933,23)),Promise.resolve().then(n.t.bind(n,86695,23)),Promise.resolve().then(n.t.bind(n,54775,23)),Promise.resolve().then(n.t.bind(n,22908,23)),Promise.resolve().then(n.t.bind(n,23624,23)),Promise.resolve().then(n.t.bind(n,59440,23)),Promise.resolve().then(n.t.bind(n,62920,23)),Promise.resolve().then(n.t.bind(n,19710,23))}},e=>{var s=s=>e(e.s=s);e.O(0,[1032,4619],()=>(s(36596),s(24473))),_N_E=e.O()}]);
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
!function(r,i){"use strict";var e,o=r.location,s=r.document,t=s.querySelector('[src*="'+i+'"]'),l=t&&t.getAttribute("data-domain"),p=r.localStorage.plausible_ignore;function c(e){console.warn("Ignoring Event: "+e)}function a(e,t){if(/^localhost$|^127(?:\.[0-9]+){0,2}\.[0-9]+$|^(?:0*\:)*?:?0*1$/.test(o.hostname)||"file:"===o.protocol)return c("localhost");if(!(r.phantom||r._phantom||r.__nightmare||r.navigator.webdriver||r.Cypress)){if("true"==p)return c("localStorage flag");var a={};a.n=e,a.u=o.href,a.d=l,a.r=s.referrer||null,a.w=r.innerWidth,t&&t.meta&&(a.m=JSON.stringify(t.meta)),t&&t.props&&(a.p=JSON.stringify(t.props));var n=new XMLHttpRequest;n.open("POST",i+"/api/event",!0),n.setRequestHeader("Content-Type","text/plain"),n.send(JSON.stringify(a)),n.onreadystatechange=function(){4==n.readyState&&t&&t.callback&&t.callback()}}}function n(){e!==o.pathname&&(e=o.pathname,a("pageview"))}try{var u,h=r.history;h.pushState&&(u=h.pushState,h.pushState=function(){u.apply(this,arguments),n()},r.addEventListener("popstate",n));var g=r.plausible&&r.plausible.q||[];r.plausible=a;for(var f=0;f<g.length;f++)a.apply(this,g[f]);"prerender"===s.visibilityState?s.addEventListener("visibilitychange",function(){e||"visible"!==s.visibilityState||n()}):n()}catch(e){console.error(e),(new Image).src=i+"/api/error?message="+encodeURIComponent(e.message)}}(window,"https://analytics.jamstackvietnam.com");
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 959 B

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 796 B

+382
View File
@@ -0,0 +1,382 @@
<!doctype html>
<html lang="vi">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Thành tựu và Bài viết Bệnh viện</title>
<style>
/* ===================================================
1. RESET CSS & FONT CƠ BẢN
=================================================== */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif;
}
body {
background-color: #f8fafc;
color: #1e293b;
padding: 40px 20px;
}
.news-container {
max-width: 1200px;
margin: 0 auto;
}
/* ===================================================
2. FILTER TABS (THANH LỌC DANH MỤC)
=================================================== */
.filter-tabs {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 30px;
}
.tab-btn {
padding: 8px 18px;
border: none;
background-color: #e2e8f0;
color: #475569;
font-size: 14px;
font-weight: 500;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
}
.tab-btn:hover {
background-color: #cbd5e1;
}
.tab-btn.active {
background-color: #0284c7; /* Màu xanh chủ đạo y tế */
color: #ffffff;
}
/* ===================================================
3. CARD GRID (LƯỚI 3 CỘT chuẩn UI)
=================================================== */
.news-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
margin-bottom: 40px;
}
.news-card {
background-color: #ffffff;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
display: flex;
flex-direction: column;
transition:
transform 0.25s ease,
box-shadow 0.25s ease;
cursor: pointer;
border: 1px solid #f1f5f9;
}
.news-card:hover {
transform: translateY(-4px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
}
/* THUMBNAIL & BADGE NĂM */
.card-thumb {
position: relative;
width: 100%;
aspect-ratio: 16 / 9; /* Cố định tỉ lệ hình ảnh */
overflow: hidden;
background-color: #e2e8f0;
}
.card-thumb img {
width: 100%;
height: 100%;
object-fit: cover;
}
.year-badge {
position: absolute;
top: 10px;
right: 10px;
background-color: #0284c7;
color: #ffffff;
font-size: 12px;
font-weight: 600;
padding: 4px 10px;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
/* NỘI DUNG CARD */
.card-body {
padding: 16px 18px 20px;
display: flex;
flex-direction: column;
flex-grow: 1;
}
.card-title {
font-size: 14px;
font-weight: 700;
color: #0284c7; /* Chữ xanh y tế */
line-height: 1.45;
/* Cắt bớt văn bản thành 3 dòng kèm dấu ... */
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* ===================================================
4. PHÂN TRANG (PAGINATION)
=================================================== */
.pagination {
display: flex;
justify-content: center;
align-items: center;
list-style: none;
gap: 8px;
}
.page-link {
display: inline-flex;
justify-content: center;
align-items: center;
width: 36px;
height: 36px;
border-radius: 50%;
border: 1px solid #e2e8f0;
background-color: #ffffff;
color: #64748b;
text-decoration: none;
font-size: 14px;
font-weight: 500;
transition: all 0.2s ease;
}
.page-link:hover {
border-color: #0284c7;
color: #0284c7;
}
.page-link.active {
background-color: #0284c7;
color: #ffffff;
border-color: #0284c7;
}
.page-dots {
padding: 0 4px;
color: #94a3b8;
}
/* ===================================================
5. RESPONSIVE
=================================================== */
@media (max-width: 992px) {
.news-grid {
grid-template-columns: repeat(2, 1fr); /* Tablet chia 2 cột */
}
}
@media (max-width: 576px) {
.news-grid {
grid-template-columns: 1fr; /* Mobile hiển thị 1 cột */
}
.filter-tabs {
justify-content: flex-start;
}
}
</style>
</head>
<body>
<div class="news-container">
<!-- 1. TAB LỌC NỘI DUNG -->
<div class="filter-tabs">
<button class="tab-btn active">Tất cả</button>
<button class="tab-btn">Điều trị</button>
<button class="tab-btn">Đào tạo - Nghiên cứu khoa học</button>
<button class="tab-btn">Quản trị Bệnh viện</button>
</div>
<!-- 2. DANH SÁCH BÀI VIẾT dạng LƯỚI GRID -->
<div class="news-grid">
<!-- Card 1 -->
<article class="news-card">
<div class="card-thumb">
<img
src="https://picsum.photos/400/250?random=1"
alt="Bài viết 1"
/>
<span class="year-badge">2026</span>
</div>
<div class="card-body">
<h3 class="card-title">
Bệnh viện Đại học Y Dược TP. Hồ Chí Minh ghi dấu ấn tại giải
thưởng "Đại sứ truyền thông...
</h3>
</div>
</article>
<!-- Card 2 -->
<article class="news-card">
<div class="card-thumb">
<img
src="https://picsum.photos/400/250?random=2"
alt="Bài viết 2"
/>
<span class="year-badge">2026</span>
</div>
<div class="card-body">
<h3 class="card-title">
Tác phẩm "Tuyển tập những bệnh án lâm sàng đặc sắc" đạt giải B
giải thưởng sách quốc gia...
</h3>
</div>
</article>
<!-- Card 3 -->
<article class="news-card">
<div class="card-thumb">
<img
src="https://picsum.photos/400/250?random=3"
alt="Bài viết 3"
/>
<span class="year-badge">2026</span>
</div>
<div class="card-body">
<h3 class="card-title">
Trái tim từ bắc vào nam hồi sinh sự sống cho bệnh nhi ngày đầu
xuân mới
</h3>
</div>
</article>
<!-- Card 4 -->
<article class="news-card">
<div class="card-thumb">
<img
src="https://picsum.photos/400/250?random=4"
alt="Bài viết 4"
/>
<span class="year-badge">2025</span>
</div>
<div class="card-body">
<h3 class="card-title">
Ca ghép tim xuyên đêm tại Bệnh viện Đại học Y Dược TP. Hồ Chí
Minh: Cuộc chạy đua sinh tử...
</h3>
</div>
</article>
<!-- Card 5 -->
<article class="news-card">
<div class="card-thumb">
<img
src="https://picsum.photos/400/250?random=5"
alt="Bài viết 5"
/>
<span class="year-badge">2025</span>
</div>
<div class="card-body">
<h3 class="card-title">
Đại học Y Dược TP. Hồ Chí Minh công nhận 02 nhóm nghiên cứu tiềm
năng mạnh đầu tiên
</h3>
</div>
</article>
<!-- Card 6 -->
<article class="news-card">
<div class="card-thumb">
<img
src="https://picsum.photos/400/250?random=6"
alt="Bài viết 6"
/>
<span class="year-badge">2025</span>
</div>
<div class="card-body">
<h3 class="card-title">
Bệnh viện Đại học Y Dược TP. Hồ Chí Minh - Điển hình tiên tiến
Ngành Y tế tại Đại hội Thi đ...
</h3>
</div>
</article>
<!-- Card 7 -->
<article class="news-card">
<div class="card-thumb">
<img
src="https://picsum.photos/400/250?random=7"
alt="Bài viết 7"
/>
<span class="year-badge">2025</span>
</div>
<div class="card-body">
<h3 class="card-title">
Bệnh viện Đại học Y Dược TP. Hồ Chí Minh tổ chức lễ kỷ niệm 100 ca
TAVI thành công trao...
</h3>
</div>
</article>
<!-- Card 8 -->
<article class="news-card">
<div class="card-thumb">
<img
src="https://picsum.photos/400/250?random=8"
alt="Bài viết 8"
/>
<span class="year-badge">2025</span>
</div>
<div class="card-body">
<h3 class="card-title">
Bệnh viện Đại học Y Dược TP. Hồ Chí Minh đạt giải "Vệ sinh tay
xuất sắc năm 2025"
</h3>
</div>
</article>
<!-- Card 9 -->
<article class="news-card">
<div class="card-thumb">
<img
src="https://picsum.photos/400/250?random=9"
alt="Bài viết 9"
/>
<span class="year-badge">2025</span>
</div>
<div class="card-body">
<h3 class="card-title">
Tiến bộ trong y học bào thai: Can thiệp thành công cứu sống thai
nhi mắc khối u nhau thai...
</h3>
</div>
</article>
</div>
<!-- 3. THANH PHÂN TRANG -->
<ul class="pagination">
<li><a href="#" class="page-link">&laquo;</a></li>
<li><a href="#" class="page-link">&lsaquo;</a></li>
<li><a href="#" class="page-link active">1</a></li>
<li><a href="#" class="page-link">2</a></li>
<li><a href="#" class="page-link">3</a></li>
<li><span class="page-dots">...</span></li>
<li><a href="#" class="page-link">6</a></li>
<li><a href="#" class="page-link">&rsaquo;</a></li>
<li><a href="#" class="page-link">&raquo;</a></li>
</ul>
</div>
</body>
</html>
@@ -1,5 +1,5 @@
<section class="sis-hover-card-v2-section py-5"> <section class="sis-hover-card-v2-section py-5">
<div class="container"> <div class="container">
<h2 class="text-center display-4 text-primary-600 xl:mb-6 md:mb-4 mb-3 max-md:px-4 uppercase font-weight-bold">GIÁ TRỊ CỐT LÕI</h2> <h2 class="text-center display-4 text-primary-600 xl:mb-6 md:mb-4 mb-3 max-md:px-4 uppercase font-weight-bold">GIÁ TRỊ CỐT LÕI 2</h2>
</div> </div>
</section> </section>

Some files were not shown because too many files have changed in this diff Show More