diff --git a/.changeset/content-anchor-fontface.md b/.changeset/content-anchor-fontface.md new file mode 100644 index 000000000..bb14ece44 --- /dev/null +++ b/.changeset/content-anchor-fontface.md @@ -0,0 +1,5 @@ +--- +'@app/ratewise': patch +--- + +修復 PWA standalone 下內容頁頁內錨點跳轉(隱私頁目錄、使用指南步驟)被變高的返回列遮蔽的問題;修復品牌標準字體在正式站永遠未載入(全站 unused preload 警告、頁首 wordmark 回落系統字體)的問題。 diff --git a/apps/ratewise/index.html b/apps/ratewise/index.html index 4992d21ee..f59018b7b 100644 --- a/apps/ratewise/index.html +++ b/apps/ratewise/index.html @@ -140,15 +140,8 @@ line-height: 1.6; -webkit-text-size-adjust: 100%; } - /* 品牌標準字(showcase-twincoins SSOT):Nunito 900 子集,僅含 HaoRate 六個字符。 */ - @font-face { - font-family: 'Nunito Wordmark'; - font-style: normal; - font-weight: 900; - font-display: swap; - src: url('__BASE_PATH__fonts/nunito-wordmark-900.woff2') format('woff2'); - unicode-range: U+0048, U+0052, U+0061, U+0065, U+006F, U+0074; - } + /* 品牌標準字 @font-face 移至 src/index.css(與 .brand-wordmark 同 sheet): + Beasties 逐 sheet 判定 critical fonts,內嵌 sheet 無使用者會整條剝除 @font-face。 */ /* Theme-aware skeleton variables * Maps to user's selected theme for seamless loading experience * @see index.css - theme color definitions diff --git a/apps/ratewise/src/components/content/ContentSections.tsx b/apps/ratewise/src/components/content/ContentSections.tsx index a1d0131d2..935986634 100644 --- a/apps/ratewise/src/components/content/ContentSections.tsx +++ b/apps/ratewise/src/components/content/ContentSections.tsx @@ -77,7 +77,7 @@ export function ContentSectionCard({ return (
{title &&

{title}

} {children} @@ -175,7 +175,7 @@ function renderSection(section: ContentSection) { ); case 'faq': return ( -
+
{section.title && (

{section.title}

)} diff --git a/apps/ratewise/src/components/content/__tests__/ContentPageLayout.test.tsx b/apps/ratewise/src/components/content/__tests__/ContentPageLayout.test.tsx index e840ab144..813bc4c9b 100644 --- a/apps/ratewise/src/components/content/__tests__/ContentPageLayout.test.tsx +++ b/apps/ratewise/src/components/content/__tests__/ContentPageLayout.test.tsx @@ -46,6 +46,20 @@ describe('ContentPageLayout', () => { const container = screen.getByTestId('content-page').firstElementChild; expect(container?.className).toContain('pb-24'); }); + + // sticky 返回列 safe-area 適配由 PageNavHeader.safe-area.test.tsx(#605)守門; + // 此處守門錨點連動:sticky 列在 standalone 下變高,錨點目標 scroll-margin 必須同步吃 safe-area(#600)。 + it('錨點 section 的 scroll-margin-top 疊加 safe-area-inset-top', () => { + render( + + + , + ); + const section = document.getElementById('anchor-a'); + expect(section?.className).toContain('scroll-mt-[calc(5rem+env(safe-area-inset-top,0px))]'); + }); }); describe('ContentSections', () => { diff --git a/apps/ratewise/src/index.css b/apps/ratewise/src/index.css index 4582b227b..0564fdf7b 100644 --- a/apps/ratewise/src/index.css +++ b/apps/ratewise/src/index.css @@ -21,8 +21,19 @@ /* ======================================== 品牌標準字 Wordmark(SSOT: scripts/source-images/brand/showcase-twincoins.html) Nunito 900、字距 -0.02em;「Rate」使用主題主色。 - @font-face 定義於 index.html critical CSS(__BASE_PATH__ SSOT 替換)。 ======================================== */ +/* 品牌標準字子集(Nunito 900,僅 wordmark 六字符,1.2KB)。 + 必須與 .brand-wordmark 同 sheet:Beasties 逐 sheet 判定 critical fonts, + 放在 index.html 內嵌樣式會於 SSG 階段被整條剝除(unused preload 根因)。 */ +@font-face { + font-family: 'Nunito Wordmark'; + font-style: normal; + font-weight: 900; + font-display: swap; + src: url('/fonts/nunito-wordmark-900.woff2') format('woff2'); + unicode-range: U+0048, U+0052, U+0061, U+0065, U+006F, U+0074; +} + .brand-wordmark { font-family: 'Nunito Wordmark', diff --git a/apps/ratewise/src/index.html.test.ts b/apps/ratewise/src/index.html.test.ts index d57052d32..2508e4883 100644 --- a/apps/ratewise/src/index.html.test.ts +++ b/apps/ratewise/src/index.html.test.ts @@ -120,11 +120,17 @@ describe('index.html - Static Template (SEOHelmet Architecture)', () => { expect(indexHtmlContent).toContain(' { - // 品牌標準字(Nunito 900 子集):preload + @font-face 均須走 __BASE_PATH__ 佔位符。 + it('should preload the brand wordmark font subset with base path SSOT', () => { + // 品牌標準字(Nunito 900 子集):preload 走 __BASE_PATH__ 佔位符。 + // @font-face 必須在 src/index.css(與 .brand-wordmark 同 sheet), + // 否則 Beasties 逐 sheet 判定 critical fonts 時會整條剝除(unused preload 根因)。 expect(indexHtmlContent).toContain('__BASE_PATH__fonts/nunito-wordmark-900.woff2'); - expect(indexHtmlContent).toContain("font-family: 'Nunito Wordmark'"); - expect(indexHtmlContent).toContain('font-display: swap'); + expect(indexHtmlContent).not.toMatch(/@font-face\s*\{/); + + const indexCssContent = readFileSync(resolve(__dirname, 'index.css'), 'utf-8'); + expect(indexCssContent).toContain("font-family: 'Nunito Wordmark'"); + expect(indexCssContent).toContain('font-display: swap'); + expect(indexCssContent).toContain("url('/fonts/nunito-wordmark-900.woff2')"); }); it('should retain PWA manifest hints', () => { diff --git a/apps/ratewise/src/pages/Guide.tsx b/apps/ratewise/src/pages/Guide.tsx index 64264189f..737645bd1 100644 --- a/apps/ratewise/src/pages/Guide.tsx +++ b/apps/ratewise/src/pages/Guide.tsx @@ -160,7 +160,7 @@ const Guide = () => {
diff --git a/apps/ratewise/vite.config.ts b/apps/ratewise/vite.config.ts index 065437d39..205d53798 100644 --- a/apps/ratewise/vite.config.ts +++ b/apps/ratewise/vite.config.ts @@ -503,6 +503,13 @@ export default defineConfig(({ mode }) => { script: 'async', formatting: 'beautify', dirStyle: 'nested', + // Beasties 預設 inlineFonts:false 會剝除 index.html inline @font-face 並注入重複 + // font preload,導致品牌字體永遠未被使用(unused preload console warning)。 + // inlineFonts:true 保留 @font-face;preloadFonts:false 交由 index.html 靜態 preload SSOT。 + beastiesOptions: { + inlineFonts: true, + preloadFonts: false, + }, // vite-react-ssg + Beasties may read freshly rendered nested amount pages during // writeout. Serial rendering avoids intermittent ENOENT on CI/pre-push. concurrency: 1, diff --git a/docs/dev/002_development_reward_penalty_log.md b/docs/dev/002_development_reward_penalty_log.md index 40f6ecaff..2b04bf7b1 100644 --- a/docs/dev/002_development_reward_penalty_log.md +++ b/docs/dev/002_development_reward_penalty_log.md @@ -2,7 +2,7 @@ > 版本:outline-v2-ultra > 原則:每筆只保留日期、ID、原因、解法。 -> 本次分數變化:+1(reward 1、penalty 0、neutral 0)|累計總分:+147 +> 本次分數變化:+2(reward 2、penalty 0、neutral 1)|累計總分:+149 ## 新增模板(4 行) @@ -13,6 +13,21 @@ ## 條目(新→舊) +- 日期:2026-07-06 +- ID:reward-rw-600-anchor-scroll-mt-safe-area +- 原因:#605 修了 sticky 返回列 safe-area 但錨點目標 `scroll-mt-20` 固定 80px 未連動,standalone 下 sticky 列變高(+59px)後隱私目錄與 Guide 步驟跳轉被遮蔽(#600 連動項) +- 解法:三處錨點(Privacy 目錄、Guide 步驟、ContentSections)改 `scroll-mt-[calc(5rem+env(safe-area-inset-top,0px))]`,CDP inset 59px 實測 targetTop 139 > headerBottom 124,補 ContentPageLayout 錨點斷言 + +- 日期:2026-07-06 +- ID:reward-rw-font-preload-beasties-fontface-strip +- 原因:品牌字體 @font-face 放 index.html 內嵌樣式,Beasties 逐 sheet 判定 critical fonts 時因該 sheet 無使用者而整條剝除,全站 unused preload 警告且 wordmark 永遠回落系統字體(正式站取證:HTML 無任何 @font-face) +- 解法:@font-face 移至 src/index.css(與 .brand-wordmark 同 sheet)+ ssgOptions.beastiesOptions 設 inlineFonts:true/preloadFonts:false,preview 驗證警告歸零、document.fonts.check 為 true、preload 單一化 + +- 日期:2026-07-06 +- ID:neutral-rw-612-staging-manifest-cross-origin +- 原因:staging manifest `scope`/`start_url` 由 generate-manifest.mjs 以 APP_INFO.siteUrl 硬編正式站絕對 URL,對 staging origin 跨網域;屬刻意設計(PWA partition/HTTPS-First)連動 SSOT 生成鏈 +- 解法:取證後立案 #612(附 curl 證據與範圍)不硬修;React #418 staging 複驗無殘留(#595 已修) + - 日期:2026-07-06 - ID:reward-rw-e5b-currency-page-uiux-six-section-ia - 原因:34 幣別頁+金額頁為牆文式段落佈局、視覺語言與 app 端割裂且行動版缺底部導覽,未達韓系 app 水準(E5 wave-B)