This is an automated email from the ASF dual-hosted git repository.

benjobs pushed a commit to branch dev
in repository 
https://gitbox.apache.org/repos/asf/incubator-streampark-website.git


The following commit(s) were added to refs/heads/dev by this push:
     new d4ee99ba feat: improve blog page (#285)
d4ee99ba is described below

commit d4ee99ba1a8791973391d312557d3b15ec0e34a0
Author: Kriszu <[email protected]>
AuthorDate: Sat Nov 4 20:46:10 2023 +0800

    feat: improve blog page (#285)
---
 blog/0-streampark-flink-on-k8s.md                  |   5 +-
 blog/1-flink-framework-streampark.md               |   6 +-
 blog/2-streampark-usercase-chinaunion.md           |   7 +-
 blog/3-streampark-usercase-bondex-paimon.md        |  36 +-
 blog/4-streampark-usercase-shunwang.md             |   8 +-
 blog/5-streampark-usercase-dustess.md              |  26 +-
 blog/6-streampark-usercase-joyme.md                |  14 +-
 blog/7-streampark-usercase-haibo.md                |   6 +-
 docusaurus.config.js                               |   5 +-
 i18n/zh-CN/code.json                               |   6 +
 .../0-streampark-flink-on-k8s.md                   |  24 +-
 .../1-flink-framework-streampark.md                |   6 +
 .../2-streampark-usercase-chinaunion.md            |   2 +
 .../3-streampark-usercase-bondex-paimon.md         |  36 +-
 .../4-streampark-usercase-shunwang.md              |   7 +-
 .../5-streampark-usercase-dustess.md               |  26 +-
 .../6-streampark-usercase-joyme.md                 |  14 +-
 .../7-streampark-usercase-haibo.md                 |   6 +-
 package.json                                       |   2 +
 src/css/custom.css                                 | 482 ++++++++++++++++++++-
 src/pages/home/theme.less                          |  10 +-
 src/theme/BlogArchivePage/index.tsx                |  85 ++++
 src/theme/BlogLayout/index.tsx                     |  32 ++
 src/theme/BlogListPage/img/card.svg                |   3 +
 src/theme/BlogListPage/img/list.svg                |   3 +
 src/theme/BlogListPage/index.tsx                   | 111 +++++
 src/theme/BlogListPage/useViewType.ts              |  19 +
 src/theme/BlogListPaginator/index.tsx              |  45 ++
 src/theme/BlogPostItem/Container/index.tsx         |  74 ++++
 src/theme/BlogPostItem/Container/style.less        |  99 +++++
 src/theme/BlogPostItem/Content/index.tsx           |  23 +
 .../BlogPostItem/Footer/ReadMoreLink/index.tsx     |  37 ++
 .../Footer/ReadMoreLink/styles.module.css          |  11 +
 src/theme/BlogPostItem/Footer/index.tsx            |  67 +++
 src/theme/BlogPostItem/Footer/styles.module.css    |  10 +
 src/theme/BlogPostItem/Header/Author/index.tsx     |  53 +++
 src/theme/BlogPostItem/Header/Authors/index.tsx    |  47 ++
 .../BlogPostItem/Header/Authors/styles.module.css  |  21 +
 src/theme/BlogPostItem/Header/Info/index.tsx       |  65 +++
 .../BlogPostItem/Header/Info/styles.module.css     |  10 +
 src/theme/BlogPostItem/Header/Tags/index.tsx       |  36 ++
 src/theme/BlogPostItem/Header/Title/index.tsx      |  37 ++
 .../BlogPostItem/Header/Title/styles.module.css    |  56 +++
 src/theme/BlogPostItem/Header/index.tsx            |  16 +
 src/theme/BlogPostItem/index.tsx                   |  29 ++
 src/theme/BlogPostItems/index.tsx                  |  50 +++
 src/theme/BlogPostLisView/index.tsx                |  98 +++++
 src/theme/BlogPostPage/Metadata/index.tsx          |  44 ++
 src/theme/BlogPostPage/index.tsx                   |  65 +++
 src/theme/BlogPostPaginator/index.tsx              |  51 +++
 src/theme/BlogSidebar/Desktop/index.tsx            |  67 +++
 src/theme/BlogSidebar/Desktop/styles.module.css    |  80 ++++
 src/theme/BlogSidebar/Mobile/index.tsx             |  32 ++
 src/theme/BlogSidebar/index.tsx                    |  17 +
 src/theme/BlogTagsListPage/index.tsx               |  30 ++
 src/theme/BlogTagsPostsPage/index.tsx              |  93 ++++
 src/theme/TOC/index.tsx                            |  34 ++
 src/theme/TOC/styles.module.css                    |  23 +
 static/image/circle.svg                            |   8 +
 59 files changed, 2298 insertions(+), 117 deletions(-)

diff --git a/blog/0-streampark-flink-on-k8s.md 
b/blog/0-streampark-flink-on-k8s.md
index 74887759..0b77a276 100644
--- a/blog/0-streampark-flink-on-k8s.md
+++ b/blog/0-streampark-flink-on-k8s.md
@@ -2,11 +2,12 @@
 slug: streampark-flink-on-k8s
 title: StreamPark Flink on Kubernetes practice
 tags: [StreamPark, 生产实践, FlinkSQL, Kubernetes]
+description: Wuxin Technology was founded in January 2018. The current main 
business includes the research and development, design, manufacturing and sales 
of RELX brand products. With core technologies and capabilities covering the 
entire industry chain, RELX is committed to providing users with products that 
are both high quality and safe
 ---
 
-# StreamPark Flink on Kubernetes practice
+Wuxin Technology was founded in January 2018. The current main business 
includes the research and development, design, manufacturing and sales of RELX 
brand products. With core technologies and capabilities covering the entire 
industry chain, RELX is committed to providing users with products that are 
both high quality and safe.
 
-  Wuxin Technology was founded in January 2018. The current main business 
includes the research and development, design, manufacturing and sales of RELX 
brand products. With core technologies and capabilities covering the entire 
industry chain, RELX is committed to providing users with products that are 
both high quality and safe.
+<!-- truncate -->
 
 ## **Why Choose Native Kubernetes**
 
diff --git a/blog/1-flink-framework-streampark.md 
b/blog/1-flink-framework-streampark.md
index b232658b..fbd11b8b 100644
--- a/blog/1-flink-framework-streampark.md
+++ b/blog/1-flink-framework-streampark.md
@@ -4,7 +4,11 @@ title: Flink Development Toolkit StreamPark
 tags: [StreamPark, DataStream, FlinkSQL]
 ---
 
-<br/>
+Although the Hadoop system is widely used today, its architecture is 
complicated, it has a high maintenance complexity, version upgrades are 
challenging, and due to departmental reasons, data center scheduling is 
prolonged. We urgently need to explore agile data platform models. With the 
current prevalence of cloud-native architecture and the backdrop of lake and 
warehouse integration, we have decided to use Doris as an offline data 
warehouse and TiDB (which is already in production) as  [...]
+
+![](/blog/belle/doris.png)
+
+<!-- truncate -->
 
 # 1. Background
 
diff --git a/blog/2-streampark-usercase-chinaunion.md 
b/blog/2-streampark-usercase-chinaunion.md
index 6bef32b1..6f4ea6bf 100644
--- a/blog/2-streampark-usercase-chinaunion.md
+++ b/blog/2-streampark-usercase-chinaunion.md
@@ -4,7 +4,7 @@ title: China Union's Flink Real-Time Computing Platform Ops 
Practice
 tags: [StreamPark, Production Practice, FlinkSQL]
 ---
 
-# China Union Flink Real-Time Computing Platform Ops Practices
+![](/blog/chinaunion/overall_architecture.png)
 
 **Abstract:** This article is compiled from the sharing of Mu Chunjin, the 
head of China Union Data Science's real-time computing team and Apache 
StreamPark Committer, at the Flink Forward Asia 2022 platform construction 
session. The content of this article is mainly divided into four parts:
 
@@ -13,11 +13,10 @@ tags: [StreamPark, Production Practice, FlinkSQL]
 - Integrated Management Based on StreamPark
 - Future Planning and Evolution
 
-## **Introduction to the Real-Time Computing Platform Background**
-
-![](/blog/chinaunion/overall_architecture.png)
+<!-- truncate -->
 
 
+## **Introduction to the Real-Time Computing Platform Background**
 
 The image above depicts the overall architecture of the real-time computing 
platform. At the bottom layer, we have the data sources. Due to some sensitive 
information, the detailed information of the data sources is not listed. It 
mainly includes three parts: business databases, user behavior logs, and user 
location. China Union has a vast number of data sources, with just the business 
databases comprising tens of thousands of tables. The data is primarily 
processed through Flink SQL and [...]
 
diff --git a/blog/3-streampark-usercase-bondex-paimon.md 
b/blog/3-streampark-usercase-bondex-paimon.md
index b7eb7f45..eb61a598 100644
--- a/blog/3-streampark-usercase-bondex-paimon.md
+++ b/blog/3-streampark-usercase-bondex-paimon.md
@@ -4,8 +4,6 @@ title: 海程邦达基于 Apache Paimon + StreamPark 的流式数仓实践
 tags: [StreamPark, 生产实践, paimon, streaming-warehouse]
 ---
 
-# 海程邦达基于 Apache Paimon + StreamPark 的流式数仓实践
-
 ![](/blog/bondex/Bondex.png)
 
 **导读:**本文主要介绍作为供应链物流服务商海程邦达在数字化转型过程中采用 Paimon + StreamPark 平台实现流式数仓的落地方案。我们以 
Apache StreamPark 流批一体平台提供了一个易于上手的生产操作手册,以帮助用户提交 Flink 任务并迅速掌握 Paimon 的使用方法。
@@ -16,6 +14,8 @@ tags: [StreamPark, 生产实践, paimon, streaming-warehouse]
 - 问题排查分析
 - 未来规划
 
+<!-- truncate -->
+
 ## 01 公司业务情况介绍
 
 海程邦达集团一直专注于供应链物流领域,通过打造优秀的国际化物流平台,为客户提供端到端一站式智慧型供应链物流服务。集团现有员工 2000 余人,年营业额逾 
120 亿人民币,网络遍及全球 200 余个港口,在海内外有超 80 家分、子公司,助力中国企业与世界互联互通。
@@ -262,9 +262,9 @@ kubectl create ns streamx
 使用 default 账户创建 clusterrolebinding 资源:
 
 ```shell
-kubectl create secret docker-registry streamparksecret 
---docker-server=registry-vpc.cn-zhangjiakou.aliyuncs.com 
---docker-username=xxxxxx 
+kubectl create secret docker-registry streamparksecret
+--docker-server=registry-vpc.cn-zhangjiakou.aliyuncs.com
+--docker-username=xxxxxx
 --docker-password=xxxxxx -n streamx```
 ```
 
@@ -283,9 +283,9 @@ kubectl create secret docker-registry streamparksecret
 创建 k8s secret 密钥用来拉取 ACR 中的镜像 streamparksecret 为密钥名称 自定义
 
 ```shell
-kubectl create secret docker-registry streamparksecret 
---docker-server=registry-vpc.cn-zhangjiakou.aliyuncs.com 
---docker-username=xxxxxx 
+kubectl create secret docker-registry streamparksecret
+--docker-server=registry-vpc.cn-zhangjiakou.aliyuncs.com
+--docker-username=xxxxxx
 --docker-password=xxxxxx -n streamx
 ```
 
@@ -312,13 +312,13 @@ kubectl -f rbac.yaml
 kubectl -f oss-plugin.yaml
 ```
 
-\- 创建 CP&SP 的 PV 
+\- 创建 CP&SP 的 PV
 
 ```shell
 kubectl -f checkpoints_pv.yaml kubectl -f savepoints_pv.yaml
 ```
 
-\- 创建 CP&SP 的 PVC 
+\- 创建 CP&SP 的 PVC
 
 ```shell
 kubectl -f checkpoints_pvc.yaml kubectl -f savepoints_pvc.yaml
@@ -336,7 +336,7 @@ kubectl -f checkpoints_pvc.yaml kubectl -f 
savepoints_pvc.yaml
 
 ```sql
 SET 'execution.runtime-mode' = 'streaming';
-set 'table.exec.sink.upsert-materialize' = 'none'; 
+set 'table.exec.sink.upsert-materialize' = 'none';
 SET 'sql-client.execution.result-mode' = 'tableau';
 -- 创建并使用 FTS Catalog 底层存储方案采用阿里云oss
 CREATE CATALOG `table_store` WITH (
@@ -368,7 +368,7 @@ Kubernetes Namespace :streamx
 Kubernetes ClusterId :(任务名自定义即可)
 
 #上传到阿里云镜像仓库的基础镜像
-Flink Base Docker Image 
:registry-vpc.cn-zhangjiakou.aliyuncs.com/xxxxx/flink-table-store:v1.16.0    
+Flink Base Docker Image 
:registry-vpc.cn-zhangjiakou.aliyuncs.com/xxxxx/flink-table-store:v1.16.0
 
 Rest-Service Exposed Type:NodePort
 
@@ -408,7 +408,7 @@ volumes:
     persistentVolumeClaim:
       claimName: flink-savepoints-csi-pvc
 
-imagePullSecrets:      
+imagePullSecrets:
 - name: streamparksecret
 ```
 
@@ -675,7 +675,7 @@ CREATE TABLE IF NOT EXISTS dwm.`dwm_business_order_count` (
 `delivery_center_op_name` varchar(200) NOT NULL COMMENT '交付名称',
 `sum_orderCount` BIGINT NOT NULL COMMENT '订单数',
 `create_date` timestamp NOT NULL COMMENT '创建时间',
-PRIMARY KEY (`l_year`, 
+PRIMARY KEY (`l_year`,
              `l_month`,
              `l_date`,
              `order_type_name`,
@@ -805,12 +805,12 @@ ADS 层聚合表采用 agg sum 会出现 dwd 数据流不产生 update_before,
 
 解决办法:
 
-By specifying 'changelog-producer' = 'full-compaction', 
-Table Store will compare the results between full compactions and produce the 
differences as changelog. 
+By specifying 'changelog-producer' = 'full-compaction',
+Table Store will compare the results between full compactions and produce the 
differences as changelog.
 The latency of changelog is affected by the frequency of full compactions.
 
-By specifying changelog-producer.compaction-interval table property (default 
value 30min), 
-users can define the maximum interval between two full compactions to ensure 
latency. 
+By specifying changelog-producer.compaction-interval table property (default 
value 30min),
+users can define the maximum interval between two full compactions to ensure 
latency.
 This table property does not affect normal compactions and they may still be 
performed once in a while by writers to reduce reader costs.
 
 这样能解决上述问题。但是随之而来出现了新的问题。默认 changelog-producer.compaction-interval 是 30min,意味着 
上游的改动到 ads 查询要间隔 30min,生产过程中发现将压缩间隔时间改成 1min 或者 2 分钟的情况下,又会出现上述 ADS 层聚合数据不准的情况。
diff --git a/blog/4-streampark-usercase-shunwang.md 
b/blog/4-streampark-usercase-shunwang.md
index 410f162f..cca5c22f 100644
--- a/blog/4-streampark-usercase-shunwang.md
+++ b/blog/4-streampark-usercase-shunwang.md
@@ -4,8 +4,6 @@ title: StreamPark 在顺网科技的大规模生产实践
 tags: [StreamPark, 生产实践, FlinkSQL]
 ---
 
-# StreamPark 在顺网科技的大规模生产实践
-
 ![](/blog/shunwang/autor.png)
 
 **导读:**本文主要介绍顺网科技在使用 Flink 计算引擎中遇到的一些挑战,基于 StreamPark 
作为实时数据平台如何来解决这些问题,从而大规模支持公司的业务。
@@ -18,6 +16,8 @@ tags: [StreamPark, 生产实践, FlinkSQL]
 - 带来的收益
 - 未来规划
 
+<!-- truncate -->
+
 ## **公司业务介绍**
 
 杭州顺网科技股份有限公司成立于 2005 
年,秉承科技连接快乐的企业使命,是国内具有影响力的泛娱乐技术服务平台之一。多年来公司始终以产品和技术为驱动,致力于以数字化平台服务为人们创造沉浸式的全场景娱乐体验。
@@ -189,7 +189,7 @@ https://github.com/apache/streampark/issues/2142
 
 
 
-## 带来的收益 
+## 带来的收益
 
 我们从 StreamX 1.2.3(StreamPark 前身)开始探索和使用,经过一年多时间的磨合,我们发现 StreamPark 真实解决了 Flink 
作业在开发管理和运维上的诸多痛点。
 
@@ -201,7 +201,7 @@ StreamPark 给顺网科技带来的最大的收益就是降低了 Flink 的使
 
 ![图片](/blog/shunwang/achievements2.png)
 
-##  未 来 规 划 
+##  未 来 规 划
 
 顺网科技作为 StreamPark 早期的用户之一,在 1 年期间内一直和社区同学保持交流,参与 StreamPark 的稳定性打磨,我们将生产运维中遇到的 
Bug 和新的 Feature 提交给了社区。在未来,我们希望可以在 StreamPark 上管理 Flink 表的元数据信息,基于 Flink 引擎通过多 
Catalog 实现跨数据源查询分析功能。目前 StreamPark 正在对接 Flink-SQL-Gateway 
能力,这一块在未来对于表元数据的管理和跨数据源查询功能会提供了很大的帮助。
 
diff --git a/blog/5-streampark-usercase-dustess.md 
b/blog/5-streampark-usercase-dustess.md
index 81fe7752..bec4373a 100644
--- a/blog/5-streampark-usercase-dustess.md
+++ b/blog/5-streampark-usercase-dustess.md
@@ -4,8 +4,6 @@ title: StreamPark 在尘锋信息的最佳实践,化繁为简极致体验
 tags: [StreamPark, 生产实践, FlinkSQL]
 ---
 
-# StreamPark 在尘锋信息的最佳实践,化繁为简极致体验
-
 **摘要:**本文源自 StreamPark 在尘锋信息的生产实践, 作者是资深数据开发工程师Gump。主要内容为:
 
 1. 技术选型
@@ -18,7 +16,9 @@ tags: [StreamPark, 生产实践, FlinkSQL]
 
 目前,尘锋已在全国拥有13个城市中心,覆盖华北、华中、华东、华南、西南五大区域,为超30个行业的10,000+家企业提供数字营销服务。
 
-## **01 技术选型** 
+<!-- truncate -->
+
+## **01 技术选型**
 
 尘锋信息在2021年进入了快速发展时期,随着服务行业和企业客户的增加,实时需求越来越多,落地实时计算平台迫在眉睫。
 
@@ -26,7 +26,7 @@ tags: [StreamPark, 生产实践, FlinkSQL]
 
 - 快:由于业务紧迫,我们需要快速落地规划的技术选型并运用生产
 - 稳:满足快的基础上,所选择技术一定要稳定服务业务
-- 新:在以上基础,所选择的技术也尽量的新  
+- 新:在以上基础,所选择的技术也尽量的新
 - 全:所选择技术能够满足公司快速发展和变化的业务,能够符合团队长期发展目标,能够支持且快速支持二次开发
 
 首先在计算引擎方面:我们选择 Flink,原因如下:
@@ -67,9 +67,9 @@ Flink SQL 可以极大提升开发效率和提高 Flink 的普及。StreamPark 
 
 
 
-Flink SQL 现在虽然足够强大,但使用 Java 和 Scala 等 JVM 语言开发 Flink 
任务会更加灵活、定制化更强、便于调优和提升资源利用率。与 SQL 相比 Jar 
包提交任务最大的问题是Jar包的上传管理等,没有优秀的工具产品会严重降低开发效率和加大维护成本。 
+Flink SQL 现在虽然足够强大,但使用 Java 和 Scala 等 JVM 语言开发 Flink 
任务会更加灵活、定制化更强、便于调优和提升资源利用率。与 SQL 相比 Jar 
包提交任务最大的问题是Jar包的上传管理等,没有优秀的工具产品会严重降低开发效率和加大维护成本。
 
-StreamPark 除了支持 Jar 上传,更提供了**在线更新构建**的功能,优雅解决了以上问题:  
+StreamPark 除了支持 Jar 上传,更提供了**在线更新构建**的功能,优雅解决了以上问题:
 
 1、新建 Project :填写 GitHub/Gitlab(支持企业私服)地址及用户名密码, StreamPark 就能 Pull 和 Build 项目。
 
@@ -158,7 +158,7 @@ StreamPark 的环境搭建非常简单,跟随官网的搭建教程可以在小
 http://www.streamxhub.com/docs/user-guide/deployment
 ```
 
-为了快速落地和生产使用,我们选择了稳妥的 On Yarn 资源管理模式(虽然 StreamPark 已经很完善的支持 K8S),且已经有较多公司通过 
StreamPark 落地了 K8S 部署方式,大家可以参考: 
+为了快速落地和生产使用,我们选择了稳妥的 On Yarn 资源管理模式(虽然 StreamPark 已经很完善的支持 K8S),且已经有较多公司通过 
StreamPark 落地了 K8S 部署方式,大家可以参考:
 
 ```
 http://www.streamxhub.com/blog/flink-development-framework-streamx
@@ -194,11 +194,11 @@ StreamPark 非常贴心的准备了 Demo SQL 任务,可以直接在刚搭建
 StreamingContext = ParameterTool + StreamExecutionEnvironment
 ```
 
-- StreamingContext 为 StreamPark 的封装对象 
-- ParameterTool 为解析配置文件后的参数对象 
+- StreamingContext 为 StreamPark 的封装对象
+- ParameterTool 为解析配置文件后的参数对象
 
 ```
- String value = ParameterTool.get("${user.custom.key}") 
+ String value = ParameterTool.get("${user.custom.key}")
 ```
 
 - StreamExecutionEnvironment 为 Apache Flink 原生任务上下文
@@ -223,13 +223,13 @@ StreamingContext = ParameterTool + 
StreamExecutionEnvironment
 - 计算能力开放:将大数据平台的服务器资源开放业务团队使用
 - 解决方案开放:Flink 生态的成熟 Connector、Exactly Once 语义支持,可减少业务团队流处理相关的开发成本及维护成本
 
-目前 StreamPark 还不支持多业务组功能,多业务组功能会抽象后贡献社区。   
+目前 StreamPark 还不支持多业务组功能,多业务组功能会抽象后贡献社区。
 
-![](/blog/dustess/manager.png)        
+![](/blog/dustess/manager.png)
 
 ![](/blog/dustess/task_retrieval.png)
 
-## **04 未来规划**   
+## **04 未来规划**
 
 ### **01 Flink on K8S**
 
diff --git a/blog/6-streampark-usercase-joyme.md 
b/blog/6-streampark-usercase-joyme.md
index ed4174ce..f983b3a1 100644
--- a/blog/6-streampark-usercase-joyme.md
+++ b/blog/6-streampark-usercase-joyme.md
@@ -4,8 +4,6 @@ title: StreamPark 在 Joyme 的生产实践
 tags: [StreamPark, 生产实践, FlinkSQL]
 ---
 
-<br/>
-
 **摘要:** 本文带来 StreamPark 在 Joyme 中的生产实践, 作者是 Joyme 的大数据工程师秦基勇, 主要内容为:
 
 - 遇见StreamPark
@@ -16,6 +14,8 @@ tags: [StreamPark, 生产实践, FlinkSQL]
 - 社区印象
 - 总结
 
+<!-- truncate -->
+
 ## 1 遇见 StreamPark
 
 遇见 StreamPark 是必然的,基于我们现有的实时作业开发模式,不得不寻找一个开源的平台来支撑我司的实时业务。我们的现状如下:
@@ -55,7 +55,7 @@ CREATE TABLE source_table (
 'format.derive-schema' = 'true'
 );
 
--- 落地表sink 
+-- 落地表sink
 CREATE TABLE sink_table (
 `uid` STRING
 ) WITH (
@@ -92,9 +92,9 @@ SELECT  Data.uid  FROM source_table;
 
 由于我们的模式部署是 on Yarn,在动态选项配置里配置了 Yarn 的队列名称。也有一些配置了开启增量的 Checkpoint 
选项和状态过期时间,基本的这些参数都可以从 Flink 
的官网去查询到。之前有一些作业确实经常出现内存溢出的问题,加上增量参数和过期参数以后,作业的运行情况好多了。还有就是 Flink Sql 
作业设计到状态这种比较大和逻辑复杂的情况下,我个人感觉还是用 Streaming 代码来实现比较好控制一些。
 
-- -Dyarn.application.queue= yarn队列名称 
-- -Dstate.backend.incremental=true 
-- -Dtable.exec.state.ttl=过期时间 
+- -Dyarn.application.queue= yarn队列名称
+- -Dstate.backend.incremental=true
+- -Dtable.exec.state.ttl=过期时间
 
 完成配置以后提交,然后在 application 界面进行部署。
 
@@ -158,4 +158,4 @@ StreamPark 的监控需要在 setting 模块去配置发送邮件的基本信息
 
 目前我司线上运行 60 个实时作业,Flink sql 与 Custom-code 差不多各一半。后续也会有更多的实时任务进行上线。很多同学都会担心 
StreamPark 稳不稳定的问题,就我司根据几个月的生产实践而言,StreamPark 
只是一个帮助你开发作业,部署,监控和管理的一个平台。到底稳不稳,还是要看自家的 Hadoop yarn 集群稳不稳定(我们用的onyan模式),其实已经跟 
StreamPark关系不大了。还有就是你写的 Flink Sql 或者是代码健不健壮。更多的是这两方面应该是大家要考虑的,这两方面没问题再充分利用 
StreamPark 的灵活性才能让作业更好的运行,单从一方面说 StreamPark 稳不稳定,实属偏激。
 
-以上就是 StreamPark 在乐我无限的全部分享内容,感谢大家看到这里。非常感谢 StreamPark 
提供给我们这么优秀的产品,这就是做的利他人之事。从1.0 到 1.2.1 平时遇到那些bug都会被即时的修复,每一个issue都被认真对待。目前我们还是 
onyarn的部署模式,重启yarn还是会导致作业的lost状态,重启yarn也不是天天都干的事,关于这个社区也会尽早的会去修复这个问题。相信 
StreamPark 会越来越好,未来可期。
\ No newline at end of file
+以上就是 StreamPark 在乐我无限的全部分享内容,感谢大家看到这里。非常感谢 StreamPark 
提供给我们这么优秀的产品,这就是做的利他人之事。从1.0 到 1.2.1 平时遇到那些bug都会被即时的修复,每一个issue都被认真对待。目前我们还是 
onyarn的部署模式,重启yarn还是会导致作业的lost状态,重启yarn也不是天天都干的事,关于这个社区也会尽早的会去修复这个问题。相信 
StreamPark 会越来越好,未来可期。
diff --git a/blog/7-streampark-usercase-haibo.md 
b/blog/7-streampark-usercase-haibo.md
index 821cacd3..6e80ef2c 100644
--- a/blog/7-streampark-usercase-haibo.md
+++ b/blog/7-streampark-usercase-haibo.md
@@ -4,7 +4,6 @@ title: StreamPark 一站式计算利器在海博科技的生产实践,助力
 tags: [StreamPark, 生产实践, FlinkSQL]
 ---
 
-# StreamPark 一站式计算利器在海博科技的生产实践,助力智慧城市建设
 
 **摘要:**本文「 StreamPark 一站式计算利器在海博科技的生产实践,助力智慧城市建设 」作者是海博科技大数据架构师王庆焕,主要内容为:
 
@@ -16,6 +15,9 @@ tags: [StreamPark, 生产实践, FlinkSQL]
 
 
海博科技是一家行业领先的人工智能物联网产品和解决方案公司。目前在公共安全、智慧城市、智慧制造领域,为全国客户提供包括算法、软件和硬件产品在内的全栈式整体解决方案。
 
+<!-- truncate -->
+
+
 ## **01. 选择 StreamPark**
 
 海博科技自 2020 年开始使用 Flink SQL 
汇聚、处理各类实时物联数据。随着各地市智慧城市建设步伐的加快,需要汇聚的各类物联数据的数据种类、数据量也不断增加,导致线上维护的 Flink SQL 
任务越来越多,一个专门的能够管理众多 Flink SQL 任务的计算平台成为了迫切的需求。
@@ -52,7 +54,7 @@ StreamPark 在海博主要用于运行实时 Flink SQL任务: 读取 Kafka 上
 
 从2021年10月开始,公司逐渐将 Flink SQL 任务迁移至 StreamPark 平台来集中管理,承载我司实时物联数据的汇聚、计算、预警。
 
-截至目前,StreamPark 已在多个政府、公安生产环境进行部署,汇聚处理城市实时物联数据、人车抓拍数据。以下是在某市专网部署的 StreamPark 
平台截图 : 
+截至目前,StreamPark 已在多个政府、公安生产环境进行部署,汇聚处理城市实时物联数据、人车抓拍数据。以下是在某市专网部署的 StreamPark 
平台截图 :
 
 ![](/blog/haibo/application.png)
 
diff --git a/docusaurus.config.js b/docusaurus.config.js
index 43f6d3bc..2ebdb84d 100644
--- a/docusaurus.config.js
+++ b/docusaurus.config.js
@@ -76,11 +76,12 @@ const config = {
 
         blog: {
           blogSidebarCount: 15,
-          postsPerPage: 5,
+          postsPerPage: 6,
           showReadingTime: true,
+          blogSidebarTitle: "近期文章",
           // Please change this to your repo.
           editUrl:
-            'https://github.com/apache/incubator-streampark-website/edit/dev/',
+            'https://github.com/apache/incubator-streampark-website/edit/dev/'
         },
         theme: {
           customCss: require.resolve('./src/css/custom.css'),
diff --git a/i18n/zh-CN/code.json b/i18n/zh-CN/code.json
new file mode 100644
index 00000000..e7c2eea5
--- /dev/null
+++ b/i18n/zh-CN/code.json
@@ -0,0 +1,6 @@
+{
+  "theme.blog.newerPost":{
+    "message": "最新博客",
+    "description": "latest blogs heading"
+  }
+}
diff --git 
a/i18n/zh-CN/docusaurus-plugin-content-blog/0-streampark-flink-on-k8s.md 
b/i18n/zh-CN/docusaurus-plugin-content-blog/0-streampark-flink-on-k8s.md
index ccea49e5..4e9096c8 100644
--- a/i18n/zh-CN/docusaurus-plugin-content-blog/0-streampark-flink-on-k8s.md
+++ b/i18n/zh-CN/docusaurus-plugin-content-blog/0-streampark-flink-on-k8s.md
@@ -4,10 +4,10 @@ title: StreamPark Flink on Kubernetes 实践
 tags: [StreamPark, 生产实践, FlinkSQL, Kubernetes]
 ---
 
-# StreamPark Flink on Kubernetes 实践
-
 雾芯科技创立于2018年1月。目前主营业务包括 RELX 悦刻品牌产品的研发、设计、制造及销售。凭借覆盖全产业链的核心技术与能力,RELX 
悦刻致力于为用户提供兼具高品质和安全性的产品。
 
+<!-- truncate -->
+
 ## **为什么选择 Native Kubernetes**
 
 Native Kubernetes 具有以下优势:
@@ -55,15 +55,15 @@ COPY my-flink-job.jar $FLINK_HOME/usrlib/my-flink-job.jar
 5. 使用 Kubectl 命令获取到 Flink 作业的 WebUI 访问地址和 JobId
 
 ```shell
-kubectl -n flink-cluster get svc 
+kubectl -n flink-cluster get svc
 ```
 
 6. 使用Flink命令停止作业
 
 ```shell
-./bin/flink cancel 
-    --target kubernetes-application 
-    -Dkubernetes.cluster-id=my-first-application-cluster 
+./bin/flink cancel
+    --target kubernetes-application
+    -Dkubernetes.cluster-id=my-first-application-cluster
     -Dkubernetes.namespace=flink-cluster <jobId>
 ```
 
@@ -180,11 +180,11 @@ StreamPark 既支持 Upload Jar 也支持直接编写 Flink SQL 作业, **Flink
 
 ## **StreamPark 在雾芯科技的落地实践**
 
-StreamPark 在雾芯科技落地较晚,目前主要用于实时数据集成作业和实时指标计算作业的开发部署,有 Jar 任务也有 Flink SQL 任务,全部使用 
Native Kubernetes 部署;数据源有CDC、Kafka 等,Sink 端有 Maxcompute、kafka、Hive 
等,以下是公司开发环境StreamPark 平台截图: 
+StreamPark 在雾芯科技落地较晚,目前主要用于实时数据集成作业和实时指标计算作业的开发部署,有 Jar 任务也有 Flink SQL 任务,全部使用 
Native Kubernetes 部署;数据源有CDC、Kafka 等,Sink 端有 Maxcompute、kafka、Hive 
等,以下是公司开发环境StreamPark 平台截图:
 
 ![](/blog/relx/screenshot.png)
 
-## 遇到的问题  
+## 遇到的问题
 
 任何新技术都有探索与踩坑的过程,失败的经验是宝贵的,这里介绍下 StreamPark 在雾芯科技落地过程中踩的一些坑和经验,**这块的内容不仅仅关于 
StreamPark 的部分, 相信会带给所有使用 Flink on Kubernetes 的小伙伴一些参考。
 
@@ -242,7 +242,7 @@ spec:
   serviceAccount: default
   containers:
   - name: flink-main-container
-    image: 
+    image:
   imagePullSecrets:
   - name: regsecret
   hostAliases:
@@ -289,14 +289,14 @@ FROM flink:1.13.6-scala_2.11COPY lib $FLINK_HOME/lib/
 **3. 基础镜像制作并推送私有仓库**
 
 ```shell
-docker login --username=xxxdocker \ 
+docker login --username=xxxdocker \
 build -t udf_flink_1.13.6-scala_2.11:latest \
 .docker tag udf_flink_1.13.6-scala_2.11:latest \
 k8s-harbor.xxx.com/streamx/udf_flink_1.13.6-scala_2.11:latestdocker \
 push k8s-harbor.xxx.com/streamx/udf_flink_1.13.6-scala_2.11:latest
 ```
 
-##  **未来期待**   
+##  **未来期待**
 
 - **StreamPark 对于 Flink 作业 Metric 监控的支持**
 
@@ -304,7 +304,7 @@ StreamPark 如果可以对接 Flink Metric 数据而且可以在 StreamPark 平
 
 - **StreamPark 对于Flink 作业日志持久化的支持**
 
-对于部署到 YARN 的 Flink 来说,如果 Flink 程序挂了,我们可以去 YARN 上看历史日志,但是对于 Kubernetes 
来说,如果程序挂了,那么 Kubernetes 的 pod 就消失了,就没法查日志了。所以用户需要借助 Kubernetes 上的工具进行日志持久化,如果 
StreamPark 支持 Kubernetes 日志持久化接口就更好了。 
+对于部署到 YARN 的 Flink 来说,如果 Flink 程序挂了,我们可以去 YARN 上看历史日志,但是对于 Kubernetes 
来说,如果程序挂了,那么 Kubernetes 的 pod 就消失了,就没法查日志了。所以用户需要借助 Kubernetes 上的工具进行日志持久化,如果 
StreamPark 支持 Kubernetes 日志持久化接口就更好了。
 
 - **镜像过大的问题改进**
 
diff --git 
a/i18n/zh-CN/docusaurus-plugin-content-blog/1-flink-framework-streampark.md 
b/i18n/zh-CN/docusaurus-plugin-content-blog/1-flink-framework-streampark.md
index dcd97dd7..3604cdf9 100644
--- a/i18n/zh-CN/docusaurus-plugin-content-blog/1-flink-framework-streampark.md
+++ b/i18n/zh-CN/docusaurus-plugin-content-blog/1-flink-framework-streampark.md
@@ -6,6 +6,12 @@ tags: [StreamPark, DataStream, FlinkSQL]
 
 <br/>
 
+Hadoop 
体系虽然在目前应用非常广泛,但架构繁琐、运维复杂度过高、版本升级困难,且由于部门原因,数据中台需求排期较长,我们急需探索敏捷性开发的数据平台模式。在目前云原生架构的普及和湖仓一体化的大背景下,我们已经确定了将
 Doris 作为离线数据仓库,将 TiDB(目前已经应用于生产)作为实时数据平台,同时因为 Doris 具有 on MySQL 的 ODBC 
能力,所以又可以对外部数据库资源进行整合,统一对外输出报表
+
+![](/blog/belle/doris.png)
+
+<!-- truncate -->
+
 # 1. 背景
 
 Hadoop 
体系虽然在目前应用非常广泛,但架构繁琐、运维复杂度过高、版本升级困难,且由于部门原因,数据中台需求排期较长,我们急需探索敏捷性开发的数据平台模式。在目前云原生架构的普及和湖仓一体化的大背景下,我们已经确定了将
 Doris 作为离线数据仓库,将 TiDB(目前已经应用于生产)作为实时数据平台,同时因为 Doris 具有 on MySQL 的 ODBC 
能力,所以又可以对外部数据库资源进行整合,统一对外输出报表
diff --git 
a/i18n/zh-CN/docusaurus-plugin-content-blog/2-streampark-usercase-chinaunion.md 
b/i18n/zh-CN/docusaurus-plugin-content-blog/2-streampark-usercase-chinaunion.md
index 98e5b3d7..ca77f998 100644
--- 
a/i18n/zh-CN/docusaurus-plugin-content-blog/2-streampark-usercase-chinaunion.md
+++ 
b/i18n/zh-CN/docusaurus-plugin-content-blog/2-streampark-usercase-chinaunion.md
@@ -13,6 +13,8 @@ tags: [StreamPark, 生产实践, FlinkSQL]
 - 基于 StreamPark 一体化管理
 - 未来规划与演进
 
+<!-- truncate -->
+
 ## **实时计算平台背景介绍**
 
 ![](/blog/chinaunion/overall_architecture.png)
diff --git 
a/i18n/zh-CN/docusaurus-plugin-content-blog/3-streampark-usercase-bondex-paimon.md
 
b/i18n/zh-CN/docusaurus-plugin-content-blog/3-streampark-usercase-bondex-paimon.md
index b7eb7f45..eb61a598 100644
--- 
a/i18n/zh-CN/docusaurus-plugin-content-blog/3-streampark-usercase-bondex-paimon.md
+++ 
b/i18n/zh-CN/docusaurus-plugin-content-blog/3-streampark-usercase-bondex-paimon.md
@@ -4,8 +4,6 @@ title: 海程邦达基于 Apache Paimon + StreamPark 的流式数仓实践
 tags: [StreamPark, 生产实践, paimon, streaming-warehouse]
 ---
 
-# 海程邦达基于 Apache Paimon + StreamPark 的流式数仓实践
-
 ![](/blog/bondex/Bondex.png)
 
 **导读:**本文主要介绍作为供应链物流服务商海程邦达在数字化转型过程中采用 Paimon + StreamPark 平台实现流式数仓的落地方案。我们以 
Apache StreamPark 流批一体平台提供了一个易于上手的生产操作手册,以帮助用户提交 Flink 任务并迅速掌握 Paimon 的使用方法。
@@ -16,6 +14,8 @@ tags: [StreamPark, 生产实践, paimon, streaming-warehouse]
 - 问题排查分析
 - 未来规划
 
+<!-- truncate -->
+
 ## 01 公司业务情况介绍
 
 海程邦达集团一直专注于供应链物流领域,通过打造优秀的国际化物流平台,为客户提供端到端一站式智慧型供应链物流服务。集团现有员工 2000 余人,年营业额逾 
120 亿人民币,网络遍及全球 200 余个港口,在海内外有超 80 家分、子公司,助力中国企业与世界互联互通。
@@ -262,9 +262,9 @@ kubectl create ns streamx
 使用 default 账户创建 clusterrolebinding 资源:
 
 ```shell
-kubectl create secret docker-registry streamparksecret 
---docker-server=registry-vpc.cn-zhangjiakou.aliyuncs.com 
---docker-username=xxxxxx 
+kubectl create secret docker-registry streamparksecret
+--docker-server=registry-vpc.cn-zhangjiakou.aliyuncs.com
+--docker-username=xxxxxx
 --docker-password=xxxxxx -n streamx```
 ```
 
@@ -283,9 +283,9 @@ kubectl create secret docker-registry streamparksecret
 创建 k8s secret 密钥用来拉取 ACR 中的镜像 streamparksecret 为密钥名称 自定义
 
 ```shell
-kubectl create secret docker-registry streamparksecret 
---docker-server=registry-vpc.cn-zhangjiakou.aliyuncs.com 
---docker-username=xxxxxx 
+kubectl create secret docker-registry streamparksecret
+--docker-server=registry-vpc.cn-zhangjiakou.aliyuncs.com
+--docker-username=xxxxxx
 --docker-password=xxxxxx -n streamx
 ```
 
@@ -312,13 +312,13 @@ kubectl -f rbac.yaml
 kubectl -f oss-plugin.yaml
 ```
 
-\- 创建 CP&SP 的 PV 
+\- 创建 CP&SP 的 PV
 
 ```shell
 kubectl -f checkpoints_pv.yaml kubectl -f savepoints_pv.yaml
 ```
 
-\- 创建 CP&SP 的 PVC 
+\- 创建 CP&SP 的 PVC
 
 ```shell
 kubectl -f checkpoints_pvc.yaml kubectl -f savepoints_pvc.yaml
@@ -336,7 +336,7 @@ kubectl -f checkpoints_pvc.yaml kubectl -f 
savepoints_pvc.yaml
 
 ```sql
 SET 'execution.runtime-mode' = 'streaming';
-set 'table.exec.sink.upsert-materialize' = 'none'; 
+set 'table.exec.sink.upsert-materialize' = 'none';
 SET 'sql-client.execution.result-mode' = 'tableau';
 -- 创建并使用 FTS Catalog 底层存储方案采用阿里云oss
 CREATE CATALOG `table_store` WITH (
@@ -368,7 +368,7 @@ Kubernetes Namespace :streamx
 Kubernetes ClusterId :(任务名自定义即可)
 
 #上传到阿里云镜像仓库的基础镜像
-Flink Base Docker Image 
:registry-vpc.cn-zhangjiakou.aliyuncs.com/xxxxx/flink-table-store:v1.16.0    
+Flink Base Docker Image 
:registry-vpc.cn-zhangjiakou.aliyuncs.com/xxxxx/flink-table-store:v1.16.0
 
 Rest-Service Exposed Type:NodePort
 
@@ -408,7 +408,7 @@ volumes:
     persistentVolumeClaim:
       claimName: flink-savepoints-csi-pvc
 
-imagePullSecrets:      
+imagePullSecrets:
 - name: streamparksecret
 ```
 
@@ -675,7 +675,7 @@ CREATE TABLE IF NOT EXISTS dwm.`dwm_business_order_count` (
 `delivery_center_op_name` varchar(200) NOT NULL COMMENT '交付名称',
 `sum_orderCount` BIGINT NOT NULL COMMENT '订单数',
 `create_date` timestamp NOT NULL COMMENT '创建时间',
-PRIMARY KEY (`l_year`, 
+PRIMARY KEY (`l_year`,
              `l_month`,
              `l_date`,
              `order_type_name`,
@@ -805,12 +805,12 @@ ADS 层聚合表采用 agg sum 会出现 dwd 数据流不产生 update_before,
 
 解决办法:
 
-By specifying 'changelog-producer' = 'full-compaction', 
-Table Store will compare the results between full compactions and produce the 
differences as changelog. 
+By specifying 'changelog-producer' = 'full-compaction',
+Table Store will compare the results between full compactions and produce the 
differences as changelog.
 The latency of changelog is affected by the frequency of full compactions.
 
-By specifying changelog-producer.compaction-interval table property (default 
value 30min), 
-users can define the maximum interval between two full compactions to ensure 
latency. 
+By specifying changelog-producer.compaction-interval table property (default 
value 30min),
+users can define the maximum interval between two full compactions to ensure 
latency.
 This table property does not affect normal compactions and they may still be 
performed once in a while by writers to reduce reader costs.
 
 这样能解决上述问题。但是随之而来出现了新的问题。默认 changelog-producer.compaction-interval 是 30min,意味着 
上游的改动到 ads 查询要间隔 30min,生产过程中发现将压缩间隔时间改成 1min 或者 2 分钟的情况下,又会出现上述 ADS 层聚合数据不准的情况。
diff --git 
a/i18n/zh-CN/docusaurus-plugin-content-blog/4-streampark-usercase-shunwang.md 
b/i18n/zh-CN/docusaurus-plugin-content-blog/4-streampark-usercase-shunwang.md
index 410f162f..776036fb 100644
--- 
a/i18n/zh-CN/docusaurus-plugin-content-blog/4-streampark-usercase-shunwang.md
+++ 
b/i18n/zh-CN/docusaurus-plugin-content-blog/4-streampark-usercase-shunwang.md
@@ -4,7 +4,6 @@ title: StreamPark 在顺网科技的大规模生产实践
 tags: [StreamPark, 生产实践, FlinkSQL]
 ---
 
-# StreamPark 在顺网科技的大规模生产实践
 
 ![](/blog/shunwang/autor.png)
 
@@ -18,6 +17,8 @@ tags: [StreamPark, 生产实践, FlinkSQL]
 - 带来的收益
 - 未来规划
 
+<!-- truncate -->
+
 ## **公司业务介绍**
 
 杭州顺网科技股份有限公司成立于 2005 
年,秉承科技连接快乐的企业使命,是国内具有影响力的泛娱乐技术服务平台之一。多年来公司始终以产品和技术为驱动,致力于以数字化平台服务为人们创造沉浸式的全场景娱乐体验。
@@ -189,7 +190,7 @@ https://github.com/apache/streampark/issues/2142
 
 
 
-## 带来的收益 
+## 带来的收益
 
 我们从 StreamX 1.2.3(StreamPark 前身)开始探索和使用,经过一年多时间的磨合,我们发现 StreamPark 真实解决了 Flink 
作业在开发管理和运维上的诸多痛点。
 
@@ -201,7 +202,7 @@ StreamPark 给顺网科技带来的最大的收益就是降低了 Flink 的使
 
 ![图片](/blog/shunwang/achievements2.png)
 
-##  未 来 规 划 
+##  未 来 规 划
 
 顺网科技作为 StreamPark 早期的用户之一,在 1 年期间内一直和社区同学保持交流,参与 StreamPark 的稳定性打磨,我们将生产运维中遇到的 
Bug 和新的 Feature 提交给了社区。在未来,我们希望可以在 StreamPark 上管理 Flink 表的元数据信息,基于 Flink 引擎通过多 
Catalog 实现跨数据源查询分析功能。目前 StreamPark 正在对接 Flink-SQL-Gateway 
能力,这一块在未来对于表元数据的管理和跨数据源查询功能会提供了很大的帮助。
 
diff --git 
a/i18n/zh-CN/docusaurus-plugin-content-blog/5-streampark-usercase-dustess.md 
b/i18n/zh-CN/docusaurus-plugin-content-blog/5-streampark-usercase-dustess.md
index 81fe7752..bec4373a 100644
--- a/i18n/zh-CN/docusaurus-plugin-content-blog/5-streampark-usercase-dustess.md
+++ b/i18n/zh-CN/docusaurus-plugin-content-blog/5-streampark-usercase-dustess.md
@@ -4,8 +4,6 @@ title: StreamPark 在尘锋信息的最佳实践,化繁为简极致体验
 tags: [StreamPark, 生产实践, FlinkSQL]
 ---
 
-# StreamPark 在尘锋信息的最佳实践,化繁为简极致体验
-
 **摘要:**本文源自 StreamPark 在尘锋信息的生产实践, 作者是资深数据开发工程师Gump。主要内容为:
 
 1. 技术选型
@@ -18,7 +16,9 @@ tags: [StreamPark, 生产实践, FlinkSQL]
 
 目前,尘锋已在全国拥有13个城市中心,覆盖华北、华中、华东、华南、西南五大区域,为超30个行业的10,000+家企业提供数字营销服务。
 
-## **01 技术选型** 
+<!-- truncate -->
+
+## **01 技术选型**
 
 尘锋信息在2021年进入了快速发展时期,随着服务行业和企业客户的增加,实时需求越来越多,落地实时计算平台迫在眉睫。
 
@@ -26,7 +26,7 @@ tags: [StreamPark, 生产实践, FlinkSQL]
 
 - 快:由于业务紧迫,我们需要快速落地规划的技术选型并运用生产
 - 稳:满足快的基础上,所选择技术一定要稳定服务业务
-- 新:在以上基础,所选择的技术也尽量的新  
+- 新:在以上基础,所选择的技术也尽量的新
 - 全:所选择技术能够满足公司快速发展和变化的业务,能够符合团队长期发展目标,能够支持且快速支持二次开发
 
 首先在计算引擎方面:我们选择 Flink,原因如下:
@@ -67,9 +67,9 @@ Flink SQL 可以极大提升开发效率和提高 Flink 的普及。StreamPark 
 
 
 
-Flink SQL 现在虽然足够强大,但使用 Java 和 Scala 等 JVM 语言开发 Flink 
任务会更加灵活、定制化更强、便于调优和提升资源利用率。与 SQL 相比 Jar 
包提交任务最大的问题是Jar包的上传管理等,没有优秀的工具产品会严重降低开发效率和加大维护成本。 
+Flink SQL 现在虽然足够强大,但使用 Java 和 Scala 等 JVM 语言开发 Flink 
任务会更加灵活、定制化更强、便于调优和提升资源利用率。与 SQL 相比 Jar 
包提交任务最大的问题是Jar包的上传管理等,没有优秀的工具产品会严重降低开发效率和加大维护成本。
 
-StreamPark 除了支持 Jar 上传,更提供了**在线更新构建**的功能,优雅解决了以上问题:  
+StreamPark 除了支持 Jar 上传,更提供了**在线更新构建**的功能,优雅解决了以上问题:
 
 1、新建 Project :填写 GitHub/Gitlab(支持企业私服)地址及用户名密码, StreamPark 就能 Pull 和 Build 项目。
 
@@ -158,7 +158,7 @@ StreamPark 的环境搭建非常简单,跟随官网的搭建教程可以在小
 http://www.streamxhub.com/docs/user-guide/deployment
 ```
 
-为了快速落地和生产使用,我们选择了稳妥的 On Yarn 资源管理模式(虽然 StreamPark 已经很完善的支持 K8S),且已经有较多公司通过 
StreamPark 落地了 K8S 部署方式,大家可以参考: 
+为了快速落地和生产使用,我们选择了稳妥的 On Yarn 资源管理模式(虽然 StreamPark 已经很完善的支持 K8S),且已经有较多公司通过 
StreamPark 落地了 K8S 部署方式,大家可以参考:
 
 ```
 http://www.streamxhub.com/blog/flink-development-framework-streamx
@@ -194,11 +194,11 @@ StreamPark 非常贴心的准备了 Demo SQL 任务,可以直接在刚搭建
 StreamingContext = ParameterTool + StreamExecutionEnvironment
 ```
 
-- StreamingContext 为 StreamPark 的封装对象 
-- ParameterTool 为解析配置文件后的参数对象 
+- StreamingContext 为 StreamPark 的封装对象
+- ParameterTool 为解析配置文件后的参数对象
 
 ```
- String value = ParameterTool.get("${user.custom.key}") 
+ String value = ParameterTool.get("${user.custom.key}")
 ```
 
 - StreamExecutionEnvironment 为 Apache Flink 原生任务上下文
@@ -223,13 +223,13 @@ StreamingContext = ParameterTool + 
StreamExecutionEnvironment
 - 计算能力开放:将大数据平台的服务器资源开放业务团队使用
 - 解决方案开放:Flink 生态的成熟 Connector、Exactly Once 语义支持,可减少业务团队流处理相关的开发成本及维护成本
 
-目前 StreamPark 还不支持多业务组功能,多业务组功能会抽象后贡献社区。   
+目前 StreamPark 还不支持多业务组功能,多业务组功能会抽象后贡献社区。
 
-![](/blog/dustess/manager.png)        
+![](/blog/dustess/manager.png)
 
 ![](/blog/dustess/task_retrieval.png)
 
-## **04 未来规划**   
+## **04 未来规划**
 
 ### **01 Flink on K8S**
 
diff --git 
a/i18n/zh-CN/docusaurus-plugin-content-blog/6-streampark-usercase-joyme.md 
b/i18n/zh-CN/docusaurus-plugin-content-blog/6-streampark-usercase-joyme.md
index ed4174ce..f983b3a1 100644
--- a/i18n/zh-CN/docusaurus-plugin-content-blog/6-streampark-usercase-joyme.md
+++ b/i18n/zh-CN/docusaurus-plugin-content-blog/6-streampark-usercase-joyme.md
@@ -4,8 +4,6 @@ title: StreamPark 在 Joyme 的生产实践
 tags: [StreamPark, 生产实践, FlinkSQL]
 ---
 
-<br/>
-
 **摘要:** 本文带来 StreamPark 在 Joyme 中的生产实践, 作者是 Joyme 的大数据工程师秦基勇, 主要内容为:
 
 - 遇见StreamPark
@@ -16,6 +14,8 @@ tags: [StreamPark, 生产实践, FlinkSQL]
 - 社区印象
 - 总结
 
+<!-- truncate -->
+
 ## 1 遇见 StreamPark
 
 遇见 StreamPark 是必然的,基于我们现有的实时作业开发模式,不得不寻找一个开源的平台来支撑我司的实时业务。我们的现状如下:
@@ -55,7 +55,7 @@ CREATE TABLE source_table (
 'format.derive-schema' = 'true'
 );
 
--- 落地表sink 
+-- 落地表sink
 CREATE TABLE sink_table (
 `uid` STRING
 ) WITH (
@@ -92,9 +92,9 @@ SELECT  Data.uid  FROM source_table;
 
 由于我们的模式部署是 on Yarn,在动态选项配置里配置了 Yarn 的队列名称。也有一些配置了开启增量的 Checkpoint 
选项和状态过期时间,基本的这些参数都可以从 Flink 
的官网去查询到。之前有一些作业确实经常出现内存溢出的问题,加上增量参数和过期参数以后,作业的运行情况好多了。还有就是 Flink Sql 
作业设计到状态这种比较大和逻辑复杂的情况下,我个人感觉还是用 Streaming 代码来实现比较好控制一些。
 
-- -Dyarn.application.queue= yarn队列名称 
-- -Dstate.backend.incremental=true 
-- -Dtable.exec.state.ttl=过期时间 
+- -Dyarn.application.queue= yarn队列名称
+- -Dstate.backend.incremental=true
+- -Dtable.exec.state.ttl=过期时间
 
 完成配置以后提交,然后在 application 界面进行部署。
 
@@ -158,4 +158,4 @@ StreamPark 的监控需要在 setting 模块去配置发送邮件的基本信息
 
 目前我司线上运行 60 个实时作业,Flink sql 与 Custom-code 差不多各一半。后续也会有更多的实时任务进行上线。很多同学都会担心 
StreamPark 稳不稳定的问题,就我司根据几个月的生产实践而言,StreamPark 
只是一个帮助你开发作业,部署,监控和管理的一个平台。到底稳不稳,还是要看自家的 Hadoop yarn 集群稳不稳定(我们用的onyan模式),其实已经跟 
StreamPark关系不大了。还有就是你写的 Flink Sql 或者是代码健不健壮。更多的是这两方面应该是大家要考虑的,这两方面没问题再充分利用 
StreamPark 的灵活性才能让作业更好的运行,单从一方面说 StreamPark 稳不稳定,实属偏激。
 
-以上就是 StreamPark 在乐我无限的全部分享内容,感谢大家看到这里。非常感谢 StreamPark 
提供给我们这么优秀的产品,这就是做的利他人之事。从1.0 到 1.2.1 平时遇到那些bug都会被即时的修复,每一个issue都被认真对待。目前我们还是 
onyarn的部署模式,重启yarn还是会导致作业的lost状态,重启yarn也不是天天都干的事,关于这个社区也会尽早的会去修复这个问题。相信 
StreamPark 会越来越好,未来可期。
\ No newline at end of file
+以上就是 StreamPark 在乐我无限的全部分享内容,感谢大家看到这里。非常感谢 StreamPark 
提供给我们这么优秀的产品,这就是做的利他人之事。从1.0 到 1.2.1 平时遇到那些bug都会被即时的修复,每一个issue都被认真对待。目前我们还是 
onyarn的部署模式,重启yarn还是会导致作业的lost状态,重启yarn也不是天天都干的事,关于这个社区也会尽早的会去修复这个问题。相信 
StreamPark 会越来越好,未来可期。
diff --git 
a/i18n/zh-CN/docusaurus-plugin-content-blog/7-streampark-usercase-haibo.md 
b/i18n/zh-CN/docusaurus-plugin-content-blog/7-streampark-usercase-haibo.md
index 821cacd3..8a5e0041 100644
--- a/i18n/zh-CN/docusaurus-plugin-content-blog/7-streampark-usercase-haibo.md
+++ b/i18n/zh-CN/docusaurus-plugin-content-blog/7-streampark-usercase-haibo.md
@@ -4,8 +4,6 @@ title: StreamPark 一站式计算利器在海博科技的生产实践,助力
 tags: [StreamPark, 生产实践, FlinkSQL]
 ---
 
-# StreamPark 一站式计算利器在海博科技的生产实践,助力智慧城市建设
-
 **摘要:**本文「 StreamPark 一站式计算利器在海博科技的生产实践,助力智慧城市建设 」作者是海博科技大数据架构师王庆焕,主要内容为:
 
 1. 选择 StreamPark
@@ -16,6 +14,8 @@ tags: [StreamPark, 生产实践, FlinkSQL]
 
 
海博科技是一家行业领先的人工智能物联网产品和解决方案公司。目前在公共安全、智慧城市、智慧制造领域,为全国客户提供包括算法、软件和硬件产品在内的全栈式整体解决方案。
 
+<!-- truncate -->
+
 ## **01. 选择 StreamPark**
 
 海博科技自 2020 年开始使用 Flink SQL 
汇聚、处理各类实时物联数据。随着各地市智慧城市建设步伐的加快,需要汇聚的各类物联数据的数据种类、数据量也不断增加,导致线上维护的 Flink SQL 
任务越来越多,一个专门的能够管理众多 Flink SQL 任务的计算平台成为了迫切的需求。
@@ -52,7 +52,7 @@ StreamPark 在海博主要用于运行实时 Flink SQL任务: 读取 Kafka 上
 
 从2021年10月开始,公司逐渐将 Flink SQL 任务迁移至 StreamPark 平台来集中管理,承载我司实时物联数据的汇聚、计算、预警。
 
-截至目前,StreamPark 已在多个政府、公安生产环境进行部署,汇聚处理城市实时物联数据、人车抓拍数据。以下是在某市专网部署的 StreamPark 
平台截图 : 
+截至目前,StreamPark 已在多个政府、公安生产环境进行部署,汇聚处理城市实时物联数据、人车抓拍数据。以下是在某市专网部署的 StreamPark 
平台截图 :
 
 ![](/blog/haibo/application.png)
 
diff --git a/package.json b/package.json
index 51282403..9a55b870 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
   "dependencies": {
     "@docusaurus/core": "2.4.3",
     "@docusaurus/plugin-content-docs": "^2.4.3",
+    "@docusaurus/theme-common": "2.4.3",
     "@docusaurus/preset-classic": "2.4.3",
     "@easyops-cn/docusaurus-search-local": "^0.36.0",
     "@mdx-js/react": "^1.6.22",
@@ -26,6 +27,7 @@
     "clsx": "^1.2.1",
     "file-loader": "^6.2.0",
     "prism-react-renderer": "^1.3.1",
+    "framer-motion": "^10.13.1",
     "react": "^18.2.0",
     "react-copy-to-clipboard": "^5.1.0",
     "react-dom": "^18.2.0",
diff --git a/src/css/custom.css b/src/css/custom.css
index 3685f70a..770fc67d 100644
--- a/src/css/custom.css
+++ b/src/css/custom.css
@@ -83,15 +83,39 @@
   --ifm-pre-color: inherit;
   --ifm-pre-line-height: 1.45;
   --ifm-pre-padding: 1rem;
-  --ifm-container-width-xl: 1200px;
+  --ifm-container-width-xl: 1320px;
   --ifm-menu-link-padding-vertical: 0.5rem;
   --ifm-menu-link-padding-horizontal: 1.25rem;
-  --default-font: 0.90rem;
+  --default-font: 0.9rem;
   --default-line-height: 21px;
   /* --ifm-navbar-height: 5rem; */
+
+  /* 卡片背景 */
+  --card-background: #ffffff;
+
+  /* 选中未选中 */
+  --btn-selected: var(--ifm-color-primary);
+  --btn-unselected: #cecece;
+  --ifm-col-width: 100%;
 }
 
+html[data-theme="dark"] {
+  /* 卡片背景 */
+  --card-background: #0c0c0c;
+  --content-background-color: #0c0c0c;
+  --blog-item-background-color: #0c0c0c;
+  --blog-item-shadow: 0 10px 60px 5px #2c2e40, 0 0 10px 0 #000309;
+}
 html {
+  --post-title-color: hsl(220deg 79% 58%);
+  --post-pub-date-color: #8c8c8c;
+  --post-shadow-color: rgba(20, 85, 182, 0.1);
+  --post-shadow: 0 0 120px var(--post-shadow-color);
+  --blog-item-background-color: #fff;
+  --content-background-color: #fafafa;
+
+  --blog-item-shadow: 0 10px 60px 5px #f1f5f9, 0 0 10px 0 #e4e4e7;
+
   font-variant: tabular-nums;
   font-feature-settings: "tnum";
 }
@@ -100,7 +124,17 @@ body {
   margin: 0;
   font-size: var(--default-font) !important;
   line-height: var(--default-line-height);
-  font-family: -apple-system,BlinkMacSystemFont,Segoe 
UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI 
Symbol;
+  font-family:
+    -apple-system,
+    BlinkMacSystemFont,
+    Segoe UI,
+    Roboto,
+    Helvetica,
+    Arial,
+    sans-serif,
+    Apple Color Emoji,
+    Segoe UI Emoji,
+    Segoe UI Symbol;
 }
 
 .navbar__title {
@@ -374,7 +408,7 @@ footer .subscribe-box ul li a:hover .wechat-dropdown {
 }
 
 .theme-doc-sidebar-menu:not(#\#):not(#\#) {
-  font-size: 0.90rem;
+  font-size: 0.9rem;
 }
 
 .theme-doc-sidebar-item-link-level-1:not(:first-child):not(#\#):not(#\#),
@@ -477,3 +511,443 @@ p {
   color: var(--ifm-color-secondary-contrast-foreground);
   font-weight: 500;
 }
+
+/* .blog-wrapper > .container > .row > *:nth-child(3) > div > div:nth-child(1) 
{
+  position: static;
+  top: unset;
+} */
+
+.footer__copyright a {
+  color: var(--ifm-color-primary-light);
+}
+
+/* 文档侧栏字体大小 */
+.menu__list-item > .menu__link[tabindex] {
+  font-size: 0.875rem;
+  font-weight: normal;
+  font-family: serif;
+  letter-spacing: 1.2px;
+  line-height: 1.8em;
+}
+
+/* 搜索 */
+.navbar__search-input {
+  outline: none;
+}
+
+@media only screen and (max-width: 996px) {
+  .blog-wrapper > .container > .row {
+    display: flex;
+  }
+}
+
+@media (max-width: 570px) {
+  .main-wrapper h1,
+  .markdown > h1 {
+    font-size: 1.6em;
+  }
+
+  .markdown > h2 {
+    font-size: 1.4em;
+  }
+
+  .markdown > h3 {
+    font-size: 1.2em;
+  }
+
+  .blog__section_title {
+    margin-top: 2em;
+  }
+  .post__date-container {
+    margin-bottom: 1em !important;
+  }
+}
+
+@media (max-width: 400px) {
+  .article__footer {
+    display: grid;
+    grid-template-columns: 1fr;
+    justify-items: end;
+    row-gap: 36px;
+  }
+
+  .main-wrapper {
+    max-width: 100vw !important;
+    overflow: hidden;
+  }
+}
+
+@keyframes fading {
+  from {
+    opacity: 0;
+  }
+  to {
+    opacity: 1;
+  }
+}
+
+/* 导航收缩相应尺寸调大 */
+@media (max-width: 1100px) {
+  .navbar > .container,
+  .navbar > .container-fluid {
+    padding: 0;
+  }
+  .navbar__toggle {
+    display: inherit;
+  }
+  .navbar__item {
+    display: none;
+  }
+  .navbar__search-input {
+    width: 9rem;
+  }
+  .navbar-sidebar {
+    display: block;
+  }
+}
+
+/* google adsense */
+.google-auto-placed {
+  text-align: inherit !important;
+}
+
+ins {
+  max-height: 100% !important;
+}
+/* ============================================== 博客配置 
============================================== */
+
+html[data-theme="dark"] .bloghome__intro > p {
+  color: #dfdfdf;
+}
+
+.blog__section_title {
+  margin: 1em 0 0 0;
+  /* display: flex;
+  align-items: center;
+  justify-content: center; */
+  text-align: center;
+  font-size: calc(1.375rem + 1.5vw);
+  position: relative;
+}
+
+.blog__section_title svg {
+  position: absolute;
+  top: 0;
+}
+
+.newicon {
+  fill: var(--ifm-color-primary);
+}
+
+.bloghome__posts-list,
+.bloghome__posts-card {
+  animation: fading 0.8s;
+}
+
+/* 切换视图按钮 */
+.bloghome__swith-view {
+  text-align: center;
+  margin: 2em 0 1em 0 !important;
+}
+
+.bloghome__swith-view svg {
+  cursor: pointer;
+  transition: 0.6s;
+}
+
+.bloghome__switch--selected {
+  fill: var(--btn-selected);
+}
+
+.bloghome__switch {
+  fill: var(--btn-unselected);
+}
+
+.bloghome__posts-list {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  justify-content: center;
+  gap: 12px;
+  padding: 0 0 3em 0;
+}
+
+.post__list-style-ad {
+  grid-column: 1 / 3;
+}
+
+.post__list-item {
+  display: grid;
+  grid-template-columns: max-content 1fr;
+  grid-template-areas:
+    "title title"
+    "tags date";
+  column-gap: 2em;
+  row-gap: 1em;
+  align-items: center;
+  padding: 1em 1.2em;
+  background: var(--blog-item-background-color);
+  box-shadow: 4px 2px 5px 0 #338bff0d;
+  border-radius: 6px;
+}
+[data-theme="dark"] .post__list-item {
+  box-shadow: none;
+  border: 1px solid #2c2e40;
+}
+[data-theme="dark"] hr {
+  background-color: #2c2e40;
+}
+.post__list-item .post__list-title {
+  color: inherit;
+  font-size: 1em;
+  text-decoration: none;
+  transition: 0.6s;
+  grid-area: title;
+}
+
+.post__list-item .post__list:hover {
+  color: var(--ifm-color-primary);
+}
+
+.post__list-tags {
+  grid-area: tags;
+  overflow-x: auto;
+  padding: 0.2em 0;
+}
+
+.post__list-tags a {
+  background: white;
+  border: 1px solid var(--ifm-color-primary);
+  color: inherit;
+}
+
+.post__list-date {
+  grid-area: date;
+  justify-self: end;
+  color: var(--ifm-color-emphasis-600);
+}
+
+/* 发布日期 */
+.post__date-container {
+  display: grid;
+  justify-items: center;
+}
+
+.post__date {
+  background: url("/image/circle.svg") no-repeat;
+  background-size: contain;
+  background-position: center;
+  display: grid;
+  justify-items: center;
+  align-items: center;
+  width: 10.5em;
+  height: 10.5em;
+  padding-top: 1em;
+  margin-top: 4em;
+  grid-auto-rows: auto;
+}
+
+.post__day {
+  font-size: 4.25em;
+  line-height: 1em;
+  font-weight: 900;
+}
+
+.post__year_month {
+  align-self: start;
+  color: var(--post-pub-date-color);
+}
+
+.line__decor {
+  width: 65%;
+  height: 4px;
+  background-color: var(--ifm-color-primary);
+  align-self: end;
+  margin-bottom: 1.15em;
+  opacity: 0.25;
+}
+
+.post__tags-container {
+  white-space: nowrap;
+  overflow: auto;
+  padding-bottom: 12px;
+}
+
+.post__tags {
+  background: var(--ifm-color-primary);
+  padding: 6px 10px;
+  border-radius: 6px;
+  color: #ffffff;
+}
+
+.post__tags:hover {
+  color: #ffffff;
+  text-decoration: none;
+}
+
+html[data-theme="dark"] .post__tags {
+  color: #d4e8fa;
+  background: #0179fa77;
+}
+
+/* 底部 */
+.footer {
+  /* margin-top: 4em; */
+}
+
+.article__footer {
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+}
+
+.footer__read_count {
+  opacity: 0.8;
+  color: var(--ifm-color-primary);
+  font-size: 1.8em;
+}
+
+.footer_eye {
+  fill: var(--ifm-color-primary);
+}
+
+html[data-theme="dark"] .footer__read_count {
+  color: var(--ifm-color-primary-light);
+}
+
+html[data-theme="dark"] .footer_eye {
+  fill: var(--ifm-color-primary-light);
+}
+
+.pagination-nav__link {
+  /* border: none; */
+  margin: 2em 0 !important;
+  background: linear-gradient(90deg, var(--ifm-color-primary-light) 11.3%, 
var(--ifm-color-primary) 161.54%);
+  box-shadow: 0px 0px 10px rgb(0 105 165 / 35%);
+  color: white;
+  border: none;
+  height: auto;
+}
+
+.pagination-nav__link:hover {
+  color: white;
+}
+
+pagination-nav__item:hover .pagination-nav__link {
+  border: none;
+}
+
+.pagination-nav__item--next > .pagination-nav__link {
+  background: linear-gradient(90deg, var(--ifm-color-primary) 11.3%, 
var(--ifm-color-primary-light) 161.54%);
+}
+
+.pagination-nav__sublabel {
+  color: white;
+}
+
+/* 博客详情页 CSS 覆盖 */
+.blog-wrapper > .container {
+  width: 100vw;
+}
+
+.blog-wrapper > .container > .row:not([class~="blog-tags__page"]) {
+  display: grid;
+  grid-template-areas:
+    "detail recent"
+    "detail outline";
+  grid-template-rows: auto 1fr;
+  grid-template-columns: minmax(0, 1fr) 250px;
+}
+
+.blog-wrapper > .container > .row .col[class*="col--"] {
+  max-width: unset;
+}
+
+/* 近期文章 */
+.blog-wrapper > .container > .row > *:nth-child(1) {
+  grid-area: recent;
+  align-self: start;
+  /* height: 200px; */
+}
+.blog-wrapper > .container > .row > *:nth-child(1) > div {
+  position: static;
+}
+
+.blog-wrapper > .container > .row > *:nth-child(2) {
+  grid-area: detail;
+}
+.blog-wrapper > .container > .row > *:nth-child(3) {
+  grid-area: outline;
+}
+
+.blog-wrapper > .container > .row > *:nth-child(3) > div > div::before {
+  content: "文章目录";
+  display: block;
+  margin-bottom: 0.5rem;
+  font-size: var(--ifm-h3-font-size);
+  color: var(--ifm-heading-color);
+  font-weight: var(--ifm-heading-font-weight);
+}
+
+@media (max-width: 1000px) {
+  .blog__section_title {
+    margin-top: 3em;
+  }
+
+  .post__date-container {
+    justify-items: start;
+  }
+
+  .line__decor {
+    display: none;
+  }
+}
+
+/* post list view adjustment */
+@media only screen and (max-width: 700px) {
+  .bloghome__posts-list {
+    row-gap: 36px;
+    grid-template-columns: minmax(0, max-content);
+  }
+  .post__list-style-ad {
+    grid-column: initial;
+  }
+}
+
+/* 博客目录隐藏后,回复 row 默认样式 */
+@media (max-width: 996px) {
+  .blog-wrapper > .container > .row:not([class~="blog-tags__page"]) {
+    display: initial;
+  }
+}
+
+/* 首页背景 */
+.container-wrapper {
+  background: var(--content-background-color);
+}
+
+article > p,
+article > a,
+article > span,
+article > li {
+  line-height: 1.8em;
+}
+.post-footer {
+  margin-top: 2em !important;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  flex-direction: row;
+}
+
+svg:not(:root).svg-inline--fa,
+svg:not(:host).svg-inline--fa {
+  overflow: visible;
+  box-sizing: content-box;
+}
+
+.svg-inline--fa {
+  display: var(--fa-display, inline-block);
+  height: 1em;
+  overflow: visible;
+  vertical-align: -0.125em;
+}
diff --git a/src/pages/home/theme.less b/src/pages/home/theme.less
index 3613bffc..18fd162e 100644
--- a/src/pages/home/theme.less
+++ b/src/pages/home/theme.less
@@ -65,10 +65,6 @@ section,
   overflow: auto !important;
 }
 
-.overflow-hidden {
-  overflow: hidden !important;
-}
-
 .overflow-visible {
   overflow: visible !important;
 }
@@ -2568,6 +2564,12 @@ section,
   }
 }
 
+@media (min-width: 1440px) {
+  .container {
+    max-width: var(--ifm-container-width-xl)
+  }
+}
+
 .row {
   --bs-gutter-x: 1.5rem;
   --bs-gutter-y: 0;
diff --git a/src/theme/BlogArchivePage/index.tsx 
b/src/theme/BlogArchivePage/index.tsx
new file mode 100644
index 00000000..d72fb8cf
--- /dev/null
+++ b/src/theme/BlogArchivePage/index.tsx
@@ -0,0 +1,85 @@
+import React from 'react';
+import Link from '@docusaurus/Link';
+import {translate} from '@docusaurus/Translate';
+import {PageMetadata} from '@docusaurus/theme-common';
+import Layout from '@theme/Layout';
+import type {ArchiveBlogPost, Props} from '@theme/BlogArchivePage';
+
+type YearProp = {
+  year: string;
+  posts: ArchiveBlogPost[];
+};
+
+function Year({year, posts}: YearProp) {
+  return (
+    <>
+      <h3>{year}</h3>
+      <ul>
+        {posts.map((post) => (
+          <li key={post.metadata.date}>
+            <Link to={post.metadata.permalink}>
+              {post.metadata.formattedDate} - {post.metadata.title}
+            </Link>
+          </li>
+        ))}
+      </ul>
+    </>
+  );
+}
+
+function YearsSection({years}: {years: YearProp[]}) {
+  return (
+    <section className="margin-vert--lg">
+      <div className="container">
+        <div className="row">
+          {years.map((_props, idx) => (
+            <div key={idx} className="col col--4 margin-vert--lg">
+              <Year {..._props} />
+            </div>
+          ))}
+        </div>
+      </div>
+    </section>
+  );
+}
+
+function listPostsByYears(blogPosts: readonly ArchiveBlogPost[]): YearProp[] {
+  const postsByYear = blogPosts.reduceRight((posts, post) => {
+    const year = post.metadata.date.split('-')[0]!;
+    const yearPosts = posts.get(year) ?? [];
+    return posts.set(year, [post, ...yearPosts]);
+  }, new Map<string, ArchiveBlogPost[]>());
+
+  return Array.from(postsByYear, ([year, posts]) => ({
+    year,
+    posts,
+  }));
+}
+
+export default function BlogArchive({archive}: Props): JSX.Element {
+  const title = translate({
+    id: 'theme.blog.archive.title',
+    message: 'Archive',
+    description: 'The page & hero title of the blog archive page',
+  });
+  const description = translate({
+    id: 'theme.blog.archive.description',
+    message: 'Archive',
+    description: 'The page & hero description of the blog archive page',
+  });
+  const years = listPostsByYears(archive.blogPosts);
+  return (
+    <>
+      <PageMetadata title={title} description={description} />
+      <Layout>
+        <header className="hero hero--primary">
+          <div className="container">
+            <h1 className="hero__title">{title}</h1>
+            <p className="hero__subtitle">{description}</p>
+          </div>
+        </header>
+        <main>{years.length > 0 && <YearsSection years={years} />}</main>
+      </Layout>
+    </>
+  );
+}
diff --git a/src/theme/BlogLayout/index.tsx b/src/theme/BlogLayout/index.tsx
new file mode 100644
index 00000000..3c1c5296
--- /dev/null
+++ b/src/theme/BlogLayout/index.tsx
@@ -0,0 +1,32 @@
+import React from 'react';
+import clsx from 'clsx';
+import Layout from '@theme/Layout';
+import BlogSidebar from '@theme/BlogSidebar'
+import type { Props } from '@theme/BlogLayout';
+
+export default function BlogLayout(props: Props): JSX.Element {
+  const { sidebar, toc, children, ...layoutProps } = props;
+  const hasSidebar = sidebar && sidebar.items.length > 0;
+  return (
+    <Layout {...layoutProps}>
+      <div className='container-wrapper'>
+        <div className="container margin-vert--lg">
+          <div className="row">
+            <BlogSidebar sidebar={sidebar} />
+            <main
+              className={clsx('col', {
+                'col--8 overflow-hidden': hasSidebar,
+                'col--12': !hasSidebar,
+              })}
+              itemScope
+              itemType="http://schema.org/Blog";>
+              {children}
+            </main>
+            {toc && <div className="col col--2">{toc}</div>}
+          </div>
+        </div>
+
+      </div>
+    </Layout >
+  );
+}
diff --git a/src/theme/BlogListPage/img/card.svg 
b/src/theme/BlogListPage/img/card.svg
new file mode 100644
index 00000000..2858167e
--- /dev/null
+++ b/src/theme/BlogListPage/img/card.svg
@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" 
xmlns="http://www.w3.org/2000/svg";>
+<path d="M16 20H20V16H16V20ZM16 14H20V10H16V14ZM10 8H14V4H10V8ZM16 
8H20V4H16V8ZM10 14H14V10H10V14ZM4 14H8V10H4V14ZM4 20H8V16H4V20ZM10 
20H14V16H10V20ZM4 8H8V4H4V8Z"/>
+</svg>
diff --git a/src/theme/BlogListPage/img/list.svg 
b/src/theme/BlogListPage/img/list.svg
new file mode 100644
index 00000000..c077c769
--- /dev/null
+++ b/src/theme/BlogListPage/img/list.svg
@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" 
xmlns="http://www.w3.org/2000/svg";>
+<path d="M3 4H7V8H3V4ZM9 5V7H21V5H9ZM3 10H7V14H3V10ZM9 11V13H21V11H9ZM3 
16H7V20H3V16ZM9 17V19H21V17H9Z"/>
+</svg>
diff --git a/src/theme/BlogListPage/index.tsx b/src/theme/BlogListPage/index.tsx
new file mode 100644
index 00000000..9eb5eaca
--- /dev/null
+++ b/src/theme/BlogListPage/index.tsx
@@ -0,0 +1,111 @@
+import React from 'react';
+import clsx from 'clsx';
+
+import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
+import {
+  PageMetadata,
+  HtmlClassNameProvider,
+  ThemeClassNames,
+} from '@docusaurus/theme-common';
+import BlogLayout from '@theme/BlogLayout';
+import BlogListPaginator from '@theme/BlogListPaginator';
+import SearchMetadata from '@theme/SearchMetadata';
+import type { Props } from '@theme/BlogListPage';
+import BlogPostItems from '@theme/BlogPostItems';
+import BlogPostLisView from '../BlogPostLisView';
+import Translate from '@docusaurus/Translate';
+import { useViewType } from './useViewType';
+import ListFilter from "./img/list.svg";
+import CardFilter from "./img/card.svg";
+
+function BlogListPageMetadata(props: Props): JSX.Element {
+  const { metadata } = props;
+  const {
+    siteConfig: { title: siteTitle },
+  } = useDocusaurusContext();
+  const { blogDescription, blogTitle, permalink } = metadata;
+  const isBlogOnlyMode = permalink === '/';
+  const title = isBlogOnlyMode ? siteTitle : blogTitle;
+  return (
+    <>
+      <PageMetadata title={title} description={blogDescription} />
+      <SearchMetadata tag="blog_posts_list" />
+    </>
+  );
+}
+
+
+function BlogHeaderContent() {
+  return (
+    <h1 className="blog__section_title" id="homepage_blogs">
+      <Translate
+        id="theme.blog.newerPost"
+        description="latest blogs heading">
+        Latest blog
+      </Translate>
+      &nbsp;
+      <svg
+        width="31"
+        height="31"
+        viewBox="0 0 31 31"
+        fill="none"
+        xmlns="http://www.w3.org/2000/svg";
+      >
+        <path
+          d="M25.8333 5.16666H5.16668C3.73293 5.16666 2.59626 6.31624 2.59626 
7.74999L2.58334 23.25C2.58334 24.6837 3.73293 25.8333 5.16668 
25.8333H25.8333C27.2671 25.8333 28.4167 24.6837 28.4167 23.25V7.74999C28.4167 
6.31624 27.2671 5.16666 25.8333 5.16666ZM10.9792 19.375H9.42918L6.13543 
14.8542V19.375H4.52084V11.625H6.13543L9.36459 
16.1458V11.625H10.9792V19.375ZM17.4375 
13.2525H14.2083V14.6992H17.4375V16.3267H14.2083V17.7604H17.4375V19.375H12.2708V11.625H17.4375V13.2525ZM26.4792
 18.083 [...]
+          className="newicon"
+        />
+      </svg>
+    </h1>
+  )
+}
+function BlogListPageContent(props: Props): JSX.Element {
+  const { metadata, items } = props;
+  const { viewType, toggleViewType } = useViewType();
+  const isCardView = viewType === "card";
+  const isListView = viewType === "list";
+  return (
+    <BlogLayout>
+      <BlogHeaderContent />
+      {/* switch list and card */}
+      <div className="bloghome__swith-view">
+        <CardFilter
+          onClick={() => toggleViewType("card")}
+          className={viewType === "card" ? "bloghome__switch--selected" : 
"bloghome__switch"}
+        />
+        <ListFilter
+          onClick={() => toggleViewType("list")}
+          className={viewType === "list" ? "bloghome__switch--selected" : 
"bloghome__switch"}
+        />
+      </div>
+      <div className="bloghome__posts">
+        {isCardView && (
+          <div className="bloghome__posts-card">
+            <BlogPostItems items={items} />
+          </div>
+        )}
+
+        {isListView && (
+            <BlogPostLisView items={items} />
+        )}
+        <BlogListPaginator metadata={metadata} />
+      </div>
+
+
+
+    </BlogLayout>
+  );
+}
+
+export default function BlogListPage(props: Props): JSX.Element {
+  return (
+    <HtmlClassNameProvider
+      className={clsx(
+        ThemeClassNames.wrapper.blogPages,
+        ThemeClassNames.page.blogListPage,
+      )}>
+      <BlogListPageMetadata {...props} />
+      <BlogListPageContent {...props} />
+    </HtmlClassNameProvider>
+  );
+}
diff --git a/src/theme/BlogListPage/useViewType.ts 
b/src/theme/BlogListPage/useViewType.ts
new file mode 100644
index 00000000..baaae8bd
--- /dev/null
+++ b/src/theme/BlogListPage/useViewType.ts
@@ -0,0 +1,19 @@
+import { useCallback, useEffect, useState } from "react";
+
+export function useViewType() {
+  const [viewType, setViewType] = useState("card");
+
+  useEffect(() => {
+    setViewType(localStorage.getItem("viewType") || "card");
+  }, []);
+
+  const toggleViewType = useCallback((newViewType) => {
+    setViewType(newViewType);
+    localStorage.setItem("viewType", newViewType);
+  }, []);
+
+  return {
+    viewType,
+    toggleViewType,
+  };
+}
diff --git a/src/theme/BlogListPaginator/index.tsx 
b/src/theme/BlogListPaginator/index.tsx
new file mode 100644
index 00000000..02739409
--- /dev/null
+++ b/src/theme/BlogListPaginator/index.tsx
@@ -0,0 +1,45 @@
+import React from 'react';
+import Translate, { translate } from '@docusaurus/Translate';
+import PaginatorNavLink from '@theme/PaginatorNavLink';
+import type { Props } from '@theme/BlogListPaginator';
+
+export default function BlogListPaginator(props: Props): JSX.Element {
+  const { metadata } = props;
+  const { previousPage, nextPage } = metadata;
+
+  return (
+    <nav
+      className="pagination-nav"
+      aria-label={translate({
+        id: 'theme.blog.paginator.navAriaLabel',
+        message: 'Blog list page navigation',
+        description: 'The ARIA label for the blog pagination',
+      })}>
+      {previousPage && (
+        <PaginatorNavLink
+          permalink={previousPage}
+          title={
+            <Translate
+              id="theme.blog.paginator.newerEntries"
+              description="The label used to navigate to the newer blog posts 
page (previous page)">
+              Newer Entries
+            </Translate>
+          }
+        />
+      )}
+      {nextPage && (
+        <PaginatorNavLink
+          permalink={nextPage}
+          title={
+            <Translate
+              id="theme.blog.paginator.olderEntries"
+              description="The label used to navigate to the older blog posts 
page (next page)">
+              Older Entries
+            </Translate>
+          }
+          isNext
+        />
+      )}
+    </nav>
+  );
+}
diff --git a/src/theme/BlogPostItem/Container/index.tsx 
b/src/theme/BlogPostItem/Container/index.tsx
new file mode 100644
index 00000000..30c2f161
--- /dev/null
+++ b/src/theme/BlogPostItem/Container/index.tsx
@@ -0,0 +1,74 @@
+import React from 'react';
+import { useBaseUrlUtils } from '@docusaurus/useBaseUrl';
+//@ts-ignore internal func
+import { useBlogPost } from '@docusaurus/theme-common/internal';
+import type { Props } from '@theme/BlogPostItem/Container';
+import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
+
+import "./style.less"
+
+export default function BlogPostItemContainer({
+  children,
+  className,
+}: Props): JSX.Element {
+  const {
+    frontMatter,
+    assets,
+    metadata: { description, date },
+    isBlogPostPage
+  } = useBlogPost();
+  const { withBaseUrl } = useBaseUrlUtils();
+  const image = assets.image ?? frontMatter.image;
+  const keywords = frontMatter.keywords ?? [];
+  const dateObj = new Date(date);
+  // 当前语言
+  const {
+    i18n: { currentLocale },
+  } = useDocusaurusContext();
+
+  const year = dateObj.getFullYear();
+  let month = `${dateObj.getMonth() + 1}`;
+  const day = dateObj.getDate();
+  let dateStr = `${year}年${month}月`;
+
+  if (currentLocale === "en") {
+    month = dateObj.toLocaleString("default", { month: "long" });
+    dateStr = `${month}, ${year}`;
+  }
+
+  return (
+    <div className={`${!isBlogPostPage ? "blog-list--box" : ""}`}>
+      <div
+        className={`row ${!isBlogPostPage ? "blog-list--item" : ""}`}
+        style={{ margin: 0 }}
+      >
+        {/* 列表页日期 */}
+        {!isBlogPostPage && (
+          <div className="post__date-container col col--3 padding-right--lg 
margin-bottom--lg">
+            <div className="post__date">
+              <div className="post__day">{day}</div>
+              <div className="post__year_month">{dateStr}</div>
+            </div>
+            <div className="line__decor"></div>
+          </div>
+        )}
+        <div className={`col ${isBlogPostPage ? `col--12 article__details 
article-bg` : `col--9`}`} >
+          <article
+            className={className}
+            itemProp="blogPost"
+            itemScope
+            itemType="http://schema.org/BlogPosting";>
+            {description && <meta itemProp="description" content={description} 
/>}
+            {image && (
+              <link itemProp="image" href={withBaseUrl(image, { absolute: true 
})} />
+            )}
+            {keywords.length > 0 && (
+              <meta itemProp="keywords" content={keywords.join(',')} />
+            )}
+            {children}
+          </article>
+        </div>
+      </div>
+    </div>
+  );
+}
diff --git a/src/theme/BlogPostItem/Container/style.less 
b/src/theme/BlogPostItem/Container/style.less
new file mode 100644
index 00000000..79d1fb54
--- /dev/null
+++ b/src/theme/BlogPostItem/Container/style.less
@@ -0,0 +1,99 @@
+/* 卡片新拟态特效 */
+.blog-list--box {
+  margin-top: 0em;
+  margin-bottom: 7.25em;
+  line-height: 1.75rem;
+
+  .blog-list--item {
+    border-radius: 12px;
+    background: var(--blog-item-background-color);
+    box-shadow: var(--blog-item-shadow);
+    padding: 1em 0.5em;
+    position: relative;
+  }
+
+
+
+  @media (max-width: 570px) {
+    .article__details {
+      padding: 0;
+    }
+  }
+
+  article {
+    .single-post--date {
+      color: var(--ifm-color-primary);
+      font-size: 0.9em;
+    }
+
+    >header {
+      >h1 {
+        font-size: 2em;
+
+        /* color: #2f5c85; */
+        @media (max-width: 570px) {
+          & {
+            font-size: 1.6em;
+            text-align: center;
+          }
+        }
+      }
+
+      >h2 {
+        font-size: 2em;
+        line-height: 1.5em;
+        margin-bottom: 20px !important;
+
+        a {
+          color: var(--ifm-heading-color);
+
+          &:hover {
+            text-decoration: none;
+          }
+        }
+
+        @media (max-width: 570px) {
+          & {
+            font-size: 1.7em;
+          }
+        }
+      }
+
+      >div>time {
+        color: var(--post-pub-date-color);
+      }
+    }
+
+    .markdown p,
+    .markdown ul {
+      font-family: var(--content-font-family);
+    }
+  }
+
+  @media (max-width: 1000px) {
+    .blog-list--item {
+      padding-right: 1em;
+    }
+  }
+}
+
+.article-bg {
+  background: var(--blog-item-background-color);
+  box-shadow: var(--blog-item-shadow);
+  padding: 1.5em 1em;
+  position: relative;
+  border-radius: 5px;
+}
+
+
+[data-theme="dark"] {
+  .article-bg {
+    box-shadow: none;
+  }
+
+  .blog-list--item {
+    box-shadow: none;
+    border: 1px solid #2c2e40;
+  }
+
+}
diff --git a/src/theme/BlogPostItem/Content/index.tsx 
b/src/theme/BlogPostItem/Content/index.tsx
new file mode 100644
index 00000000..7842fa06
--- /dev/null
+++ b/src/theme/BlogPostItem/Content/index.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import clsx from 'clsx';
+import {blogPostContainerID} from '@docusaurus/utils-common';
+//@ts-ignore internal func
+import {useBlogPost} from '@docusaurus/theme-common/internal';
+import MDXContent from '@theme/MDXContent';
+import type {Props} from '@theme/BlogPostItem/Content';
+
+export default function BlogPostItemContent({
+  children,
+  className,
+}: Props): JSX.Element {
+  const {isBlogPostPage} = useBlogPost();
+  return (
+    <div
+      // This ID is used for the feed generation to locate the main content
+      id={isBlogPostPage ? blogPostContainerID : undefined}
+      className={clsx('markdown', className)}
+      itemProp="articleBody">
+      <MDXContent>{children}</MDXContent>
+    </div>
+  );
+}
diff --git a/src/theme/BlogPostItem/Footer/ReadMoreLink/index.tsx 
b/src/theme/BlogPostItem/Footer/ReadMoreLink/index.tsx
new file mode 100644
index 00000000..205c3efd
--- /dev/null
+++ b/src/theme/BlogPostItem/Footer/ReadMoreLink/index.tsx
@@ -0,0 +1,37 @@
+import React from 'react';
+import Translate, {translate} from '@docusaurus/Translate';
+import Link from '@docusaurus/Link';
+import type {Props} from '@theme/BlogPostItem/Footer/ReadMoreLink';
+import styles from './styles.module.css';
+function ReadMoreLabel() {
+  return (
+    <b className={styles.readMore}>
+      <Translate
+        id="theme.blog.post.readMore"
+        description="The label used in blog post item excerpts to link to full 
blog posts">
+        Read More
+      </Translate>
+    </b>
+  );
+}
+
+export default function BlogPostItemFooterReadMoreLink(
+  props: Props,
+): JSX.Element {
+  const {blogPostTitle, ...linkProps} = props;
+  return (
+    <Link
+      aria-label={translate(
+        {
+          message: 'Read more about {title}',
+          id: 'theme.blog.post.readMoreLabel',
+          description:
+            'The ARIA label for the link to full blog posts from excerpts',
+        },
+        {title: blogPostTitle},
+      )}
+      {...linkProps}>
+      <ReadMoreLabel />
+    </Link>
+  );
+}
diff --git a/src/theme/BlogPostItem/Footer/ReadMoreLink/styles.module.css 
b/src/theme/BlogPostItem/Footer/ReadMoreLink/styles.module.css
new file mode 100644
index 00000000..2b3755d0
--- /dev/null
+++ b/src/theme/BlogPostItem/Footer/ReadMoreLink/styles.module.css
@@ -0,0 +1,11 @@
+.readMore {
+  margin-top: 1.4em;
+  /* background-color: var(--ifm-link-color);
+  color: white; */
+  border-radius: 8px;
+  color: white;
+  padding: 0.75em 2em;
+  background: linear-gradient(90deg, var(--ifm-color-primary) 11.3%, 
var(--ifm-color-primary-light) 161.54%);
+  box-shadow: 0px 0px 32px rgba(0, 105, 165, 0.35);
+  border-radius: 7px;
+}
diff --git a/src/theme/BlogPostItem/Footer/index.tsx 
b/src/theme/BlogPostItem/Footer/index.tsx
new file mode 100644
index 00000000..6462f0e2
--- /dev/null
+++ b/src/theme/BlogPostItem/Footer/index.tsx
@@ -0,0 +1,67 @@
+import React from 'react';
+import clsx from 'clsx';
+//@ts-ignore internal func
+import { useBlogPost } from '@docusaurus/theme-common/internal';
+import EditThisPage from '@theme/EditThisPage';
+import ReadMoreLink from '@theme/BlogPostItem/Footer/ReadMoreLink';
+import TagsListInline from '@theme/TagsListInline';
+
+import styles from './styles.module.css';
+
+export default function BlogPostItemFooter(): JSX.Element | null {
+  const { metadata, isBlogPostPage } = useBlogPost();
+  const { tags, title, editUrl, hasTruncateMarker } = metadata;
+
+  // A post is truncated if it's in the "list view" and it has a truncate 
marker
+  const truncatedPost = !isBlogPostPage && hasTruncateMarker;
+
+  const renderFooter = truncatedPost || editUrl;
+
+
+
+  if (!renderFooter) {
+    return null;
+  }
+
+  const BlogPostPageFooter = () => {
+    if (!isBlogPostPage) return null
+    const tagsExists = tags.length > 0;
+    const footerDom = []
+    if (tagsExists) {
+      footerDom.push(
+        <div className={clsx('col', { 'col--9': truncatedPost })} key={'tags'}>
+          <TagsListInline tags={tags} />
+        </div>
+      )
+    }
+    if (editUrl) {
+      footerDom.push(
+        <div className="col col-3 text--right" key={'editUrl'}>
+          <EditThisPage editUrl={editUrl} />
+        </div>
+      )
+    }
+    return footerDom
+  }
+
+  return (
+    <footer
+      className={clsx(
+        'row docusaurus-mt-lg',
+        isBlogPostPage && styles.blogPostFooterDetailsFull,
+      )}>
+
+      <div className='post-footer'>
+        {BlogPostPageFooter()}
+      </div>
+
+
+      {truncatedPost && (
+        <div
+          className={clsx('col text--right')}>
+          <ReadMoreLink blogPostTitle={title} to={metadata.permalink} />
+        </div>
+      )}
+    </footer>
+  );
+}
diff --git a/src/theme/BlogPostItem/Footer/styles.module.css 
b/src/theme/BlogPostItem/Footer/styles.module.css
new file mode 100644
index 00000000..f9272fb5
--- /dev/null
+++ b/src/theme/BlogPostItem/Footer/styles.module.css
@@ -0,0 +1,10 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+.blogPostFooterDetailsFull {
+  flex-direction: column;
+}
diff --git a/src/theme/BlogPostItem/Header/Author/index.tsx 
b/src/theme/BlogPostItem/Header/Author/index.tsx
new file mode 100644
index 00000000..ea35ca41
--- /dev/null
+++ b/src/theme/BlogPostItem/Header/Author/index.tsx
@@ -0,0 +1,53 @@
+import React from 'react';
+import clsx from 'clsx';
+import Link, {type Props as LinkProps} from '@docusaurus/Link';
+
+import type {Props} from '@theme/BlogPostItem/Header/Author';
+
+function MaybeLink(props: LinkProps): JSX.Element {
+  if (props.href) {
+    return <Link {...props} />;
+  }
+  return <>{props.children}</>;
+}
+
+export default function BlogPostItemHeaderAuthor({
+  author,
+  className,
+}: Props): JSX.Element {
+  const {name, title, url, imageURL, email} = author;
+  const link = url || (email && `mailto:${email}`) || undefined;
+  return (
+    <div className={clsx('avatar margin-bottom--sm', className)}>
+      {imageURL && (
+        <MaybeLink href={link} className="avatar__photo-link">
+          <img
+            className="avatar__photo"
+            src={imageURL}
+            alt={name}
+            itemProp="image"
+          />
+        </MaybeLink>
+      )}
+
+      {name && (
+        <div
+          className="avatar__intro"
+          itemProp="author"
+          itemScope
+          itemType="https://schema.org/Person";>
+          <div className="avatar__name">
+            <MaybeLink href={link} itemProp="url">
+              <span itemProp="name">{name}</span>
+            </MaybeLink>
+          </div>
+          {title && (
+            <small className="avatar__subtitle" itemProp="description">
+              {title}
+            </small>
+          )}
+        </div>
+      )}
+    </div>
+  );
+}
diff --git a/src/theme/BlogPostItem/Header/Authors/index.tsx 
b/src/theme/BlogPostItem/Header/Authors/index.tsx
new file mode 100644
index 00000000..8827c871
--- /dev/null
+++ b/src/theme/BlogPostItem/Header/Authors/index.tsx
@@ -0,0 +1,47 @@
+import React from 'react';
+import clsx from 'clsx';
+//@ts-ignore internal func
+import {useBlogPost} from '@docusaurus/theme-common/internal';
+import BlogPostItemHeaderAuthor from '@theme/BlogPostItem/Header/Author';
+import type {Props} from '@theme/BlogPostItem/Header/Authors';
+import styles from './styles.module.css';
+
+// Component responsible for the authors layout
+export default function BlogPostItemHeaderAuthors({
+  className,
+}: Props): JSX.Element | null {
+  const {
+    metadata: {authors},
+    assets,
+  } = useBlogPost();
+  const authorsCount = authors.length;
+  if (authorsCount === 0) {
+    return null;
+  }
+  const imageOnly = authors.every(({name}) => !name);
+  return (
+    <div
+      className={clsx(
+        'margin-top--md margin-bottom--sm',
+        imageOnly ? styles.imageOnlyAuthorRow : 'row',
+        className,
+      )}>
+      {authors.map((author, idx) => (
+        <div
+          className={clsx(
+            !imageOnly && 'col col--6',
+            imageOnly ? styles.imageOnlyAuthorCol : styles.authorCol,
+          )}
+          key={idx}>
+          <BlogPostItemHeaderAuthor
+            author={{
+              ...author,
+              // Handle author images using relative paths
+              imageURL: assets.authorsImageUrls[idx] ?? author.imageURL,
+            }}
+          />
+        </div>
+      ))}
+    </div>
+  );
+}
diff --git a/src/theme/BlogPostItem/Header/Authors/styles.module.css 
b/src/theme/BlogPostItem/Header/Authors/styles.module.css
new file mode 100644
index 00000000..b1bd1106
--- /dev/null
+++ b/src/theme/BlogPostItem/Header/Authors/styles.module.css
@@ -0,0 +1,21 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+.authorCol {
+  max-width: inherit !important;
+  flex-grow: 1 !important;
+}
+
+.imageOnlyAuthorRow {
+  display: flex;
+  flex-flow: row wrap;
+}
+
+.imageOnlyAuthorCol {
+  margin-left: 0.3rem;
+  margin-right: 0.3rem;
+}
diff --git a/src/theme/BlogPostItem/Header/Info/index.tsx 
b/src/theme/BlogPostItem/Header/Info/index.tsx
new file mode 100644
index 00000000..2e0aea7c
--- /dev/null
+++ b/src/theme/BlogPostItem/Header/Info/index.tsx
@@ -0,0 +1,65 @@
+import React from 'react';
+import clsx from 'clsx';
+import {translate} from '@docusaurus/Translate';
+import {usePluralForm} from '@docusaurus/theme-common';
+//@ts-ignore internal func
+import {useBlogPost} from '@docusaurus/theme-common/internal';
+import type {Props} from '@theme/BlogPostItem/Header/Info';
+
+import styles from './styles.module.css';
+
+// Very simple pluralization: probably good enough for now
+function useReadingTimePlural() {
+  const {selectMessage} = usePluralForm();
+  return (readingTimeFloat: number) => {
+    const readingTime = Math.ceil(readingTimeFloat);
+    return selectMessage(
+      readingTime,
+      translate(
+        {
+          id: 'theme.blog.post.readingTime.plurals',
+          description:
+            'Pluralized label for "{readingTime} min read". Use as much plural 
forms (separated by "|") as your language support (see 
https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)',
+          message: 'One min read|{readingTime} min read',
+        },
+        {readingTime},
+      ),
+    );
+  };
+}
+
+function ReadingTime({readingTime}: {readingTime: number}) {
+  const readingTimePlural = useReadingTimePlural();
+  return <>{readingTimePlural(readingTime)}</>;
+}
+
+function Date({date, formattedDate}: {date: string; formattedDate: string}) {
+  return (
+    <time dateTime={date} itemProp="datePublished">
+      {formattedDate}
+    </time>
+  );
+}
+
+function Spacer() {
+  return <>{' · '}</>;
+}
+
+export default function BlogPostItemHeaderInfo({
+  className,
+}: Props): JSX.Element {
+  const {metadata,isBlogPostPage} = useBlogPost();
+  const {date, formattedDate, readingTime} = metadata;
+  if(!isBlogPostPage) return null
+  return (
+    <div className={clsx(styles.container, 'margin-vert--md', className)}>
+      <Date date={date} formattedDate={formattedDate} />
+      {typeof readingTime !== 'undefined' && (
+        <>
+          <Spacer />
+          <ReadingTime readingTime={readingTime} />
+        </>
+      )}
+    </div>
+  );
+}
diff --git a/src/theme/BlogPostItem/Header/Info/styles.module.css 
b/src/theme/BlogPostItem/Header/Info/styles.module.css
new file mode 100644
index 00000000..796b0f17
--- /dev/null
+++ b/src/theme/BlogPostItem/Header/Info/styles.module.css
@@ -0,0 +1,10 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+.container {
+  font-size: 0.9rem;
+}
diff --git a/src/theme/BlogPostItem/Header/Tags/index.tsx 
b/src/theme/BlogPostItem/Header/Tags/index.tsx
new file mode 100644
index 00000000..8ea24089
--- /dev/null
+++ b/src/theme/BlogPostItem/Header/Tags/index.tsx
@@ -0,0 +1,36 @@
+import React from 'react';
+//@ts-ignore internal func
+import { useBlogPost } from '@docusaurus/theme-common/internal';
+import Link from '@docusaurus/Link';
+
+export default function BlogPostItemHeaderTitle(): JSX.Element {
+  const { metadata, isBlogPostPage } = useBlogPost();
+
+  const { tags,hasTruncateMarker } = metadata;
+  if (isBlogPostPage || tags.length === 0) return null
+
+  return (tags.length > 0 || hasTruncateMarker) && (
+    <div className="post__tags-container margin-top--none margin-bottom--md">
+      {tags.length > 0 && (
+        <>
+          <svg aria-hidden="true" focusable="false" data-prefix="fas" 
data-icon="tags" className="svg-inline--fa fa-tags margin-right--md" role="img" 
xmlns="http://www.w3.org/2000/svg"; viewBox="0 0 640 512" color="#c4d3e0">
+            <path fill="currentColor" d="M497.941 225.941L286.059 14.059A48 48 
0 0 0 252.118 0H48C21.49 0 0 21.49 0 48v204.118a48 48 0 0 0 14.059 
33.941l211.882 211.882c18.744 18.745 49.136 18.746 67.882 
0l204.118-204.118c18.745-18.745 18.745-49.137 0-67.882zM112 160c-26.51 
0-48-21.49-48-48s21.49-48 48-48 48 21.49 48 48-21.49 48-48 48zm513.941 
133.823L421.823 497.941c-18.745 18.745-49.137 18.745-67.882 0l-.36-.36L527.64 
323.522c16.999-16.999 26.36-39.6 26.36-63.64s-9.362-46.641-26.36-63. [...]
+            </svg>
+          {tags
+            .slice(0, 4)
+            .map(({ label, permalink: tagPermalink }, index) => (
+              <Link
+                key={tagPermalink}
+                className={`post__tags ${index > 0 ? "margin-horiz--sm" : 
"margin-right--sm"
+                  }`}
+                to={tagPermalink}
+                style={{ fontSize: "0.75em", fontWeight: 500 }}
+              >
+                {label}
+              </Link>
+            ))}
+        </>
+      )}
+    </div>
+  );
+}
diff --git a/src/theme/BlogPostItem/Header/Title/index.tsx 
b/src/theme/BlogPostItem/Header/Title/index.tsx
new file mode 100644
index 00000000..e2cb8c16
--- /dev/null
+++ b/src/theme/BlogPostItem/Header/Title/index.tsx
@@ -0,0 +1,37 @@
+import React from 'react';
+import clsx from 'clsx';
+import Link from '@docusaurus/Link';
+//@ts-ignore internal func
+import { useBlogPost } from '@docusaurus/theme-common/internal';
+import type { Props } from '@theme/BlogPostItem/Header/Title';
+
+import styles from './styles.module.css';
+
+export default function BlogPostItemHeaderTitle({
+  className,
+}: Props): JSX.Element {
+  const { metadata, isBlogPostPage } = useBlogPost();
+  const { permalink, title } = metadata;
+  const TitleHeading = isBlogPostPage ? 'h1' : 'h2';
+  return (
+    <TitleHeading
+      className={clsx(
+        isBlogPostPage ? "margin-bottom--md" : "margin-vert--md",
+        styles.blogPostTitle,
+        isBlogPostPage ? "text--center" : "",
+        className,
+        'post--titleLink'
+      )}
+      itemProp="headline">
+      {isBlogPostPage ? (
+        title
+      ) : (
+        <div className={styles.blogPostTitleLink}>
+          <Link itemProp="url" to={permalink}>
+            {title}
+          </Link>
+        </div>
+      )}
+    </TitleHeading>
+  );
+}
diff --git a/src/theme/BlogPostItem/Header/Title/styles.module.css 
b/src/theme/BlogPostItem/Header/Title/styles.module.css
new file mode 100644
index 00000000..02e6c3dd
--- /dev/null
+++ b/src/theme/BlogPostItem/Header/Title/styles.module.css
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+.blogPostTitle {
+  font-size: 3rem;
+}
+.blogPostTitleLink {
+  color: var(--ifm-heading-color);
+  font-weight: 500;
+  position: relative;
+  transition: 0.5s;
+}
+.blogPostTitleLink a {
+  color: var(--ifm-heading-color);
+  font-weight: 500;
+  position: relative;
+  transition: 0.5s;
+}
+
+.blogPostTitleLink:hover {
+  color: var(--ifm-color-primary);
+  text-decoration: none;
+}
+
+.blogPostTitleLink:hover:after {
+  -webkit-transform: scaleX(1);
+  transform: scaleX(1);
+  visibility: visible;
+}
+
+.blogPostTitleLink:after {
+  background: var(--ifm-color-primary);
+  bottom: 0;
+  content: "";
+  height: 2px;
+  left: 0;
+  position: absolute;
+  -webkit-transform: scaleX(0);
+  transform: scaleX(0);
+  transition: 0.3s linear;
+  visibility: hidden;
+  width: 100%;
+}
+
+/**
+  Blog post title should be smaller on smaller devices
+**/
+@media (max-width: 576px) {
+  .title {
+    font-size: 2rem;
+  }
+}
diff --git a/src/theme/BlogPostItem/Header/index.tsx 
b/src/theme/BlogPostItem/Header/index.tsx
new file mode 100644
index 00000000..38078941
--- /dev/null
+++ b/src/theme/BlogPostItem/Header/index.tsx
@@ -0,0 +1,16 @@
+import React from 'react';
+import BlogPostItemHeaderTitle from '@theme/BlogPostItem/Header/Title';
+import BlogPostItemHeaderInfo from '@theme/BlogPostItem/Header/Info';
+import BlogPostItemHeaderAuthors from '@theme/BlogPostItem/Header/Authors';
+import BlogPostItemHeaderTags from './Tags';
+
+export default function BlogPostItemHeader(): JSX.Element {
+  return (
+    <header>
+      <BlogPostItemHeaderTitle />
+      <BlogPostItemHeaderInfo />
+      <BlogPostItemHeaderAuthors />
+      <BlogPostItemHeaderTags />
+    </header>
+  );
+}
diff --git a/src/theme/BlogPostItem/index.tsx b/src/theme/BlogPostItem/index.tsx
new file mode 100644
index 00000000..c63fbfc7
--- /dev/null
+++ b/src/theme/BlogPostItem/index.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import clsx from 'clsx';
+//@ts-ignore internal func
+import { useBlogPost } from '@docusaurus/theme-common/internal';
+import BlogPostItemContainer from '@theme/BlogPostItem/Container';
+import BlogPostItemHeader from '@theme/BlogPostItem/Header';
+import BlogPostItemContent from '@theme/BlogPostItem/Content';
+import BlogPostItemFooter from '@theme/BlogPostItem/Footer';
+import type { Props } from '@theme/BlogPostItem';
+
+// apply a bottom margin in list view
+function useContainerClassName() {
+  const { isBlogPostPage } = useBlogPost();
+  return !isBlogPostPage ? 'margin-bottom--xl' : undefined;
+}
+
+export default function BlogPostItem({
+  children,
+  className,
+}: Props): JSX.Element {
+  const containerClassName = useContainerClassName();
+  return (
+    <BlogPostItemContainer className={clsx(containerClassName, className)}>
+      <BlogPostItemHeader />
+      <BlogPostItemContent>{children}</BlogPostItemContent>
+      <BlogPostItemFooter />
+    </BlogPostItemContainer>
+  );
+}
diff --git a/src/theme/BlogPostItems/index.tsx 
b/src/theme/BlogPostItems/index.tsx
new file mode 100644
index 00000000..1a6cf1f8
--- /dev/null
+++ b/src/theme/BlogPostItems/index.tsx
@@ -0,0 +1,50 @@
+import React from 'react';
+//@ts-ignore internal func
+import {BlogPostProvider} from '@docusaurus/theme-common/internal';
+import BlogPostItem from '@theme/BlogPostItem';
+import type {Props} from '@theme/BlogPostItems';
+import { motion, Variants } from 'framer-motion'
+
+const variants: Variants = {
+  from: { opacity: 0.01, y: 100 },
+  to: i => ({
+    opacity: 1,
+    y: 0,
+    transition: {
+      type: 'spring',
+      damping: 25,
+      stiffness: 100,
+      bounce: 0.2,
+      duration: 0.3,
+      delay: i * 0.2,
+    },
+  }),
+}
+
+export default function BlogPostItems({
+  items,
+  component: BlogPostItemComponent = BlogPostItem,
+}: Props): JSX.Element {
+  return (
+    <>
+      {items.map(({content: BlogPostContent},i) => (
+        <BlogPostProvider
+          key={BlogPostContent.metadata.permalink}
+          content={BlogPostContent}>
+            <motion.div
+            initial="from"
+            animate="to"
+            custom={i}
+            viewport={{ once: true, amount: 0.8 }}
+            variants={variants}
+          >
+            <BlogPostItemComponent>
+            <BlogPostContent />
+          </BlogPostItemComponent>
+          </motion.div>
+
+        </BlogPostProvider>
+      ))}
+    </>
+  );
+}
diff --git a/src/theme/BlogPostLisView/index.tsx 
b/src/theme/BlogPostLisView/index.tsx
new file mode 100644
index 00000000..ce3bd575
--- /dev/null
+++ b/src/theme/BlogPostLisView/index.tsx
@@ -0,0 +1,98 @@
+import Link from "@docusaurus/Link";
+import React from "react";
+import {  motion } from 'framer-motion'
+
+
+const container = {
+  hidden: { opacity: 1, scale: 0 },
+  visible: {
+    opacity: 1,
+    scale: 1,
+    transition: {
+      delayChildren: 0.3,
+      staggerChildren: 0.2,
+    },
+  },
+}
+
+const item = {
+  hidden: { y: 20, opacity: 0 },
+  visible: {
+    y: 0,
+    opacity: 1,
+  },
+}
+
+
+export default function BlogPostLisView({ items }: any): JSX.Element {
+
+  return (
+    <motion.div
+      className="bloghome__posts-list"
+      variants={container}
+      initial="hidden"
+      animate="visible"
+    >
+      {
+        items.map(({ content: BlogPostContent }, index) => {
+          const { metadata: blogMetaData, frontMatter } = BlogPostContent;
+          const { title } = frontMatter;
+          const { permalink, date, tags } = blogMetaData;
+
+          const dateObj = new Date(date);
+
+          const year = dateObj.getFullYear();
+          let month = ("0" + (dateObj.getMonth() + 1)).slice(-2);
+          const day = ("0" + dateObj.getDate()).slice(-2);
+
+          return (
+            <React.Fragment key={blogMetaData.permalink}>
+              <motion.div
+                className="post__list-item"
+                key={blogMetaData.permalink}
+                variants={item}
+              >
+
+                <Link to={permalink} className="post__list-title">
+                  {title}
+                </Link>
+
+                <div className="post__list-tags">
+                  {tags.length > 0 &&
+                    tags
+                      .slice(0, 2)
+                      .map(
+                        (
+                          { label, permalink: tagPermalink },
+                          index
+                        ) => (
+                          <Link
+                            key={tagPermalink}
+                            className={`post__tags ${index < tags.length
+                              ? "margin-right--sm"
+                              : ""
+                              }`}
+                            to={tagPermalink}
+                            style={{
+                              fontSize: "0.75em",
+                              fontWeight: 500,
+                            }}
+                          >
+                            {label}
+                          </Link>
+                        )
+                      )}
+                </div>
+                <div className="post__list-date">
+                  {year}-{month}-{day}
+                </div>
+              </motion.div>
+            </React.Fragment>
+          );
+        })
+      }
+
+    </motion.div>
+  )
+
+}
diff --git a/src/theme/BlogPostPage/Metadata/index.tsx 
b/src/theme/BlogPostPage/Metadata/index.tsx
new file mode 100644
index 00000000..16e8ae88
--- /dev/null
+++ b/src/theme/BlogPostPage/Metadata/index.tsx
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import React from 'react';
+import {PageMetadata} from '@docusaurus/theme-common';
+//@ts-ignore internal func
+import {useBlogPost} from '@docusaurus/theme-common/internal';
+
+export default function BlogPostPageMetadata(): JSX.Element {
+  const {assets, metadata} = useBlogPost();
+  const {title, description, date, tags, authors, frontMatter} = metadata;
+
+  const {keywords} = frontMatter;
+  const image = assets.image ?? frontMatter.image;
+  return (
+    <PageMetadata
+      title={title}
+      description={description}
+      keywords={keywords}
+      image={image}>
+      <meta property="og:type" content="article" />
+      <meta property="article:published_time" content={date} />
+      {authors.some((author) => author.url) && (
+        <meta
+          property="article:author"
+          content={authors
+            .map((author) => author.url)
+            .filter(Boolean)
+            .join(',')}
+        />
+      )}
+      {tags.length > 0 && (
+        <meta
+          property="article:tag"
+          content={tags.map((tag) => tag.label).join(',')}
+        />
+      )}
+    </PageMetadata>
+  );
+}
diff --git a/src/theme/BlogPostPage/index.tsx b/src/theme/BlogPostPage/index.tsx
new file mode 100644
index 00000000..6a6f1902
--- /dev/null
+++ b/src/theme/BlogPostPage/index.tsx
@@ -0,0 +1,65 @@
+import React, {type ReactNode} from 'react';
+import clsx from 'clsx';
+import {HtmlClassNameProvider, ThemeClassNames} from 
'@docusaurus/theme-common';
+//@ts-ignore internal func
+import {BlogPostProvider, useBlogPost} from 
'@docusaurus/theme-common/internal';
+import BlogLayout from '@theme/BlogLayout';
+import BlogPostItem from '@theme/BlogPostItem';
+import BlogPostPaginator from '@theme/BlogPostPaginator';
+import BlogPostPageMetadata from '@theme/BlogPostPage/Metadata';
+import TOC from '@theme/TOC';
+import type {Props} from '@theme/BlogPostPage';
+import type {BlogSidebar} from '@docusaurus/plugin-content-blog';
+
+function BlogPostPageContent({
+  sidebar,
+  children,
+}: {
+  sidebar: BlogSidebar;
+  children: ReactNode;
+}): JSX.Element {
+  const {metadata, toc} = useBlogPost();
+  const {nextItem, prevItem, frontMatter} = metadata;
+  const {
+    hide_table_of_contents: hideTableOfContents,
+    toc_min_heading_level: tocMinHeadingLevel,
+    toc_max_heading_level: tocMaxHeadingLevel,
+  } = frontMatter;
+  return (
+    <BlogLayout
+      sidebar={sidebar}
+      toc={
+        !hideTableOfContents && toc.length > 0 ? (
+          <TOC
+            toc={toc}
+            minHeadingLevel={tocMinHeadingLevel}
+            maxHeadingLevel={tocMaxHeadingLevel}
+          />
+        ) : undefined
+      }>
+      <BlogPostItem>{children}</BlogPostItem>
+
+      {(nextItem || prevItem) && (
+        <BlogPostPaginator nextItem={nextItem} prevItem={prevItem} />
+      )}
+    </BlogLayout>
+  );
+}
+
+export default function BlogPostPage(props: Props): JSX.Element {
+  const BlogPostContent = props.content;
+  return (
+    <BlogPostProvider content={props.content} isBlogPostPage>
+      <HtmlClassNameProvider
+        className={clsx(
+          ThemeClassNames.wrapper.blogPages,
+          ThemeClassNames.page.blogPostPage,
+        )}>
+        <BlogPostPageMetadata />
+        <BlogPostPageContent sidebar={props.sidebar}>
+          <BlogPostContent />
+        </BlogPostPageContent>
+      </HtmlClassNameProvider>
+    </BlogPostProvider>
+  );
+}
diff --git a/src/theme/BlogPostPaginator/index.tsx 
b/src/theme/BlogPostPaginator/index.tsx
new file mode 100644
index 00000000..acf93a00
--- /dev/null
+++ b/src/theme/BlogPostPaginator/index.tsx
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import React from 'react';
+import Translate, {translate} from '@docusaurus/Translate';
+import PaginatorNavLink from '@theme/PaginatorNavLink';
+import type {Props} from '@theme/BlogPostPaginator';
+
+export default function BlogPostPaginator(props: Props): JSX.Element {
+  const {nextItem, prevItem} = props;
+
+  return (
+    <nav
+      className="pagination-nav docusaurus-mt-lg"
+      aria-label={translate({
+        id: 'theme.blog.post.paginator.navAriaLabel',
+        message: 'Blog post page navigation',
+        description: 'The ARIA label for the blog posts pagination',
+      })}>
+      {prevItem && (
+        <PaginatorNavLink
+          {...prevItem}
+          subLabel={
+            <Translate
+              id="theme.blog.post.paginator.newerPost"
+              description="The blog post button label to navigate to the 
newer/previous post">
+              Newer Post
+            </Translate>
+          }
+        />
+      )}
+      {nextItem && (
+        <PaginatorNavLink
+          {...nextItem}
+          subLabel={
+            <Translate
+              id="theme.blog.post.paginator.olderPost"
+              description="The blog post button label to navigate to the 
older/next post">
+              Older Post
+            </Translate>
+          }
+          isNext
+        />
+      )}
+    </nav>
+  );
+}
diff --git a/src/theme/BlogSidebar/Desktop/index.tsx 
b/src/theme/BlogSidebar/Desktop/index.tsx
new file mode 100644
index 00000000..2e52e243
--- /dev/null
+++ b/src/theme/BlogSidebar/Desktop/index.tsx
@@ -0,0 +1,67 @@
+import React from 'react'
+import clsx from 'clsx'
+import Link from '@docusaurus/Link'
+import { translate } from '@docusaurus/Translate'
+import type { Props } from '@theme/BlogSidebar/Desktop'
+import { motion } from 'framer-motion'
+import styles from './styles.module.css'
+
+export default function BlogSidebarDesktop({ sidebar }: Props): JSX.Element {
+
+  const handleBack = () => {
+    window.history.back()
+  }
+
+  return (
+    <motion.aside
+      className="col col--2 overflow-hidden"
+      initial={{ opacity: 0, x: -100 }}
+      animate={{ opacity: 1, x: 0 }}
+      transition={{
+        type: 'spring',
+        stiffness: 400,
+        damping: 20,
+        duration: 0.3,
+      }}
+    >
+      <nav
+        className={clsx(styles.sidebar, 'thin-scrollbar')}
+        aria-label={translate({
+          id: 'theme.blog.sidebar.navAriaLabel',
+          message: 'Blog recent posts navigation',
+          description: 'The ARIA label for recent posts in the blog sidebar',
+        })}
+      >
+        {(
+          <div className={styles.backButton} onClick={handleBack}>
+            <svg xmlns="http://www.w3.org/2000/svg"; width="32" height="32" 
viewBox="0 0 24 24">
+              <path fill="currentColor" d="M8 7v4L2 6l6-5v4h5a8 8 0 1 1 0 
16H4v-2h9a6 6 0 0 0 0-12H8Z" />
+            </svg>
+          </div>
+        )}
+
+        <Link
+          href="/blog"
+          className={clsx(styles.sidebarItemTitle, 'margin-bottom--sm')}
+        >
+          {sidebar.title}
+        </Link>
+        <ul className={clsx(styles.sidebarItemList, 'clean-list')}>
+          {sidebar.items.map(item => (
+            <li key={item.permalink} className={styles.sidebarItem}>
+              <Link
+                isNavLink
+                to={item.permalink}
+                className={styles.sidebarItemLink}
+                activeClassName={styles.sidebarItemLinkActive}
+              >
+                {item.title}
+              </Link>
+            </li>
+          ))}
+        </ul>
+      </nav>
+
+    </motion.aside>
+  )
+}
diff --git a/src/theme/BlogSidebar/Desktop/styles.module.css 
b/src/theme/BlogSidebar/Desktop/styles.module.css
new file mode 100644
index 00000000..e016d836
--- /dev/null
+++ b/src/theme/BlogSidebar/Desktop/styles.module.css
@@ -0,0 +1,80 @@
+.sidebar {
+  max-height: calc(100vh - (var(--ifm-navbar-height) + 2rem));
+  overflow-y: auto;
+  position: sticky;
+  top: calc(var(--ifm-navbar-height) + 2rem);
+}
+
+.sidebarItemTitle {
+  font-size: var(--ifm-h4-font-size);
+  font-weight: var(--ifm-font-weight-bold);
+  font-family: var(--ifm-heading-font-family);
+  color: var(--ifm-text-color);
+}
+
+.sidebarItemTitle:hover {
+  color: var(--ifm-link-color);
+  text-decoration: none;
+}
+
+.sidebarItemList {
+  font-size: 0.8rem;
+}
+
+.sidebarItem {
+  margin-top: 0.7rem;
+}
+
+.sidebarItemLink {
+  color: var(--ifm-font-color-base);
+  display: block;
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+}
+
+.sidebarItemLink:hover {
+  text-decoration: none;
+}
+
+.sidebarItemLinkActive {
+  color: var(--ifm-color-primary) !important;
+}
+
+@media (max-width: 996px) {
+  .sidebar {
+    display: none;
+  }
+}
+
+.backButton {
+  width: 32px;
+  height: 32px;
+  border-radius: 50%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  margin-bottom: 2rem;
+  text-align: right;
+  float: right;
+  transition: all 0.3s ease-in-out;
+  cursor: pointer;
+  background-color: #fff;
+}
+.backButton svg {
+  width: 16px;
+  height: 16px;
+}
+
+.backButton:hover {
+  color: var(--ifm-link-color);
+  background-color: #f1f5f9;
+}
+
+html[data-theme="dark"] .backButton {
+  background-color: #1e293b;
+}
+
+html[data-theme="dark"] .backButton:hover {
+  background-color: #334155;
+}
diff --git a/src/theme/BlogSidebar/Mobile/index.tsx 
b/src/theme/BlogSidebar/Mobile/index.tsx
new file mode 100644
index 00000000..d2590a92
--- /dev/null
+++ b/src/theme/BlogSidebar/Mobile/index.tsx
@@ -0,0 +1,32 @@
+import React from 'react'
+import Link from '@docusaurus/Link'
+import { NavbarSecondaryMenuFiller } from '@docusaurus/theme-common'
+import type { Props } from '@theme/BlogSidebar/Mobile'
+
+function BlogSidebarMobileSecondaryMenu({ sidebar }: Props): JSX.Element {
+  return (
+    <ul className="menu__list">
+      {sidebar.items.map(item => (
+        <li key={item.permalink} className="menu__list-item">
+          <Link
+            isNavLink
+            to={item.permalink}
+            className="menu__link"
+            activeClassName="menu__link--active"
+          >
+            {item.title}
+          </Link>
+        </li>
+      ))}
+    </ul>
+  )
+}
+
+export default function BlogSidebarMobile(props: Props): JSX.Element {
+  return (
+    <NavbarSecondaryMenuFiller
+      component={BlogSidebarMobileSecondaryMenu}
+      props={props}
+    />
+  )
+}
diff --git a/src/theme/BlogSidebar/index.tsx b/src/theme/BlogSidebar/index.tsx
new file mode 100644
index 00000000..766c394f
--- /dev/null
+++ b/src/theme/BlogSidebar/index.tsx
@@ -0,0 +1,17 @@
+import React from 'react'
+import { useWindowSize } from '@docusaurus/theme-common'
+import BlogSidebarDesktop from '@theme/BlogSidebar/Desktop'
+import BlogSidebarMobile from '@theme/BlogSidebar/Mobile'
+import type { Props } from '@theme/BlogSidebar'
+
+export default function BlogSidebar({ sidebar }: Props): JSX.Element | null {
+  const windowSize = useWindowSize()
+  if (!sidebar?.items.length) {
+    return null
+  }
+  // Mobile sidebar doesn't need to be server-rendered
+  if (windowSize === 'mobile') {
+    return <BlogSidebarMobile sidebar={sidebar} />
+  }
+  return <BlogSidebarDesktop sidebar={sidebar} />
+}
diff --git a/src/theme/BlogTagsListPage/index.tsx 
b/src/theme/BlogTagsListPage/index.tsx
new file mode 100644
index 00000000..847f1da5
--- /dev/null
+++ b/src/theme/BlogTagsListPage/index.tsx
@@ -0,0 +1,30 @@
+import React from 'react';
+import clsx from 'clsx';
+import {
+  PageMetadata,
+  HtmlClassNameProvider,
+  ThemeClassNames,
+  translateTagsPageTitle,
+} from '@docusaurus/theme-common';
+import BlogLayout from '@theme/BlogLayout';
+import TagsListByLetter from '@theme/TagsListByLetter';
+import type {Props} from '@theme/BlogTagsListPage';
+import SearchMetadata from '@theme/SearchMetadata';
+
+export default function BlogTagsListPage({tags, sidebar}: Props): JSX.Element {
+  const title = translateTagsPageTitle();
+  return (
+    <HtmlClassNameProvider
+      className={clsx(
+        ThemeClassNames.wrapper.blogPages,
+        ThemeClassNames.page.blogTagsListPage,
+      )}>
+      <PageMetadata title={title} />
+      <SearchMetadata tag="blog_tags_list" />
+      <BlogLayout sidebar={sidebar}>
+        <h1>{title}</h1>
+        <TagsListByLetter tags={tags} />
+      </BlogLayout>
+    </HtmlClassNameProvider>
+  );
+}
diff --git a/src/theme/BlogTagsPostsPage/index.tsx 
b/src/theme/BlogTagsPostsPage/index.tsx
new file mode 100644
index 00000000..e63986c6
--- /dev/null
+++ b/src/theme/BlogTagsPostsPage/index.tsx
@@ -0,0 +1,93 @@
+import React from 'react';
+import clsx from 'clsx';
+import Translate, {translate} from '@docusaurus/Translate';
+import {
+  PageMetadata,
+  HtmlClassNameProvider,
+  ThemeClassNames,
+  usePluralForm,
+} from '@docusaurus/theme-common';
+import Link from '@docusaurus/Link';
+import BlogLayout from '@theme/BlogLayout';
+import BlogListPaginator from '@theme/BlogListPaginator';
+import SearchMetadata from '@theme/SearchMetadata';
+import type {Props} from '@theme/BlogTagsPostsPage';
+import BlogPostItems from '@theme/BlogPostItems';
+
+// Very simple pluralization: probably good enough for now
+function useBlogPostsPlural() {
+  const {selectMessage} = usePluralForm();
+  return (count: number) =>
+    selectMessage(
+      count,
+      translate(
+        {
+          id: 'theme.blog.post.plurals',
+          description:
+            'Pluralized label for "{count} posts". Use as much plural forms 
(separated by "|") as your language support (see 
https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)',
+          message: 'One post|{count} posts',
+        },
+        {count},
+      ),
+    );
+}
+
+function useBlogTagsPostsPageTitle(tag: Props['tag']): string {
+  const blogPostsPlural = useBlogPostsPlural();
+  return translate(
+    {
+      id: 'theme.blog.tagTitle',
+      description: 'The title of the page for a blog tag',
+      message: '{nPosts} tagged with "{tagName}"',
+    },
+    {nPosts: blogPostsPlural(tag.count), tagName: tag.label},
+  );
+}
+
+function BlogTagsPostsPageMetadata({tag}: Props): JSX.Element {
+  const title = useBlogTagsPostsPageTitle(tag);
+  return (
+    <>
+      <PageMetadata title={title} />
+      <SearchMetadata tag="blog_tags_posts" />
+    </>
+  );
+}
+
+function BlogTagsPostsPageContent({
+  tag,
+  items,
+  sidebar,
+  listMetadata,
+}: Props): JSX.Element {
+  const title = useBlogTagsPostsPageTitle(tag);
+  return (
+    <BlogLayout sidebar={sidebar}>
+      <header className="margin-bottom--xl">
+        <h1>{title}</h1>
+
+        <Link href={tag.allTagsPath}>
+          <Translate
+            id="theme.tags.tagsPageLink"
+            description="The label of the link targeting the tag list page">
+            View All Tags
+          </Translate>
+        </Link>
+      </header>
+      <BlogPostItems items={items} />
+      <BlogListPaginator metadata={listMetadata} />
+    </BlogLayout>
+  );
+}
+export default function BlogTagsPostsPage(props: Props): JSX.Element {
+  return (
+    <HtmlClassNameProvider
+      className={clsx(
+        ThemeClassNames.wrapper.blogPages,
+        ThemeClassNames.page.blogTagPostListPage,
+      )}>
+      <BlogTagsPostsPageMetadata {...props} />
+      <BlogTagsPostsPageContent {...props} />
+    </HtmlClassNameProvider>
+  );
+}
diff --git a/src/theme/TOC/index.tsx b/src/theme/TOC/index.tsx
new file mode 100644
index 00000000..f4c9d773
--- /dev/null
+++ b/src/theme/TOC/index.tsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import clsx from 'clsx';
+import TOCItems from '@theme/TOCItems';
+import type {Props} from '@theme/TOC';
+
+import styles from './styles.module.css';
+import { motion } from 'framer-motion'
+
+// Using a custom className
+// This prevents TOCInline/TOCCollapsible getting highlighted by mistake
+const LINK_CLASS_NAME = 'table-of-contents__link toc-highlight';
+const LINK_ACTIVE_CLASS_NAME = 'table-of-contents__link--active';
+
+export default function TOC({className, ...props}: Props): JSX.Element {
+  return (
+    <motion.div
+      className={clsx(styles.tableOfContents, 'thin-scrollbar', className)}
+      initial={{ opacity: 0, x: 100 }}
+      animate={{ opacity: 1, x: 0 }}
+      transition={{
+        type: 'spring',
+        stiffness: 400,
+        damping: 20,
+        duration: 0.3,
+      }}
+    >
+    <TOCItems
+        {...props}
+        linkClassName={LINK_CLASS_NAME}
+        linkActiveClassName={LINK_ACTIVE_CLASS_NAME}
+      />
+    </motion.div>
+  );
+}
diff --git a/src/theme/TOC/styles.module.css b/src/theme/TOC/styles.module.css
new file mode 100644
index 00000000..4b6d2bcc
--- /dev/null
+++ b/src/theme/TOC/styles.module.css
@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+.tableOfContents {
+  max-height: calc(100vh - (var(--ifm-navbar-height) + 2rem));
+  overflow-y: auto;
+  position: sticky;
+  top: calc(var(--ifm-navbar-height) + 1rem);
+}
+
+@media (max-width: 996px) {
+  .tableOfContents {
+    display: none;
+  }
+
+  .docItemContainer {
+    padding: 0 0.3rem;
+  }
+}
diff --git a/static/image/circle.svg b/static/image/circle.svg
new file mode 100644
index 00000000..3dd2d7ff
--- /dev/null
+++ b/static/image/circle.svg
@@ -0,0 +1,8 @@
+<svg width="144" height="156" viewBox="0 0 144 156" fill="none" 
xmlns="http://www.w3.org/2000/svg";>
+  <path
+    d="M139.18 113C126.836 137.89 101.166 155 71.5 155C41.8339 155 16.1641 
137.89 3.82036 113H2.70569C15.1268 138.46 41.265 156 71.5 156C101.735 156 
127.873 138.46 140.294 113H139.18Z"
+    fill="#4490D6" />
+  <path
+    d="M1.06727 50C11.7989 21.3746 39.4129 1 71.7857 1C104.158 1 131.772 
21.3746 142.504 50H143.571C132.792 20.8111 104.719 0 71.7857 0C38.852 0 10.779 
20.8111 0 50H1.06727Z"
+    fill="#4453D6" />
+</svg>

Reply via email to