hexo-bb-channel 0.1.1 → 0.1.3

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/CHANGELOG.md ADDED
@@ -0,0 +1,26 @@
1
+ # Changelog
2
+
3
+ ## 0.1.3 - 2026-07-20
4
+
5
+ ### Fixed
6
+
7
+ - Unified card hover and mobile auto-active styling so both states use the same transition timing, offset, border, background, and shadow behavior.
8
+ - Kept active-card placeholder outlines visible on mobile while image viewer open states still suppress the card offset.
9
+
10
+ ### Changed
11
+
12
+ - Exposed card interaction tokens as CSS variables so themes can tune the visual style without overriding plugin state selectors.
13
+
14
+ ## 0.1.2 - 2026-07-20
15
+
16
+ ### Fixed
17
+
18
+ - Prevented multi-image expand/collapse from shifting the page scroll position.
19
+ - Improved mobile card auto-selection so the card nearest the reading position is selected instead of a taller partially visible card.
20
+ - Restored the mobile active-card placeholder outline while keeping the selected card offset visible.
21
+ - Made bb title and content typography theme-driven through CSS variables, avoiding hard-coded theme font assumptions.
22
+
23
+ ### Changed
24
+
25
+ - Mobile card active styling now works without hover and keeps the original dashed placeholder visible.
26
+ - Image carousel positioning uses non-smooth initial placement to avoid jumpy first-open behavior.
package/README.md CHANGED
@@ -11,8 +11,6 @@ English | [Chinese](./README.zh-CN.md)
11
11
 
12
12
  `hexo-bb-channel` is a small Hexo plugin that creates a `/bb/` page shell and loads posts from a separate API at runtime. Your blog stays static, while Telegram updates can appear without rebuilding the whole site.
13
13
 
