hexo-theme-nblog 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/_config.yml CHANGED
@@ -1,7 +1,9 @@
1
1
  #! 注意! 为了更新 npm package 后你的数据不会丢失
2
- #! 请将此文件复制到根目录并重命名为 `_config.nblog.yml` 并在此文件的基础上进行站点
2
+ #! 请将此文件复制到根目录并重命名为 `_config.nblog.yml` 并在此文件的基础上进行站点配置
3
3
  default_license: CC BY-NC-SA 4.0
4
4
 
5
+ primary_color: '#FF7F78'
6
+
5
7
  site_name: My Blog
6
8
  author: Your Name
7
9
  avatar: /images/avatar.png
@@ -82,4 +84,10 @@ medium_zoom:
82
84
 
83
85
  lazyload:
84
86
  enable: true
85
- placeholder: blur
87
+ placeholder: blur
88
+
89
+ notifications:
90
+ enable: true
91
+ interval: 10000
92
+ items:
93
+ - message: '欢迎访问我的博客!'
package/layout/index.ejs CHANGED
@@ -1,127 +1,176 @@
1
1
  <div class="posts-page container">
2
- <div class="posts-page__content">
3
- <div class="posts-list">
4
- <% page.posts.each(function(post) { %>
5
- <article class="post-card material-card animate-fade-in-up">
6
- <a href="<%= url_for(post.path) %>" class="post-card__link">
7
- <div class="post-card__cover">
8
- <% if (post.cover) { %>
9
- <img data-src="<%= post.cover %>" alt="<%= post.title %>">
10
- <% } else { %>
11
- <div class="post-card__cover-placeholder"></div>
12
- <% } %>
13
- <div class="post-card__overlay"></div>
14
- <div class="post-card__info">
15
- <div class="post-card__meta">
16
- <span class="post-card__date">
17
- <%= date(post.date, 'YYYY-MM-DD' ) %>
18
- </span>
19
- <% if (post.categories && post.categories.length> 0) { %>
20
- <span class="post-card__category">
21
- <%= post.categories.toArray()[0].name %>
22
- </span>
23
- <% } %>
24
- </div>
25
- <h2 class="post-card__title">
26
- <%= post.title %>
27
- </h2>
28
- </div>
2
+ <h1 class="sr-only">
3
+ <%= config.title %>
4
+ </h1>
5
+ <% if (theme.notifications && theme.notifications.enable && theme.notifications.items &&
6
+ theme.notifications.items.length> 0) { %>
7
+ <div class="notification-banner notification-banner--mobile material-card" id="notificationBannerMobile"
8
+ data-interval="<%= theme.notifications.interval || 5000 %>">
9
+ <div class="notification-banner__inner">
10
+ <div class="notification-banner__content">
11
+ <% theme.notifications.items.forEach(function(item, index) { %>
12
+ <div class="notification-banner__item<%= index === 0 ? ' is-active' : '' %>">
13
+ <span class="notification-banner__message">
14
+ <%- markdown(item.message) %>
15
+ </span>
29
16
  </div>
30
- </a>
31
- </article>
32
- <% }); %>
33
-
34
- <% if (page.total> 1) { %>
35
- <nav class="pagination">
36
- <%- paginator({
37
- prev_text: '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 18 9 12 15 6"></polyline></svg>'
38
- ,
39
- next_text: '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"></polyline></svg>'
40
- , escape: false }) %>
41
- </nav>
42
- <% } %>
43
- </div>
44
-
45
- <aside class="sidebar">
46
- <div class="author-card material-card animate-fade-in-right">
47
- <div class="author-card__avatar">
48
- <img data-src="<%= theme.avatar %>" alt="<%= theme.author %>">
49
- </div>
50
- <h3 class="author-card__name">
51
- <%= theme.author %>
52
- </h3>
53
- <p class="author-card__bio">
54
- <%= theme.bio %>
55
- </p>
56
- <div class="author-card__stats">
57
- <div class="author-card__stat">
58
- <span class="author-card__stat-value">
59
- <%= site.posts.length %>
60
- </span>
61
- <span class="author-card__stat-label">文章</span>
62
- </div>
63
- <div class="author-card__stat">
64
- <span class="author-card__stat-value">
65
- <%= site.categories.length %>
66
- </span>
67
- <span class="author-card__stat-label">分类</span>
68
- </div>
69
- <div class="author-card__stat">
70
- <span class="author-card__stat-value">
71
- <%= site.tags.length %>
72
- </span>
73
- <span class="author-card__stat-label">标签</span>
74
- </div>
17
+ <% }); %>
75
18
  </div>
76
- <% if (theme.social && theme.social.length> 0) { %>
77
- <div class="author-card__social">
78
- <% for (var link of theme.social) { %>
79
- <a href="<%= link.url %>" target="_blank" rel="noopener" class="author-card__social-link"
80
- title="<%= link.platform %>">
81
- <%- link.icon %>
82
- </a>
83
- <% } %>
84
- </div>
85
- <% } %>
86
- </div>
87
-
88
- <div class="sidebar-widget material-card animate-fade-in-right animate-delay-1">
89
- <h3 class="sidebar-widget__title">
90
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
91
- <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
92
- </svg>
93
- 分类
94
- </h3>
95
- <div class="sidebar-widget__content">
96
- <% site.categories.sort('name').forEach(function(category) { %>
97
- <a href="<%= url_for(category.path) %>" class="sidebar-widget__item">
98
- <span class="sidebar-widget__item-name">
99
- <%= category.name %>
100
- </span>
101
- <span class="sidebar-widget__item-count">
102
- <%= category.length %>
103
- </span>
104
- </a>
19
+ <div class="notification-banner__indicators">
20
+ <% theme.notifications.items.forEach(function(item, index) { %>
21
+ <span class="notification-banner__indicator<%= index === 0 ? ' is-active' : '' %>"
22
+ data-index="<%= index %>"></span>
105
23
  <% }); %>
106
24
  </div>
107
25
  </div>
108
-
109
- <div class="sidebar-widget material-card animate-fade-in-right animate-delay-2">
110
- <h3 class="sidebar-widget__title">
111
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
112
- <path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"></path>
113
- <line x1="7" y1="7" x2="7.01" y2="7"></line>
114
- </svg>
115
- 标签云
116
- </h3>
117
- <div class="sidebar-widget__content sidebar-widget__content--tags">
118
- <% site.tags.sort('name').forEach(function(tag) { %>
119
- <a href="<%= url_for(tag.path) %>" class="tag-cloud__tag">
120
- <%= tag.name %>
121
- </a>
26
+ </div>
27
+ <% } %>
28
+ <div class="posts-page__content">
29
+ <div class="posts-list">
30
+ <% page.posts.each(function(post) { %>
31
+ <article class="post-card material-card animate-fade-in-up">
32
+ <a href="<%= url_for(post.path) %>" class="post-card__link">
33
+ <div class="post-card__cover">
34
+ <% if (post.cover) { %>
35
+ <img data-src="<%= post.cover %>" alt="<%= post.title %>">
36
+ <% } else { %>
37
+ <div class="post-card__cover-placeholder"></div>
38
+ <% } %>
39
+ <div class="post-card__overlay"></div>
40
+ <div class="post-card__info">
41
+ <div class="post-card__meta">
42
+ <span class="post-card__date">
43
+ <%= date(post.date, 'YYYY-MM-DD' ) %>
44
+ </span>
45
+ <% if (post.categories && post.categories.length> 0) { %>
46
+ <span class="post-card__category">
47
+ <%= post.categories.toArray()[0].name %>
48
+ </span>
49
+ <% } %>
50
+ </div>
51
+ <h2 class="post-card__title">
52
+ <%= post.title %>
53
+ </h2>
54
+ </div>
55
+ </div>
56
+ </a>
57
+ </article>
122
58
  <% }); %>
59
+
60
+ <% if (page.total> 1) { %>
61
+ <nav class="pagination">
62
+ <%- paginator({
63
+ prev_text: '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 18 9 12 15 6"></polyline></svg>'
64
+ ,
65
+ next_text: '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"></polyline></svg>'
66
+ , escape: false }) %>
67
+ </nav>
68
+ <% } %>
123
69
  </div>
70
+
71
+ <aside class="sidebar">
72
+ <% if (theme.notifications && theme.notifications.enable && theme.notifications.items &&
73
+ theme.notifications.items.length> 0) { %>
74
+ <div class="notification-banner material-card animate-fade-in-right" id="notificationBanner"
75
+ data-interval="<%= theme.notifications.interval || 5000 %>">
76
+ <div class="notification-banner__inner">
77
+ <div class="notification-banner__content">
78
+ <% theme.notifications.items.forEach(function(item, index) { %>
79
+ <div class="notification-banner__item<%= index === 0 ? ' is-active' : '' %>">
80
+ <span class="notification-banner__message">
81
+ <%- markdown(item.message) %>
82
+ </span>
83
+ </div>
84
+ <% }); %>
85
+ </div>
86
+ <div class="notification-banner__indicators">
87
+ <% theme.notifications.items.forEach(function(item, index) { %>
88
+ <span class="notification-banner__indicator<%= index === 0 ? ' is-active' : '' %>"
89
+ data-index="<%= index %>"></span>
90
+ <% }); %>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ <% } %>
95
+ <div class="author-card material-card animate-fade-in-right">
96
+ <div class="author-card__avatar">
97
+ <img data-src="<%= theme.avatar %>" alt="<%= theme.author %>">
98
+ </div>
99
+ <h3 class="author-card__name">
100
+ <%= theme.author %>
101
+ </h3>
102
+ <p class="author-card__bio">
103
+ <%= theme.bio %>
104
+ </p>
105
+ <div class="author-card__stats">
106
+ <div class="author-card__stat">
107
+ <span class="author-card__stat-value">
108
+ <%= site.posts.length %>
109
+ </span>
110
+ <span class="author-card__stat-label">文章</span>
111
+ </div>
112
+ <div class="author-card__stat">
113
+ <span class="author-card__stat-value">
114
+ <%= site.categories.length %>
115
+ </span>
116
+ <span class="author-card__stat-label">分类</span>
117
+ </div>
118
+ <div class="author-card__stat">
119
+ <span class="author-card__stat-value">
120
+ <%= site.tags.length %>
121
+ </span>
122
+ <span class="author-card__stat-label">标签</span>
123
+ </div>
124
+ </div>
125
+ <% if (theme.social && theme.social.length> 0) { %>
126
+ <div class="author-card__social">
127
+ <% for (var link of theme.social) { %>
128
+ <a href="<%= link.url %>" target="_blank" rel="noopener" class="author-card__social-link"
129
+ title="<%= link.platform %>">
130
+ <%- link.icon %>
131
+ </a>
132
+ <% } %>
133
+ </div>
134
+ <% } %>
135
+ </div>
136
+
137
+ <div class="sidebar-widget material-card animate-fade-in-right animate-delay-1">
138
+ <h3 class="sidebar-widget__title">
139
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
140
+ <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
141
+ </svg>
142
+ 分类
143
+ </h3>
144
+ <div class="sidebar-widget__content">
145
+ <% site.categories.sort('name').forEach(function(category) { %>
146
+ <a href="<%= url_for(category.path) %>" class="sidebar-widget__item">
147
+ <span class="sidebar-widget__item-name">
148
+ <%= category.name %>
149
+ </span>
150
+ <span class="sidebar-widget__item-count">
151
+ <%= category.length %>
152
+ </span>
153
+ </a>
154
+ <% }); %>
155
+ </div>
156
+ </div>
157
+
158
+ <div class="sidebar-widget material-card animate-fade-in-right animate-delay-2">
159
+ <h3 class="sidebar-widget__title">
160
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
161
+ <path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"></path>
162
+ <line x1="7" y1="7" x2="7.01" y2="7"></line>
163
+ </svg>
164
+ 标签云
165
+ </h3>
166
+ <div class="sidebar-widget__content sidebar-widget__content--tags">
167
+ <% site.tags.sort('name').forEach(function(tag) { %>
168
+ <a href="<%= url_for(tag.path) %>" class="tag-cloud__tag">
169
+ <%= tag.name %>
170
+ </a>
171
+ <% }); %>
172
+ </div>
173
+ </div>
174
+ </aside>
124
175
  </div>
125
- </aside>
126
- </div>
127
176
  </div>
package/layout/layout.ejs CHANGED
@@ -11,38 +11,77 @@
11
11
  <%= page.title %> | <% } %>
12
12
  <%= config.title %>
13
13
  </title>
14
- <link rel="icon" type="image/svg+xml" href="/favicon.svg">
15
- <link rel="preconnect" href="https://fonts.googleapis.com">
16
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
17
- <link
18
- href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&display=swap"
19
- rel="stylesheet">
20
- <link rel="stylesheet" href="<%= url_for('/css/main.css') %>">
21
- <% if (theme.lazyload && theme.lazyload.enable) { %>
22
- <link rel="stylesheet" href="<%= url_for('/css/lazyload.css') %>">
23
- <% } %>
24
- <% if (is_post()) { %>
25
- <link rel="stylesheet" href="<%= url_for('/css/markdown.css') %>">
26
- <link rel="stylesheet" href="<%= url_for('/css/highlight.css') %>">
27
- <link rel="stylesheet" href="<%= url_for('/css/admonition.css') %>">
28
- <% if (theme.twikoo && theme.twikoo.enable) { %>
29
- <link rel="stylesheet" href="<%= url_for('/css/twikoo.css') %>">
14
+ <% var primaryColor=theme.primary_color || '#FF7F78' ; function hexToRgb(hex) { var
15
+ result=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g:
16
+ parseInt(result[2], 16), b: parseInt(result[3], 16) } : null; } function rgbToHsl(r, g, b) { r /=255; g /=255; b
17
+ /=255; var max=Math.max(r, g, b), min=Math.min(r, g, b); var h, s, l=(max + min) / 2; if (max===min) { h=s=0; } else
18
+ { var d=max - min; s=l> 0.5 ? d / (2 - max - min) : d / (max + min);
19
+ switch (max) {
20
+ case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break; case g: h=((b - r) / d + 2) / 6; break; case b: h=((r - g) /
21
+ d + 4) / 6; break; } } return { h: h * 360, s: s * 100, l: l * 100 }; } function hslToRgb(h, s, l) { h /=360; s
22
+ /=100; l /=100; var r, g, b; if (s===0) { r=g=b=l; } else { var hue2rgb=function(p, q, t) { if (t < 0) t +=1; if
23
+ (t> 1) t -= 1;
24
+ if (t < 1/6) return p + (q - p) * 6 * t; if (t < 1/2) return q; if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
25
+ return p; }; var q=l < 0.5 ? l * (1 + s) : l + s - l * s; var p=2 * l - q; r=hue2rgb(p, q, h + 1/3);
26
+ g=hue2rgb(p, q, h); b=hue2rgb(p, q, h - 1/3); } return { r: Math.round(r * 255), g: Math.round(g * 255), b:
27
+ Math.round(b * 255) }; } function rgbToHex(r, g, b) { return '#' + [r, g, b].map(function(x) { var
28
+ hex=x.toString(16); return hex.length===1 ? '0' + hex : hex; }).join(''); } function adjustLightness(hex,
29
+ amount) { var rgb=hexToRgb(hex); var hsl=rgbToHsl(rgb.r, rgb.g, rgb.b); hsl.l=Math.max(0, Math.min(100, hsl.l +
30
+ amount)); var newRgb=hslToRgb(hsl.h, hsl.s, hsl.l); return rgbToHex(newRgb.r, newRgb.g, newRgb.b); } function
31
+ adjustSaturation(hex, satAmount, lightAmount) { var rgb=hexToRgb(hex); var hsl=rgbToHsl(rgb.r, rgb.g, rgb.b);
32
+ hsl.s=Math.max(0, Math.min(100, hsl.s + satAmount)); hsl.l=Math.max(0, Math.min(100, hsl.l + lightAmount)); var
33
+ newRgb=hslToRgb(hsl.h, hsl.s, hsl.l); return rgbToHex(newRgb.r, newRgb.g, newRgb.b); } var
34
+ rgb=hexToRgb(primaryColor); var primaryLight=adjustLightness(primaryColor, 21); var
35
+ primaryDark=adjustSaturation(primaryColor, -39, -12); var btnPlainBgHover='rgba(' + rgb.r + ', ' + rgb.g + ', '
36
+ + rgb.b + ', 0.5)' ; var darkPrimary=adjustLightness(primaryColor, 5); var
37
+ darkPrimaryLight=adjustSaturation(primaryColor, -56, -36); var darkPrimaryDark=adjustLightness(primaryColor,
38
+ 21); %>
39
+ <style>
40
+ :root {
41
+ --primary-color: <%=primaryColor %>;
42
+ --primary-light: <%=primaryLight %>;
43
+ --primary-dark: <%=primaryDark %>;
44
+ --btn-plain-bg-hover: <%=btnPlainBgHover %>;
45
+ }
46
+
47
+ [data-theme="dark"] {
48
+ --primary-color: <%=darkPrimary %>;
49
+ --primary-light: <%=darkPrimaryLight %>;
50
+ --primary-dark: <%=darkPrimaryDark %>;
51
+ }
52
+ </style>
53
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg">
54
+ <link rel="preconnect" href="https://fonts.googleapis.com">
55
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
56
+ <link
57
+ href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&display=swap"
58
+ rel="stylesheet">
59
+ <link rel="stylesheet" href="<%= url_for('/css/main.css') %>">
60
+ <% if (theme.lazyload && theme.lazyload.enable) { %>
61
+ <link rel="stylesheet" href="<%= url_for('/css/lazyload.css') %>">
30
62
  <% } %>
31
- <% } %>
32
- <%- partial('_partial/open-graph') %>
33
- <% if (theme.canonical && theme.canonical.enable && typeof autoCanonical==='function' ) { %>
34
- <%- autoCanonical(config, page) %>
35
- <% } %>
36
- <script>
37
- (function () {
38
- var savedTheme = localStorage.getItem('theme');
39
- if (savedTheme === 'dark') {
40
- document.documentElement.setAttribute('data-theme', 'dark');
41
- } else if (savedTheme === null && window.matchMedia('(prefers-color-scheme: dark)').matches) {
42
- document.documentElement.setAttribute('data-theme', 'dark');
43
- }
44
- })();
45
- </script>
63
+ <% if (is_post()) { %>
64
+ <link rel="stylesheet" href="<%= url_for('/css/markdown.css') %>">
65
+ <link rel="stylesheet" href="<%= url_for('/css/highlight.css') %>">
66
+ <link rel="stylesheet" href="<%= url_for('/css/admonition.css') %>">
67
+ <% if (theme.twikoo && theme.twikoo.enable) { %>
68
+ <link rel="stylesheet" href="<%= url_for('/css/twikoo.css') %>">
69
+ <% } %>
70
+ <% } %>
71
+ <%- partial('_partial/open-graph') %>
72
+ <% if (theme.canonical && theme.canonical.enable && typeof autoCanonical==='function' ) { %>
73
+ <%- autoCanonical(config, page) %>
74
+ <% } %>
75
+ <script>
76
+ (function () {
77
+ var savedTheme = localStorage.getItem('theme');
78
+ if (savedTheme === 'dark') {
79
+ document.documentElement.setAttribute('data-theme', 'dark');
80
+ } else if (savedTheme === null && window.matchMedia('(prefers-color-scheme: dark)').matches) {
81
+ document.documentElement.setAttribute('data-theme', 'dark');
82
+ }
83
+ })();
84
+ </script>
46
85
  </head>
47
86
 
48
87
  <body>
@@ -51,6 +90,14 @@
51
90
  <div class="app__circle app__circle--1"></div>
52
91
  <div class="app__circle app__circle--2"></div>
53
92
  <div class="app__circle app__circle--3"></div>
93
+ <div class="app__particle app__particle--1"></div>
94
+ <div class="app__particle app__particle--2"></div>
95
+ <div class="app__particle app__particle--3"></div>
96
+ <div class="app__particle app__particle--4"></div>
97
+ <div class="app__particle app__particle--5"></div>
98
+ <div class="app__particle app__particle--6"></div>
99
+ <div class="app__particle app__particle--7"></div>
100
+ <div class="app__particle app__particle--8"></div>
54
101
  </div>
55
102
  <%- partial('_partial/header') %>
56
103
  <main class="main" id="pjax-container">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hexo-theme-nblog",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "A lightweight and minimalist blog theme",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -3,38 +3,38 @@
3
3
  const path = require('path');
4
4
  const themePkg = require('../package.json');
5
5
 
6
- hexo.extend.helper.register('theme_version', function() {
6
+ hexo.extend.helper.register('theme_version', function () {
7
7
  return 'v' + themePkg.version;
8
8
  });
9
9
 
10
- hexo.extend.filter.register('after_post_render', function(data) {
10
+ hexo.extend.filter.register('after_post_render', function (data) {
11
11
  const lazyload = hexo.theme.config.lazyload;
12
-
12
+
13
13
  if (!lazyload || !lazyload.enable) {
14
14
  return data;
15
15
  }
16
-
16
+
17
17
  if (data.content) {
18
- data.content = data.content.replace(/<img([^>]*)src=["']([^"']+)["']([^>]*)>/gi, function(match, before, src, after) {
18
+ data.content = data.content.replace(/<img([^>]*)src=["']([^"']+)["']([^>]*)>/gi, function (match, before, src, after) {
19
19
  if (match.includes('data-src=')) {
20
20
  return match;
21
21
  }
22
-
22
+
23
23
  let newAfter = after;
24
24
  if (!newAfter.includes('class=')) {
25
25
  newAfter = ' class=""' + newAfter;
26
26
  }
27
-
27
+
28
28
  const mediumZoom = hexo.theme.config.medium_zoom;
29
29
  if (mediumZoom && mediumZoom.enable) {
30
30
  if (!newAfter.includes('data-zoomable')) {
31
31
  newAfter = newAfter.replace(/class=["']([^"']*)["']/, 'class="$1" data-zoomable');
32
32
  }
33
33
  }
34
-
34
+
35
35
  return '<img' + before + 'data-src="' + src + '"' + newAfter + '>';
36
36
  });
37
37
  }
38
-
38
+
39
39
  return data;
40
40
  });
@@ -124,7 +124,7 @@ img[data-src].lazyload-image {
124
124
 
125
125
  .friend-card__avatar .lazyload-wrapper {
126
126
  width: 100%;
127
- height: 100%;
127
+ height: 101%;
128
128
  border-radius: inherit;
129
129
  }
130
130
 
@@ -1,7 +1,4 @@
1
1
  :root {
2
- --primary-color: #FF7F78;
3
- --primary-light: #FFE8E6;
4
- --primary-dark: #D6665F;
5
2
  --secondary-color: #625B71;
6
3
  --surface-color: rgba(255, 255, 255, 0.45);
7
4
  --surface-color-rgb: 255, 255, 255;
@@ -31,13 +28,21 @@
31
28
  --sidebar-width: 280px;
32
29
  --content-max-width: 1200px;
33
30
  --border-color: rgba(0, 0, 0, 0.1);
34
- --btn-plain-bg-hover: rgba(255, 127, 120, 0.5);
31
+ }
32
+
33
+ .sr-only {
34
+ position: absolute;
35
+ width: 1px;
36
+ height: 1px;
37
+ padding: 0;
38
+ margin: -1px;
39
+ overflow: hidden;
40
+ clip: rect(0, 0, 0, 0);
41
+ white-space: nowrap;
42
+ border: 0;
35
43
  }
36
44
 
37
45
  [data-theme="dark"] {
38
- --primary-color: #FF9B95;
39
- --primary-light: #8C3D37;
40
- --primary-dark: #FFE8E6;
41
46
  --surface-color: rgba(30, 30, 30, 0.45);
42
47
  --surface-color-rgb: 30, 30, 30;
43
48
  --background-color: #1C1B1F;
@@ -181,6 +186,7 @@ button {
181
186
  bottom: 0;
182
187
  z-index: 0;
183
188
  pointer-events: none;
189
+ overflow: hidden;
184
190
  }
185
191
 
186
192
  .app__circle {
@@ -220,6 +226,70 @@ button {
220
226
  animation-delay: -4s;
221
227
  }
222
228
 
229
+ .app__particle {
230
+ position: absolute;
231
+ border-radius: 50%;
232
+ background: var(--primary-color);
233
+ opacity: 0;
234
+ animation: particleFloat 20s linear infinite;
235
+ }
236
+
237
+ .app__particle--1 {
238
+ width: 6px;
239
+ height: 6px;
240
+ left: 10%;
241
+ animation-delay: 0s;
242
+ }
243
+
244
+ .app__particle--2 {
245
+ width: 4px;
246
+ height: 4px;
247
+ left: 20%;
248
+ animation-delay: -5s;
249
+ }
250
+
251
+ .app__particle--3 {
252
+ width: 8px;
253
+ height: 8px;
254
+ left: 30%;
255
+ animation-delay: -10s;
256
+ }
257
+
258
+ .app__particle--4 {
259
+ width: 5px;
260
+ height: 5px;
261
+ left: 40%;
262
+ animation-delay: -15s;
263
+ }
264
+
265
+ .app__particle--5 {
266
+ width: 7px;
267
+ height: 7px;
268
+ left: 50%;
269
+ animation-delay: -7s;
270
+ }
271
+
272
+ .app__particle--6 {
273
+ width: 4px;
274
+ height: 4px;
275
+ left: 60%;
276
+ animation-delay: -12s;
277
+ }
278
+
279
+ .app__particle--7 {
280
+ width: 6px;
281
+ height: 6px;
282
+ left: 70%;
283
+ animation-delay: -3s;
284
+ }
285
+
286
+ .app__particle--8 {
287
+ width: 5px;
288
+ height: 5px;
289
+ left: 80%;
290
+ animation-delay: -18s;
291
+ }
292
+
223
293
  @keyframes float {
224
294
 
225
295
  0%,
@@ -240,6 +310,26 @@ button {
240
310
  }
241
311
  }
242
312
 
313
+ @keyframes particleFloat {
314
+ 0% {
315
+ transform: translateY(100vh) rotate(0deg);
316
+ opacity: 0;
317
+ }
318
+
319
+ 10% {
320
+ opacity: 0.4;
321
+ }
322
+
323
+ 90% {
324
+ opacity: 0.4;
325
+ }
326
+
327
+ 100% {
328
+ transform: translateY(-100px) rotate(360deg);
329
+ opacity: 0;
330
+ }
331
+ }
332
+
243
333
  .main {
244
334
  flex: 1;
245
335
  padding-top: var(--header-height);
@@ -527,7 +617,7 @@ button {
527
617
  display: grid;
528
618
  grid-template-columns: 1fr var(--sidebar-width);
529
619
  gap: 24px;
530
- padding: 24px 0;
620
+ padding: 0 0 24px 0;
531
621
  }
532
622
 
533
623
  .tag-posts-page,
@@ -658,6 +748,97 @@ button {
658
748
  gap: 20px;
659
749
  }
660
750
 
751
+ .notification-banner {
752
+ padding: 0;
753
+ overflow: hidden;
754
+ }
755
+
756
+ .notification-banner__inner {
757
+ position: relative;
758
+ }
759
+
760
+ .notification-banner__content {
761
+ position: relative;
762
+ min-height: 44px;
763
+ cursor: grab;
764
+ user-select: none;
765
+ transition: height 0.3s ease;
766
+ }
767
+
768
+ .notification-banner__content:active {
769
+ cursor: grabbing;
770
+ }
771
+
772
+ .notification-banner__item {
773
+ position: absolute;
774
+ top: 0;
775
+ left: 0;
776
+ right: 0;
777
+ display: flex;
778
+ align-items: center;
779
+ justify-content: center;
780
+ padding: 12px 16px;
781
+ opacity: 0;
782
+ transform: translateX(20px);
783
+ transition: opacity var(--transition-normal), transform var(--transition-normal);
784
+ pointer-events: none;
785
+ }
786
+
787
+ .notification-banner__item.is-active {
788
+ position: relative;
789
+ opacity: 1;
790
+ transform: translateX(0);
791
+ pointer-events: auto;
792
+ }
793
+
794
+ .notification-banner__message {
795
+ flex: 1;
796
+ font-size: 13px;
797
+ line-height: 1.5;
798
+ color: var(--text-primary);
799
+ text-align: center;
800
+ }
801
+
802
+ .notification-banner__message a {
803
+ position: relative;
804
+ z-index: 1;
805
+ transition: color var(--transition-fast);
806
+ bottom: -0.55em;
807
+ right: 0.1em;
808
+ word-break: break-word;
809
+ }
810
+
811
+ .notification-banner__indicators {
812
+ display: flex;
813
+ justify-content: center;
814
+ gap: 6px;
815
+ padding: 0 16px 12px;
816
+ }
817
+
818
+ .notification-banner__indicator {
819
+ width: 6px;
820
+ height: 6px;
821
+ border-radius: 50%;
822
+ background: var(--border-color);
823
+ cursor: pointer;
824
+ transition: all var(--transition-fast);
825
+ }
826
+
827
+ .notification-banner__indicator:hover {
828
+ background: var(--text-secondary);
829
+ }
830
+
831
+ .notification-banner__indicator.is-active {
832
+ background: var(--primary-color);
833
+ width: 16px;
834
+ border-radius: 3px;
835
+ }
836
+
837
+ .notification-banner--mobile {
838
+ display: none;
839
+ margin-bottom: 16px;
840
+ }
841
+
661
842
  .author-card {
662
843
  padding: 24px;
663
844
  text-align: center;
@@ -1561,6 +1742,14 @@ button {
1561
1742
  .sidebar {
1562
1743
  display: none;
1563
1744
  }
1745
+
1746
+ .notification-banner {
1747
+ display: none;
1748
+ }
1749
+
1750
+ .notification-banner--mobile {
1751
+ display: block;
1752
+ }
1564
1753
  }
1565
1754
 
1566
1755
  @media (max-width: 768px) {
@@ -1634,6 +1823,15 @@ button {
1634
1823
  .categories-grid {
1635
1824
  grid-template-columns: 1fr;
1636
1825
  }
1826
+
1827
+ .friends-grid {
1828
+ grid-template-columns: repeat(1, 1fr);
1829
+ }
1830
+
1831
+ .tag-posts-page .posts-list,
1832
+ .category-posts-page .posts-list {
1833
+ grid-template-columns: 1fr;
1834
+ }
1637
1835
  }
1638
1836
 
1639
1837
  ::-webkit-scrollbar {
@@ -111,7 +111,7 @@
111
111
  position: relative;
112
112
  z-index: 1;
113
113
  transition: color var(--transition-fast);
114
- bottom: -0.5em;
114
+ bottom: -0.55em;
115
115
  right: 0.1em;
116
116
  word-break: break-word;
117
117
  }
package/source/js/main.js CHANGED
@@ -7,6 +7,174 @@
7
7
  var menuBtn = document.getElementById('menuBtn');
8
8
  var themeToggle = document.getElementById('themeToggle');
9
9
 
10
+ function initNotificationBanner(bannerId) {
11
+ const banner = document.getElementById(bannerId);
12
+ if (!banner) return;
13
+
14
+ const content = banner.querySelector('.notification-banner__content');
15
+ const items = banner.querySelectorAll('.notification-banner__item');
16
+ const indicatorsContainer = banner.querySelector('.notification-banner__indicators');
17
+ const indicators = banner.querySelectorAll('.notification-banner__indicator');
18
+ if (items.length <= 1) {
19
+ if (indicatorsContainer) indicatorsContainer.style.display = 'none';
20
+ return;
21
+ }
22
+
23
+ const interval = parseInt(banner.dataset.interval) || 5000;
24
+ let currentIndex = 0;
25
+ let autoPlayTimer = null;
26
+
27
+ let touchStartX = 0;
28
+ let touchEndX = 0;
29
+ let isSwiping = false;
30
+
31
+ function showItem(index, direction = 'next') {
32
+ const prevIndex = currentIndex;
33
+ items.forEach((item, i) => {
34
+ if (i === index) {
35
+ item.classList.add('is-active');
36
+ item.style.transform = 'translateX(0)';
37
+ item.style.opacity = '1';
38
+ } else if (i === prevIndex) {
39
+ item.classList.remove('is-active');
40
+ const translateDir = direction === 'next' ? '-20px' : '20px';
41
+ item.style.transform = `translateX(${translateDir})`;
42
+ item.style.opacity = '0';
43
+ } else {
44
+ item.classList.remove('is-active');
45
+ item.style.transform = 'translateX(20px)';
46
+ item.style.opacity = '0';
47
+ }
48
+ });
49
+
50
+ const activeItem = items[index];
51
+ const height = activeItem.offsetHeight;
52
+ content.style.height = height + 'px';
53
+
54
+ indicators.forEach((indicator, i) => {
55
+ indicator.classList.toggle('is-active', i === index);
56
+ });
57
+ currentIndex = index;
58
+ }
59
+
60
+ function nextItem() {
61
+ const next = (currentIndex + 1) % items.length;
62
+ showItem(next, 'next');
63
+ }
64
+
65
+ function prevItem() {
66
+ const prev = (currentIndex - 1 + items.length) % items.length;
67
+ showItem(prev, 'prev');
68
+ }
69
+
70
+ function startAutoPlay() {
71
+ stopAutoPlay();
72
+ autoPlayTimer = setInterval(nextItem, interval);
73
+ }
74
+
75
+ function stopAutoPlay() {
76
+ if (autoPlayTimer) {
77
+ clearInterval(autoPlayTimer);
78
+ autoPlayTimer = null;
79
+ }
80
+ }
81
+
82
+ function handleTouchStart(e) {
83
+ touchStartX = e.touches[0].clientX;
84
+ isSwiping = true;
85
+ stopAutoPlay();
86
+ }
87
+
88
+ function handleTouchMove(e) {
89
+ if (!isSwiping) return;
90
+ touchEndX = e.touches[0].clientX;
91
+ }
92
+
93
+ function handleTouchEnd() {
94
+ if (!isSwiping) return;
95
+ isSwiping = false;
96
+
97
+ const diff = touchStartX - touchEndX;
98
+ const threshold = 50;
99
+
100
+ if (Math.abs(diff) > threshold) {
101
+ if (diff > 0) {
102
+ nextItem();
103
+ } else {
104
+ prevItem();
105
+ }
106
+ }
107
+
108
+ touchStartX = 0;
109
+ touchEndX = 0;
110
+ startAutoPlay();
111
+ }
112
+
113
+ function handleMouseDown(e) {
114
+ touchStartX = e.clientX;
115
+ isSwiping = true;
116
+ stopAutoPlay();
117
+ e.preventDefault();
118
+ }
119
+
120
+ function handleMouseMove(e) {
121
+ if (!isSwiping) return;
122
+ touchEndX = e.clientX;
123
+ }
124
+
125
+ function handleMouseUp() {
126
+ if (!isSwiping) return;
127
+ isSwiping = false;
128
+
129
+ const diff = touchStartX - touchEndX;
130
+ const threshold = 50;
131
+
132
+ if (Math.abs(diff) > threshold) {
133
+ if (diff > 0) {
134
+ nextItem();
135
+ } else {
136
+ prevItem();
137
+ }
138
+ }
139
+
140
+ touchStartX = 0;
141
+ touchEndX = 0;
142
+ startAutoPlay();
143
+ }
144
+
145
+ indicators.forEach((indicator, index) => {
146
+ indicator.addEventListener('click', () => {
147
+ const direction = index > currentIndex ? 'next' : 'prev';
148
+ showItem(index, direction);
149
+ startAutoPlay();
150
+ });
151
+ });
152
+
153
+ content.addEventListener('touchstart', handleTouchStart, { passive: true });
154
+ content.addEventListener('touchmove', handleTouchMove, { passive: true });
155
+ content.addEventListener('touchend', handleTouchEnd);
156
+
157
+ content.addEventListener('mousedown', handleMouseDown);
158
+ content.addEventListener('mousemove', handleMouseMove);
159
+ content.addEventListener('mouseup', handleMouseUp);
160
+ content.addEventListener('mouseleave', handleMouseUp);
161
+
162
+ banner.addEventListener('mouseenter', stopAutoPlay);
163
+ banner.addEventListener('mouseleave', startAutoPlay);
164
+
165
+ const firstItem = items[0];
166
+ if (firstItem) {
167
+ content.style.height = firstItem.offsetHeight + 'px';
168
+ }
169
+
170
+ startAutoPlay();
171
+ }
172
+
173
+ function initNotifications() {
174
+ initNotificationBanner('notificationBanner');
175
+ initNotificationBanner('notificationBannerMobile');
176
+ }
177
+
10
178
  function initTheme() {
11
179
  const savedTheme = localStorage.getItem(themeKey);
12
180
  if (savedTheme === 'dark') {
@@ -244,9 +412,7 @@
244
412
  listItemClass: 'toc-list-item',
245
413
  activeListItemClass: 'is-active-li',
246
414
  collapseDepth: 0,
247
- scrollSmooth: true,
248
- scrollSmoothDuration: 420,
249
- scrollSmoothOffset: -80,
415
+ scrollSmooth: false,
250
416
  headingsOffset: 80,
251
417
  throttleTimeout: 50,
252
418
  disableTocScrollSync: false
@@ -254,6 +420,45 @@
254
420
 
255
421
  initTocBoundaryDetection(toc, postBody);
256
422
  initTocToggle(toc, tocToggle);
423
+ initTocSmoothScroll(tocContent);
424
+ });
425
+ }
426
+
427
+ function initTocSmoothScroll(tocContent) {
428
+ if (!tocContent) return;
429
+
430
+ tocContent.addEventListener('click', function (e) {
431
+ const link = e.target.closest('a[href^="#"]');
432
+ if (!link) return;
433
+
434
+ e.preventDefault();
435
+ const targetId = link.getAttribute('href').slice(1);
436
+ const targetElement = document.getElementById(targetId);
437
+
438
+ if (targetElement) {
439
+ const offset = 80;
440
+ const targetPosition = targetElement.getBoundingClientRect().top + window.scrollY - offset;
441
+ const startPosition = window.scrollY;
442
+ const distance = targetPosition - startPosition;
443
+ const duration = 400;
444
+ let startTime = null;
445
+
446
+ function linearScroll(currentTime) {
447
+ if (!startTime) startTime = currentTime;
448
+ const elapsed = currentTime - startTime;
449
+ const progress = Math.min(elapsed / duration, 1);
450
+
451
+ window.scrollTo(0, startPosition + distance * progress);
452
+
453
+ if (progress < 1) {
454
+ requestAnimationFrame(linearScroll);
455
+ }
456
+ }
457
+
458
+ requestAnimationFrame(linearScroll);
459
+
460
+ history.pushState(null, null, '#' + targetId);
461
+ }
257
462
  });
258
463
  }
259
464
 
@@ -353,7 +558,7 @@
353
558
  img.classList.add('medium-zoom-image');
354
559
  });
355
560
 
356
- document.querySelectorAll('[data-zoomable]').forEach(function(img) {
561
+ document.querySelectorAll('[data-zoomable]').forEach(function (img) {
357
562
  if (!img.classList.contains('medium-zoom-image')) {
358
563
  img.classList.add('medium-zoom-image');
359
564
  }
@@ -385,6 +590,7 @@
385
590
  initCodeBlocks();
386
591
  initToc();
387
592
  initMediumZoom();
593
+ initNotifications();
388
594
 
389
595
  window.scrollTo(0, 0);
390
596
  }
@@ -420,6 +626,7 @@
420
626
 
421
627
  initCodeBlocks();
422
628
  initToc();
629
+ initNotifications();
423
630
  }
424
631
 
425
632
  function handleOutsideClick(e) {