14
- Live sample: [https://www.kaaaaai.cn/bb/](https://www.kaaaaai.cn/bb/)
15
-
16
14
  ## Preview
17
15
 
18
16
  Live demo: [https://www.kaaaaai.cn/bb/](https://www.kaaaaai.cn/bb/)
@@ -25,7 +23,7 @@ Live demo: [https://www.kaaaaai.cn/bb/](https://www.kaaaaai.cn/bb/)
25
23
  - **Dynamic content on static blogs**: Telegram posts are fetched in the browser from the API backend.
26
24
  - **No Telegram token in the blog**: the Hexo side only needs a public API URL.
27
25
  - **Media-friendly timeline**: supports text, real Telegram hashtag entities, images, files, pagination, and image preview interactions.
28
- - **Comments still work**: the generated page is a normal Hexo page and keeps `comments: true`.
26
+ - **Theme comments remain available**: the generated page is a standard Hexo page with `comments: true`; whether comments render depends on your theme and comment system.
29
27
 
30
28
  ## Quick Start
31
29
 
@@ -40,6 +38,8 @@ Deploy the companion API first:
40
38
  Backend repo: [Kaaaaai/kaaaaai.tools.channel-api](https://github.com/Kaaaaai/kaaaaai.tools.channel-api)
41
39
  See the API README for detailed Telegram channel creation steps.
42
40
 
41
+ For production, set `ALLOWED_ORIGINS` to your blog's exact origin, for example `https://www.example.com`, so the API accepts requests from the generated page.
42
+
43
43
  ### 2. Install the Hexo plugin
44
44
 
45
45
  Install the latest stable release from npm:
@@ -94,7 +94,7 @@ http://localhost:4000/bb/
94
94
  | `route` | `bb/` | Output route, for example `moments/`. |
95
95
  | `title` | `moments` | Page title inside the content area. |
96
96
  | `description` | empty | Subtitle under the title. |
97
- | `api_base` | empty | Base URL of `kaaaaai.tools.channel-api`. |
97
+ | `api_base` | empty | Required in client mode. Base URL of `kaaaaai.tools.channel-api`; when omitted, the page shows `Missing bb_channel.api_base`. |
98
98
  | `page_size` | `20` | Posts requested per page. |
99
99
 
100
100
  ## How It Works
@@ -198,7 +198,6 @@ npm test
198
198
  ## Related
199
199
 
200
200
  - API backend: [Kaaaaai/kaaaaai.tools.channel-api](https://github.com/Kaaaaai/kaaaaai.tools.channel-api)
201
- - Live sample: [https://www.kaaaaai.cn/bb/](https://www.kaaaaai.cn/bb/)
202
201
 
203
202
  ## License
204
203
 
package/README.zh-CN.md CHANGED
@@ -11,8 +11,6 @@
11
11
 
12
12
  `hexo-bb-channel` 是一个轻量 Hexo 插件:它只生成 `/bb/` 页面壳,内容在浏览器端从独立 API 拉取。这样博客仍然是静态站,但 Telegram Channel 更新后不需要重新 `hexo generate`。
13
13
 
14
- 在线示例:[https://www.kaaaaai.cn/bb/](https://www.kaaaaai.cn/bb/)
15
-
16
14
  ## 效果预览
17
15
 
18
16
  在线示例:[https://www.kaaaaai.cn/bb/](https://www.kaaaaai.cn/bb/)
@@ -25,7 +23,7 @@
25
23
  - **静态博客里的动态内容**:页面访问时从 API 拉取 Telegram 内容。
26
24
  - **博客侧不放 Telegram 凭证**:Hexo 只需要配置一个 API 地址。
27
25
  - **适合碎片流展示**:支持正文、Telegram 原生 hashtag entity、图片、文件、分页、图片预览交互。
28
- - **保留评论能力**:生成的是标准 Hexo page,默认开启 `comments: true`。
26
+ - **保留主题评论能力**:生成的是标准 Hexo page,并设置 `comments: true`;最终是否显示评论取决于主题和评论系统。
29
27
 
30
28
  ## 快速开始
31
29
 
@@ -40,6 +38,8 @@
40
38
  后端仓库:[Kaaaaai/kaaaaai.tools.channel-api](https://github.com/Kaaaaai/kaaaaai.tools.channel-api)
41
39
  详细的 Telegram 频道创建步骤见 API 仓库 README。
42
40
 
41
+ 生产环境请将 `ALLOWED_ORIGINS` 设置为博客的完整 Origin,例如 `https://www.example.com`,确保生成页面能够请求 API。
42
+
43
43
  ### 2. 安装 Hexo 插件
44
44
 
45
45
  从 npm 安装最新稳定版:
@@ -94,7 +94,7 @@ http://localhost:4000/bb/
94
94
  | `route` | `bb/` | 页面路由,例如 `moments/`。 |
95
95
  | `title` | `moments` | 页面内容区标题。 |
96
96
  | `description` | 空 | 页面描述。 |
97
- | `api_base` | 空 | `kaaaaai.tools.channel-api` 的 API 根地址。 |
97
+ | `api_base` | 空 | client 模式必填。`kaaaaai.tools.channel-api` 的 API 根地址;未配置时页面会显示 `Missing bb_channel.api_base`。 |
98
98
  | `page_size` | `20` | 每页拉取数量。 |
99
99
 
100
100
  ## 工作原理
@@ -198,7 +198,6 @@ npm test
198
198
  ## 相关项目
199
199
 
200
200
  - API 后端:[Kaaaaai/kaaaaai.tools.channel-api](https://github.com/Kaaaaai/kaaaaai.tools.channel-api)
201
- - 在线示例:[https://www.kaaaaai.cn/bb/](https://www.kaaaaai.cn/bb/)
202
201
 
203
202
  ## License
204
203
 
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "hexo-bb-channel",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Render Telegram channel microblog timelines in Hexo using a dynamic API backend.",
5
5
  "main": "index.js",
6
6
  "files": [
7
7
  "index.js",
8
8
  "src",
9
+ "CHANGELOG.md",
9
10
  "README.md",
10
11
  "README.zh-CN.md",
11
12
  "LICENSE"
@@ -28,11 +29,11 @@
28
29
  },
29
30
  "repository": {
30
31
  "type": "git",
31
- "url": "git+https://github.com/Kaaaaai/kaaaaai.tools.hexo-bb-channel.git"
32
+ "url": "git+https://github.com/kaaaaai/kaaaaai.tools.hexo-bb-channel.git"
32
33
  },
33
- "homepage": "https://github.com/Kaaaaai/kaaaaai.tools.hexo-bb-channel#readme",
34
+ "homepage": "https://github.com/kaaaaai/kaaaaai.tools.hexo-bb-channel#readme",
34
35
  "bugs": {
35
- "url": "https://github.com/Kaaaaai/kaaaaai.tools.hexo-bb-channel/issues"
36
+ "url": "https://github.com/kaaaaai/kaaaaai.tools.hexo-bb-channel/issues"
36
37
  },
37
38
  "author": "Kaaaaai",
38
39
  "license": "MIT",
@@ -26,6 +26,10 @@ function renderClientScript() {
26
26
  const feed = root.querySelector('[data-bb-channel-feed]');
27
27
  const pager = root.querySelector('[data-bb-channel-pagination]');
28
28
  const status = root.querySelector('[data-bb-channel-status]');
29
+ let bbActiveCardObserver = null;
30
+ let bbActiveCardRaf = 0;
31
+ let bbActiveCardListening = false;
32
+ const bbActiveCardQuery = window.matchMedia ? window.matchMedia('(max-width: 640px)') : null;
29
33
 
30
34
  const escapeHtml = (value = '') => String(value)
31
35
  .replace(/&/g, '&')
@@ -114,6 +118,121 @@ function renderClientScript() {
114
118
  });
115
119
  };
116
120
 
121
+ const positionImageTrack = (track, index, useSmoothScroll) => {
122
+ const left = track.clientWidth * index;
123
+ track.scrollTo({ left, behavior: useSmoothScroll ? 'smooth' : 'auto' });
124
+ if (!useSmoothScroll) track.scrollLeft = left;
125
+ };
126
+
127
+ const clearMobileActiveCards = () => {
128
+ feed.querySelectorAll('.bb-channel-card[data-bb-card-active]').forEach((card) => {
129
+ delete card.dataset.bbCardActive;
130
+ });
131
+ };
132
+
133
+ const calculateCardVisibleArea = (card) => {
134
+ const surface = card.querySelector('.bb-channel-card-surface') || card;
135
+ const rect = surface.getBoundingClientRect();
136
+ const viewportWidth = window.innerWidth || document.documentElement.clientWidth || 0;
137
+ const viewportHeight = window.innerHeight || document.documentElement.clientHeight || 0;
138
+ const width = Math.max(0, Math.min(rect.right, viewportWidth) - Math.max(rect.left, 0));
139
+ const height = Math.max(0, Math.min(rect.bottom, viewportHeight) - Math.max(rect.top, 0));
140
+ return width * height;
141
+ };
142
+
143
+ const calculateCardVisibleRatio = (card) => {
144
+ const surface = card.querySelector('.bb-channel-card-surface') || card;
145
+ const rect = surface.getBoundingClientRect();
146
+ const area = calculateCardVisibleArea(card);
147
+ const total = Math.max(1, rect.width * rect.height);
148
+ return area / total;
149
+ };
150
+
151
+ const calculateCardActiveDistance = (card) => {
152
+ const surface = card.querySelector('.bb-channel-card-surface') || card;
153
+ const rect = surface.getBoundingClientRect();
154
+ const viewportHeight = window.innerHeight || document.documentElement.clientHeight || 0;
155
+ const viewportActiveLine = viewportHeight * .52;
156
+ const visibleTop = Math.max(rect.top, 0);
157
+ const visibleBottom = Math.min(rect.bottom, viewportHeight);
158
+ const visibleCenter = (visibleTop + visibleBottom) / 2;
159
+ return Math.abs(visibleCenter - viewportActiveLine);
160
+ };
161
+
162
+ const activateLargestVisibleCard = () => {
163
+ bbActiveCardRaf = 0;
164
+ if (!bbActiveCardQuery || !bbActiveCardQuery.matches) {
165
+ clearMobileActiveCards();
166
+ return;
167
+ }
168
+ let activeCard = null;
169
+ let bestDistance = Infinity;
170
+ let bestRatio = 0;
171
+ feed.querySelectorAll('.bb-channel-card').forEach((card) => {
172
+ if (card.dataset.bbViewerOpen === 'true') return;
173
+ const visibleArea = calculateCardVisibleArea(card);
174
+ const visibleRatio = calculateCardVisibleRatio(card);
175
+ if (visibleArea <= 0 || visibleRatio < .08) return;
176
+ const distance = calculateCardActiveDistance(card);
177
+ if (distance < bestDistance || (Math.abs(distance - bestDistance) < 1 && visibleRatio > bestRatio)) {
178
+ bestDistance = distance;
179
+ bestRatio = visibleRatio;
180
+ activeCard = card;
181
+ }
182
+ });
183
+ feed.querySelectorAll('.bb-channel-card').forEach((card) => {
184
+ if (card === activeCard) {
185
+ card.dataset.bbCardActive = 'true';
186
+ } else {
187
+ delete card.dataset.bbCardActive;
188
+ }
189
+ });
190
+ };
191
+
192
+ const scheduleMobileActiveCardUpdate = () => {
193
+ if (bbActiveCardRaf) return;
194
+ bbActiveCardRaf = window.requestAnimationFrame(activateLargestVisibleCard);
195
+ };
196
+
197
+ const stopMobileActiveCardTracking = () => {
198
+ if (bbActiveCardObserver) {
199
+ bbActiveCardObserver.disconnect();
200
+ bbActiveCardObserver = null;
201
+ }
202
+ if (bbActiveCardRaf) {
203
+ window.cancelAnimationFrame(bbActiveCardRaf);
204
+ bbActiveCardRaf = 0;
205
+ }
206
+ if (bbActiveCardListening) {
207
+ window.removeEventListener('scroll', scheduleMobileActiveCardUpdate);
208
+ window.removeEventListener('resize', scheduleMobileActiveCardUpdate);
209
+ bbActiveCardListening = false;
210
+ }
211
+ };
212
+
213
+ const setupMobileActiveCard = () => {
214
+ stopMobileActiveCardTracking();
215
+ clearMobileActiveCards();
216
+ if (!bbActiveCardQuery || !bbActiveCardQuery.matches) return;
217
+ window.addEventListener('scroll', scheduleMobileActiveCardUpdate, { passive: true });
218
+ window.addEventListener('resize', scheduleMobileActiveCardUpdate);
219
+ bbActiveCardListening = true;
220
+ if ('IntersectionObserver' in window) {
221
+ bbActiveCardObserver = new IntersectionObserver(scheduleMobileActiveCardUpdate, { threshold: [0, .15, .3, .45, .6, .75, .9, 1] });
222
+ feed.querySelectorAll('.bb-channel-card').forEach((card) => bbActiveCardObserver.observe(card));
223
+ }
224
+ scheduleMobileActiveCardUpdate();
225
+ };
226
+
227
+ if (bbActiveCardQuery) {
228
+ const handleActiveCardQueryChange = () => setupMobileActiveCard();
229
+ if (typeof bbActiveCardQuery.addEventListener === 'function') {
230
+ bbActiveCardQuery.addEventListener('change', handleActiveCardQueryChange);
231
+ } else if (typeof bbActiveCardQuery.addListener === 'function') {
232
+ bbActiveCardQuery.addListener(handleActiveCardQueryChange);
233
+ }
234
+ }
235
+
117
236
  const openImageViewer = (card, index) => {
118
237
  const viewer = card.querySelector('[data-bb-image-viewer]');
119
238
  const track = card.querySelector('[data-bb-image-track]');
@@ -121,6 +240,7 @@ function renderClientScript() {
121
240
  const count = card.querySelector('[data-bb-image-count]');
122
241
  if (!viewer || !track || !slides.length) return;
123
242
  const safeIndex = (Number(index) + slides.length) % slides.length;
243
+ const useSmoothScroll = card.dataset.bbViewerOpen === 'true';
124
244
  card.dataset.bbImageIndex = String(safeIndex);
125
245
  if (count) count.textContent = slides.length > 1 ? (safeIndex + 1) + ' / ' + slides.length : '';
126
246
  preserveCardViewportPosition(card, () => {
@@ -130,7 +250,7 @@ function renderClientScript() {
130
250
  hydrateImages(slides[safeIndex]);
131
251
  requestAnimationFrame(() => {
132
252
  card.dataset.bbViewerOpen = 'true';
133
- track.scrollTo({ left: track.clientWidth * safeIndex, behavior: 'smooth' });
253
+ positionImageTrack(track, safeIndex, useSmoothScroll);
134
254
  focusWithoutScroll(track);
135
255
  });
136
256
  });
@@ -199,6 +319,7 @@ function renderClientScript() {
199
319
  const data = await response.json();
200
320
  feed.innerHTML = (data.posts || []).map(renderCard).join('');
201
321
  hydrateImages(feed);
322
+ setupMobileActiveCard();
202
323
  status.textContent = '';
203
324
  pager.innerHTML = '';
204
325
  if (data.pagination && data.pagination.total > 1) {
@@ -268,26 +389,26 @@ function renderClientContent(config) {
268
389
  return normalizeHtml(`
269
390
  <style>
270
391
  .post-block:has(.bb-channel-portable) .post-title,.post-block:has(.bb-channel-portable) .post-meta-container,body:has(.bb-channel-portable) .post-toc-wrap{display:none}
271
- .bb-channel-portable{max-width:100%;margin:0 auto;color:#242424}
392
+ .bb-channel-portable{max-width:100%;margin:0 auto;color:#242424;--bb-card-transition:360ms cubic-bezier(.37,0,.63,1);--bb-card-offset-x:6px;--bb-card-offset-y:-6px;--bb-card-mobile-offset-x:4px;--bb-card-mobile-offset-y:-5px;--bb-card-placeholder-border:#d9d9d9;--bb-card-placeholder-bg:rgba(255,255,255,.2);--bb-card-surface-bg:rgba(255,255,255,.66);--bb-card-active-border:#e7b99d;--bb-card-active-bg:rgba(255,255,255,.86);--bb-card-active-shadow:0 14px 30px -28px rgba(15,23,42,.32)}
272
393
  .bb-channel-portable *{box-sizing:border-box}
273
394
  .bb-channel-portable .bb-channel-intro{position:relative;margin:0 0 1.55rem;padding:0;border-bottom:0}
274
- .bb-channel-portable .bb-channel-title{margin:0 0 .45rem;color:#202020;font-family:inherit;font-size:1.72rem;font-weight:700;line-height:1.2;letter-spacing:-.02em}
395
+ .bb-channel-portable .bb-channel-title{margin:0 0 .45rem;color:#202020;font-family:var(--bb-channel-title-font,inherit);font-size:1.72rem;font-weight:700;line-height:1.2;letter-spacing:0}
275
396
  .bb-channel-portable .bb-channel-intro-text{margin:0;color:#777;font-size:1rem;line-height:1.6;text-align:left}
276
397
  .bb-channel-portable .bb-channel-status{min-height:1.2rem;margin:.4rem 0 .8rem;color:#999}
277
398
  .bb-channel-portable .bb-channel-feed{position:relative;display:flex;flex-direction:column;gap:1.05rem;padding-left:2.1rem}
278
399
  .bb-channel-portable .bb-channel-feed::before{content:"";position:absolute;left:.22rem;top:.25rem;bottom:.25rem;width:1px;background:linear-gradient(to bottom,rgba(226,226,226,0),#e2e2e2 1.2rem,#e2e2e2 calc(100% - 1.2rem),rgba(226,226,226,0))}
279
400
  .bb-channel-portable .bb-channel-card{position:relative;border-radius:14px}
280
- .bb-channel-portable .bb-channel-card-placeholder{pointer-events:none;position:absolute;inset:0;border:1px dashed #d9d9d9;border-radius:14px;background:rgba(255,255,255,.2);opacity:0;transition:opacity 320ms cubic-bezier(.22,1,.36,1)}
281
- .bb-channel-portable .bb-channel-card-surface{position:relative;z-index:1;border:1px dashed #d9d9d9;border-radius:14px;background:rgba(255,255,255,.66);padding:1.45rem 1.65rem;box-shadow:none;transition:border-color 320ms cubic-bezier(.22,1,.36,1),background-color 320ms cubic-bezier(.22,1,.36,1),box-shadow 320ms cubic-bezier(.22,1,.36,1),transform 320ms cubic-bezier(.22,1,.36,1)}
282
- .bb-channel-portable .bb-channel-card:hover .bb-channel-card-placeholder{opacity:1}
283
- .bb-channel-portable .bb-channel-card:hover .bb-channel-card-surface{transform:translate(6px,-6px);border-style:solid;border-color:#e7b99d;background:rgba(255,255,255,.86);box-shadow:0 14px 30px -28px rgba(15,23,42,.32)}
401
+ .bb-channel-portable .bb-channel-card-placeholder{pointer-events:none;position:absolute;inset:0;border:1px dashed var(--bb-card-placeholder-border);border-radius:14px;background:var(--bb-card-placeholder-bg);opacity:0;transition:opacity var(--bb-card-transition)}
402
+ .bb-channel-portable .bb-channel-card-surface{position:relative;z-index:1;border:1px dashed var(--bb-card-placeholder-border);border-radius:14px;background:var(--bb-card-surface-bg);padding:1.45rem 1.65rem;box-shadow:none;transition:border-color var(--bb-card-transition),background-color var(--bb-card-transition),box-shadow var(--bb-card-transition),transform var(--bb-card-transition)}
403
+ .bb-channel-portable .bb-channel-card:hover .bb-channel-card-placeholder,.bb-channel-portable .bb-channel-card[data-bb-card-active="true"] .bb-channel-card-placeholder{opacity:1}
404
+ .bb-channel-portable .bb-channel-card:hover .bb-channel-card-surface,.bb-channel-portable .bb-channel-card[data-bb-card-active="true"] .bb-channel-card-surface{transform:translate(var(--bb-card-offset-x),var(--bb-card-offset-y));border-style:solid;border-color:var(--bb-card-active-border);background:var(--bb-card-active-bg);box-shadow:var(--bb-card-active-shadow)}
284
405
  .bb-channel-portable .bb-channel-dot{position:absolute;left:-2.16rem;top:1.7rem;z-index:2;width:.56rem;height:.56rem;border-radius:999px;background:#ff7900;box-shadow:0 0 0 .28rem rgba(255,121,0,.12)}
285
406
  .bb-channel-portable .bb-channel-meta{display:flex;align-items:center;gap:.7rem;margin:0 0 1.08rem;color:#737373;font-weight:500}
286
407
  .bb-channel-portable .bb-channel-time{color:inherit;text-decoration:none;border-bottom:0;font-size:.96rem;line-height:1.3}
287
408
  .bb-channel-portable .bb-channel-body-with-media{display:grid;grid-template-columns:minmax(0,1fr) minmax(10rem,13.5rem);gap:1.25rem;align-items:start}
288
409
  .bb-channel-portable .bb-channel-main{min-width:0}
289
- .bb-channel-portable .bb-channel-content{font-size:1rem;line-height:1.78;overflow-wrap:anywhere}
290
- .bb-channel-portable .bb-channel-content p{margin:0 0 .9em}
410
+ .bb-channel-portable .bb-channel-content{font-family:var(--bb-channel-content-font,inherit);font-size:1.02rem;line-height:1.82;letter-spacing:.01em;text-rendering:optimizeLegibility;overflow-wrap:anywhere}
411
+ .bb-channel-portable .bb-channel-content p{margin:0 0 .82em}
291
412
  .bb-channel-portable .bb-channel-content a{color:#e46f0a;border-bottom:1px solid rgba(228,111,10,.35);text-decoration:none}
292
413
  .bb-channel-portable .bb-channel-media-rail{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:.32rem;justify-self:end;width:min(13.5rem,100%);max-height:24rem;opacity:1;overflow:hidden;transform:translateY(0);filter:blur(0);transition:max-height 420ms cubic-bezier(.22,1,.36,1),opacity 420ms cubic-bezier(.22,1,.36,1),transform 420ms cubic-bezier(.22,1,.36,1),filter 420ms cubic-bezier(.22,1,.36,1);will-change:max-height,opacity,transform}
293
414
  .bb-channel-portable .bb-channel-media-thumb{position:relative;display:grid;min-height:0;aspect-ratio:1/1;place-items:center;border:1px solid #e7e7e7;border-radius:8px;background:#f8f8f8;padding:0;overflow:hidden;cursor:pointer}
@@ -298,6 +419,8 @@ function renderClientContent(config) {
298
419
  .bb-channel-portable .bb-channel-image-skeleton{position:absolute;inset:0;display:block;border-radius:inherit;background:linear-gradient(100deg,rgba(245,245,245,.78) 0%,rgba(233,233,233,.94) 45%,rgba(247,247,247,.8) 80%);background-size:220% 100%;animation:bb-channel-shimmer 1.15s ease-in-out infinite}
299
420
  .bb-channel-portable [data-bb-loaded="true"]>.bb-channel-image-skeleton{display:none}
300
421
  .bb-channel-portable [data-bb-loaded="error"]>.bb-channel-image-skeleton{background:#f4eeee}
422
+ .bb-channel-portable .bb-channel-card[data-bb-viewer-open="true"] .bb-channel-card-placeholder{opacity:0}
423
+ .bb-channel-portable .bb-channel-card[data-bb-viewer-open="true"] .bb-channel-card-surface{transform:none}
301
424
  .bb-channel-portable .bb-channel-card[data-bb-viewer-open="true"] .bb-channel-media-rail{max-height:0;opacity:0;transform:translateY(14px);filter:blur(2px);pointer-events:none}
302
425
  .bb-channel-portable .bb-channel-image-viewer{position:relative;margin-top:0;max-width:100%;max-height:0;overflow:hidden;opacity:0;transform:translateY(-14px);touch-action:pan-y;transition:max-height 420ms cubic-bezier(.22,1,.36,1),margin-top 420ms cubic-bezier(.22,1,.36,1),opacity 420ms cubic-bezier(.22,1,.36,1),transform 420ms cubic-bezier(.22,1,.36,1);will-change:max-height,opacity,transform}
303
426
  .bb-channel-portable .bb-channel-image-viewer[hidden]{display:none}
@@ -327,8 +450,8 @@ function renderClientContent(config) {
327
450
  .bb-channel-portable .bb-channel-pagination span{min-width:2.15rem;background:#333;color:#fff;border-color:#333}
328
451
  @media(max-width:720px){.bb-channel-portable .bb-channel-body-with-media{grid-template-columns:1fr}.bb-channel-portable .bb-channel-media-rail{justify-self:start;width:min(18rem,100%)}}
329
452
  @keyframes bb-channel-shimmer{0%{background-position:180% 0}100%{background-position:-80% 0}}
330
- @media(max-width:640px){.bb-channel-portable .bb-channel-intro{margin-bottom:1.2rem}.bb-channel-portable .bb-channel-title{font-size:1.45rem}.bb-channel-portable .bb-channel-intro-text{font-size:.94rem}.bb-channel-portable .bb-channel-feed{gap:.9rem;padding-left:1.15rem}.bb-channel-portable .bb-channel-feed::before{left:.06rem}.bb-channel-portable .bb-channel-dot{left:-1.32rem;top:1.42rem}.bb-channel-portable .bb-channel-card-surface{padding:1.05rem .95rem;border-radius:12px}.bb-channel-portable .bb-channel-card-placeholder{border-radius:12px}.bb-channel-portable .bb-channel-card:hover .bb-channel-card-surface{transform:none}.bb-channel-portable .bb-channel-meta{margin-bottom:.85rem}.bb-channel-portable .bb-channel-content{font-size:.96rem;line-height:1.72}.bb-channel-portable .bb-channel-media-rail{width:100%;max-width:20rem;grid-template-columns:repeat(3,minmax(0,1fr));justify-self:center;margin-top:.15rem}.bb-channel-portable .bb-channel-media-thumb{min-height:4.1rem}.bb-channel-portable .bb-channel-image-viewer{margin-top:.95rem;max-width:100%}.bb-channel-portable .bb-channel-image-slide{min-height:10rem}.bb-channel-portable .bb-channel-image-large{max-width:100%!important;max-height:68vh!important}.bb-channel-portable .bb-channel-image-nav{width:2.55rem;height:2.55rem}.bb-channel-portable .bb-channel-image-prev{left:.45rem}.bb-channel-portable .bb-channel-image-next{right:.45rem}.bb-channel-portable .bb-channel-attachment{align-items:flex-start;padding:.78rem .82rem}.bb-channel-portable .bb-channel-attachment-title{white-space:normal;overflow-wrap:anywhere}.bb-channel-portable .bb-channel-tags{gap:.42rem}.bb-channel-portable .bb-channel-pagination{gap:1rem}}
331
- @media(prefers-reduced-motion:reduce){.bb-channel-portable .bb-channel-card-placeholder,.bb-channel-portable .bb-channel-card-surface,.bb-channel-portable .bb-channel-media-rail,.bb-channel-portable .bb-channel-image-viewer,.bb-channel-portable .bb-channel-image-large,.bb-channel-portable .bb-channel-attachment{transition:none}.bb-channel-portable .bb-channel-card:hover .bb-channel-card-surface{transform:none}}
453
+ @media(max-width:640px){.bb-channel-portable{--bb-card-offset-x:var(--bb-card-mobile-offset-x);--bb-card-offset-y:var(--bb-card-mobile-offset-y);--bb-card-active-shadow:0 12px 26px -24px rgba(15,23,42,.32)}.bb-channel-portable .bb-channel-intro{margin-bottom:1.2rem}.bb-channel-portable .bb-channel-title{font-size:1.45rem}.bb-channel-portable .bb-channel-intro-text{font-size:.94rem}.bb-channel-portable .bb-channel-feed{gap:.9rem;padding-left:1.15rem}.bb-channel-portable .bb-channel-feed::before{left:.06rem}.bb-channel-portable .bb-channel-dot{left:-1.32rem;top:1.42rem}.bb-channel-portable .bb-channel-card-surface{padding:1.05rem .95rem;border-radius:12px}.bb-channel-portable .bb-channel-card-placeholder{border-radius:12px}.bb-channel-portable .bb-channel-meta{margin-bottom:.85rem}.bb-channel-portable .bb-channel-content{font-size:.96rem;line-height:1.72}.bb-channel-portable .bb-channel-media-rail{width:100%;max-width:20rem;grid-template-columns:repeat(3,minmax(0,1fr));justify-self:center;margin-top:.15rem}.bb-channel-portable .bb-channel-media-thumb{min-height:4.1rem}.bb-channel-portable .bb-channel-image-viewer{margin-top:.95rem;max-width:100%}.bb-channel-portable .bb-channel-image-slide{min-height:10rem}.bb-channel-portable .bb-channel-image-large{max-width:100%!important;max-height:68vh!important}.bb-channel-portable .bb-channel-image-nav{width:2.55rem;height:2.55rem}.bb-channel-portable .bb-channel-image-prev{left:.45rem}.bb-channel-portable .bb-channel-image-next{right:.45rem}.bb-channel-portable .bb-channel-attachment{align-items:flex-start;padding:.78rem .82rem}.bb-channel-portable .bb-channel-attachment-title{white-space:normal;overflow-wrap:anywhere}.bb-channel-portable .bb-channel-tags{gap:.42rem}.bb-channel-portable .bb-channel-pagination{gap:1rem}}
454
+ @media(prefers-reduced-motion:reduce){.bb-channel-portable .bb-channel-card-placeholder,.bb-channel-portable .bb-channel-card-surface,.bb-channel-portable .bb-channel-media-rail,.bb-channel-portable .bb-channel-image-viewer,.bb-channel-portable .bb-channel-image-large,.bb-channel-portable .bb-channel-attachment{transition:none}.bb-channel-portable .bb-channel-card:hover .bb-channel-card-surface,.bb-channel-portable .bb-channel-card[data-bb-card-active="true"] .bb-channel-card-surface{transform:none}}
332
455
  </style>
333
456
  <div class="bb-channel-portable" data-bb-channel-root data-api-base="${escapeHtml(config.apiBase)}" data-page-size="${escapeHtml(config.pageSize)}">
334
457
  <header class="bb-channel-intro">