diff --git a/scripts/test-detail-browser.cjs b/scripts/test-detail-browser.cjs index 862fbe8e..bb8898ae 100644 --- a/scripts/test-detail-browser.cjs +++ b/scripts/test-detail-browser.cjs @@ -11,34 +11,80 @@ const { chromium } = require('playwright'); const errors=[]; page.on('pageerror', e=>errors.push(e.message)); const content='## 阅读体验\n\n统一正文、标题和侧边信息的位置,让阅读更轻松。\n\n## 功能说明\n\n正文内容保留原有的 Markdown 格式。\n\n## 使用方式\n\n在手机与电脑上打开页面。'; const author={author_name:'这是用于检验窄屏排版的很长用户名', author_avatar:'/login-icons/microsoft.svg',published_at:'2026-09-14T00:00:00Z'}; + async function assertArticlePills(covered, width) { + const header=page.locator(covered?'.article-has-cover .hero-band':'.article-no-cover .hero-heading'); + assert.equal(await header.count(),1); + assert.equal(await page.locator(covered?'.hero-heading':'.hero-band').count(),0); + assert.equal(await page.locator('.hero-card').count(),covered?1:0); + for(const dark of [false,true]) { + await page.evaluate(value=>document.documentElement.classList.toggle('dark',value),dark); + for(const row of ['.detail-breadcrumb','.detail-label']) { + const pill=header.locator(`${row} ${covered?'.hero-pill':'.hero-pill--light'}`); + assert.equal(await pill.count(),1,`${width} ${dark?'dark':'light'} ${row}: expected capsule variant`); + await page.mouse.move(0,0); + const base=await pill.evaluate(el=>{ + const s=getComputedStyle(el),before=getComputedStyle(el,'::before'); + const probe=document.createElement('span');el.append(probe); + const color=value=>{probe.style.color=value;return getComputedStyle(probe).color;}; + const expected={text:color('var(--text-primary)'),fill:color('var(--fill)'),hover:color('var(--heading-pill-hover)'),focus:color('var(--article-accent)'),background:color('var(--background)')};probe.remove(); + return {background:s.backgroundColor,color:s.color,radius:s.borderRadius,borders:[s.borderTopWidth,s.borderRightWidth,s.borderBottomWidth,s.borderLeftWidth],transform:s.transform,shadow:s.boxShadow,textShadow:s.textShadow,filter:s.filter,backdrop:s.backdropFilter,font:s.font,padding:s.padding,targetHeight:Math.max(el.getBoundingClientRect().height,parseFloat(before.height)||0),expected}; + }); + assert.equal(base.radius,'999px');assert.deepEqual(base.borders,['0px','0px','0px','0px']); + assert.equal(base.transform,'none');assert.equal(base.shadow,'none');assert.equal(base.textShadow,'none');assert.equal(base.filter,'none');assert.equal(base.backdrop,'none'); + assert.ok(base.targetHeight>=44,'capsule hit area must be at least 44px'); + assert.equal(base.color,covered?'rgb(255, 255, 255)':base.expected.text); + assert.equal(base.background,covered?'rgba(255, 255, 255, 0.1)':base.expected.fill); + if(!covered) { + const heading=await header.evaluate(el=>({background:getComputedStyle(el).backgroundColor,image:getComputedStyle(el).backgroundImage})); + assert.equal(heading.background,base.expected.background);assert.equal(heading.image,'none'); + } + await pill.hover(); + const hover=await pill.evaluate(el=>{const s=getComputedStyle(el);return {background:s.backgroundColor,color:s.color,transform:s.transform,shadow:s.boxShadow,filter:s.filter,backdrop:s.backdropFilter,font:s.font,padding:s.padding};}); + assert.equal(hover.background,covered?'rgba(255, 255, 255, 0.18)':base.expected.hover); + for(const key of ['color','transform','shadow','filter','backdrop','font','padding'])assert.equal(hover[key],base[key],`hover only changes background: ${key}`); + if(covered) { + await page.mouse.down(); + assert.equal(await pill.evaluate(el=>getComputedStyle(el).backgroundColor),'rgba(255, 255, 255, 0.24)'); + await page.mouse.move(0,0);await page.mouse.up(); + } + await page.mouse.move(0,0);await pill.focus();await page.keyboard.press('Shift+Tab');await page.keyboard.press('Tab'); + const focus=await pill.evaluate(el=>({visible:el.matches(':focus-visible'),style:getComputedStyle(el).outlineStyle,width:getComputedStyle(el).outlineWidth,color:getComputedStyle(el).outlineColor})); + assert.equal(focus.visible,true);assert.equal(focus.style,'solid');assert.equal(focus.width,'2px');assert.equal(focus.color,covered?'rgba(255, 255, 255, 0.85)':base.expected.focus); + await pill.evaluate(el=>el.blur()); + } + } + await page.evaluate(()=>document.documentElement.classList.remove('dark')); + } await page.route('**/api/**', route=>{ const path=new URL(route.request().url()).pathname; let json={}; if(path==='/api/auth/session')json={user:null}; if(path.includes('/comments'))json=[]; - if(path==='/api/posts/1')json={id:1,title:'博客前端视觉重构与阅读体验',content,summary:'同一套布局,适配不同类型的内容。',...author,category:'开发记录与前端设计',tags:['React','移动端适配','长标签测试'.repeat(12)]}; + if(/^\/api\/posts\/[12]$/.test(path)){const id=Number(path.split('/').at(-1));json={id,title:'博客前端视觉重构与阅读体验',content,summary:'同一套布局,适配不同类型的内容。',...author,cover_image:id===2?'/login-icons/microsoft.svg':null,category:'开发记录与前端设计',tags:['React','移动端适配','长标签测试'.repeat(12)]};} if(path.startsWith('/api/projects/'))json={id:1,name:'PromptDock',content,stage:'active',summary:'原生 macOS 提示词管理工具',...author,tech_stack:'SwiftUI · SwiftData · AppKit',demo_url:'https://example.com',repo:'example/project',releases:{items:[],page:1,total:0,pageSize:20}}; if(path==='/api/updates/1')json={id:1,...author,content:'QQ 和 Google 登录已经开放,欢迎在账号设置中绑定体验。'}; - if(path==='/api/posts/1/discovery')json={related:[],previous:null,next:null}; - if(path==='/api/series/article/1')json=null; + if(/^\/api\/posts\/[12]\/discovery$/.test(path))json={related:[],previous:null,next:null}; + if(/^\/api\/series\/article\/[12]$/.test(path))json=null; if(path.startsWith('/api/analytics/'))json={views:123456}; return route.fulfill({json}); }); for(const width of [1920,1440,1100,768,375,320]){ await page.setViewportSize({width,height:900}); let expected; - for(const [name,path] of [['article','/article/1'],['project','/projects/promptdock']]){ + for(const [name,path] of [['article','/article/1'],['article-cover','/article/2'],['project','/projects/promptdock']]){ + const isArticle=name.startsWith('article'); await page.goto('http://127.0.0.1:4202'+path); await page.locator('.detail-title').waitFor(); - if(name==='article')await page.getByLabel('123456 次阅读',{exact:true}).waitFor(); + if(isArticle)await page.getByLabel('123456 次阅读',{exact:true}).waitFor(); assert.ok(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth),`${name} ${width} page overflow`); const outer=await page.locator('main.detail-container').evaluate(el=>{const r=el.getBoundingClientRect(),s=getComputedStyle(el);return {left:r.left,right:el.parentElement.getBoundingClientRect().right-r.right,width:r.width,paddingLeft:s.paddingLeft,paddingRight:s.paddingRight};}); assert.ok(Math.abs(outer.left-outer.right)<1, `${name} ${width}: equal outer gutters ${JSON.stringify(outer)}`); assert.equal(outer.paddingLeft,'0px');assert.equal(outer.paddingRight,'0px'); const usableWidth=await page.locator('main.detail-container').evaluate(el=>el.parentElement.getBoundingClientRect().width); - if(width>=1440){assert.equal(outer.width,name==='article'?usableWidth:1200);measurements.push({name,viewport:width,...outer});} - const pill=await page.locator(name==='article'?'.detail-label .hero-pill':'.detail-category').evaluate(el=>{const s=getComputedStyle(el);return {background:s.backgroundColor,radius:s.borderRadius};}); + if(width>=1440){assert.equal(outer.width,isArticle?usableWidth:1200);measurements.push({name,viewport:width,...outer});} + const pill=await page.locator(isArticle?`.detail-label ${name==='article-cover'?'.hero-pill':'.hero-pill--light'}`:'.detail-category').evaluate(el=>{const s=getComputedStyle(el);return {background:s.backgroundColor,radius:s.borderRadius};}); assert.notEqual(pill.background,'rgba(0, 0, 0, 0)');assert.equal(pill.radius,'999px'); + if(isArticle)await assertArticlePills(name==='article-cover',width); const body=await page.locator('.detail-body').boundingBox(); - if(name==='article'){ + if(isArticle){ if(width>=1100)assert.equal(body.width,720); assert.ok(Math.abs(body.x+body.width/2-(outer.left+outer.width/2))<1,'article reading column centered'); const groups=await page.locator('.hero-meta .g').evaluateAll(es=>es.map(e=>({y:e.getBoundingClientRect().y,white:getComputedStyle(e).whiteSpace}))); @@ -72,7 +118,7 @@ const { chromium } = require('playwright'); } if(width<=375){assert.equal(await page.locator('.detail-title').evaluate(el=>getComputedStyle(el).fontSize),'28px');await page.getByRole('button',{name:'打开菜单',exact:true}).click();await page.getByRole('navigation',{name:'手机导航'}).waitFor();await page.keyboard.press('Escape');} await page.screenshot({path:`.cache/detail-${name}-${width}.png`,fullPage:true}); - if(width===375){await page.evaluate(()=>document.documentElement.classList.add('dark'));await page.screenshot({path:`.cache/detail-${name}-dark.png`,fullPage:true});} + if(width===375){await page.evaluate(()=>document.documentElement.classList.add('dark'));await page.screenshot({path:`.cache/detail-${name}-dark.png`,fullPage:true});await page.evaluate(()=>document.documentElement.classList.remove('dark'));} } } // A single mounted sidebar follows breakpoint changes and native summary keyboard activation. @@ -101,6 +147,6 @@ const { chromium } = require('playwright'); } require('node:fs').writeFileSync('.cache/centering-measurements.json',JSON.stringify(measurements,null,2)+'\n'); assert.deepEqual(errors,[]); - console.log('PASS: shared 640–680px column, aligned atomic metadata, 375px/320px navigation, sidebar folding, dark theme and no page overflow.'); + console.log('PASS: shared 640–680px column, covered/coverless capsules in both themes, aligned atomic metadata, 375px/320px navigation, sidebar folding and no page overflow.'); } finally { await browser?.close(); await server.httpServer.close(); } })().catch(e=>{console.error(e);process.exitCode=1;}); diff --git a/src/app/components/ArticlePresentation.tsx b/src/app/components/ArticlePresentation.tsx index 6ea0ab68..720f724a 100644 --- a/src/app/components/ArticlePresentation.tsx +++ b/src/app/components/ArticlePresentation.tsx @@ -29,9 +29,9 @@ export function ArticlePresentation({ post, preview = false, views = null,progre let tags: string[] = []; try { tags = Array.isArray(post.tags) ? post.tags : JSON.parse(post.tags || '[]'); } catch {} return
-
- {progress} -
{post.category||'文章'}
+ {progress} +
+
{post.category||'文章'}

{post.title}

diff --git a/src/app/components/ArticleTableOfContents.tsx b/src/app/components/ArticleTableOfContents.tsx index e4f11097..028d7326 100644 --- a/src/app/components/ArticleTableOfContents.tsx +++ b/src/app/components/ArticleTableOfContents.tsx @@ -34,13 +34,13 @@ export function ArticleTableOfContents({headings,preview=false}:{headings:Headin observe();select(); return()=>{changes.disconnect();observer.disconnect();win.removeEventListener('scroll',onScroll);if(frame)win.cancelAnimationFrame(frame);}; },[headings]); - const links=; const settings=(compact=false)=>!preview&&; if(!headings.length&&preview)return null; return
; } -export function SitePage({ children, narrow = false, detail = false,className='' }: { children: ReactNode; narrow?: boolean; detail?: boolean;className?:string }) { +export function SitePage({ children, narrow = false, detail = false,className='',footerContent }: { children: ReactNode; narrow?: boolean; detail?: boolean;className?:string;footerContent?:ReactNode }) { const location=useLocation(); useLayoutEffect(() => { visibleDiagnostic(location.pathname, 'code-ready'); }, [location.key]); return ( @@ -113,6 +113,7 @@ export function SitePage({ children, narrow = false, detail = false,className=''
{if(event.target===event.currentTarget) visibleDiagnostic(location.pathname,'motion-complete');}} className={`site-container page-content ${narrow ? 'reading-page' : ''} ${detail ? 'detail-container' : ''}`}> {narrow ?
{children}
: children}
+ {footerContent}
); diff --git a/src/app/components/ReadingProgress.tsx b/src/app/components/ReadingProgress.tsx index cc5dc58f..0388b4c7 100644 --- a/src/app/components/ReadingProgress.tsx +++ b/src/app/components/ReadingProgress.tsx @@ -1,10 +1,30 @@ import { reducedMotion } from '../lib/motionPreference'; -import {useEffect,useState} from 'react'; +import {useCallback,useEffect,useRef,useState} from 'react'; +import {createPortal} from 'react-dom'; import {useAuth} from '../context/AuthContext'; import {useEngagement} from '../lib/useEngagement'; import {api} from '../lib/api'; export function ReadingProgress({postId}:{postId:number}){ const {user}=useAuth(),enabled=useEngagement(),[saved,setSaved]=useState(null),[error,setError]=useState(''),[connection,setConnection]=useState(0); + const [hovered,setHovered]=useState(false),[focused,setFocused]=useState(false),[leaving,setLeaving]=useState(false); + const dismissed=useRef(false),remaining=useRef(8000),exitTimer=useRef|null>(null); + useEffect(()=>{ + dismissed.current=false;remaining.current=8000;setSaved(null);setLeaving(false); + return()=>{if(exitTimer.current!==null)clearTimeout(exitTimer.current);}; + },[postId]); + const dismiss=useCallback(()=>{ + if(dismissed.current)return; + dismissed.current=true; + if(reducedMotion()){setSaved(null);return;} + setLeaving(true);exitTimer.current=setTimeout(()=>{setSaved(null);setLeaving(false);},160); + },[]); + const progress=Number(saved?.progress),visible=!error&&Boolean(saved)&&Number.isFinite(progress)&&progress>=0.05&&progress<=0.95; + useEffect(()=>{if(!visible){setHovered(false);setFocused(false);}},[visible]); + useEffect(()=>{ + if(!visible||hovered||focused||leaving)return; + const started=Date.now(),timer=setTimeout(dismiss,remaining.current); + return()=>{clearTimeout(timer);remaining.current=Math.max(0,remaining.current-(Date.now()-started));}; + },[visible,hovered,focused,leaving,dismiss]); useEffect(()=>{ if(!enabled||!postId)return; @@ -14,7 +34,7 @@ export function ReadingProgress({postId}:{postId:number}){ if(!user){for(const entry of Object.keys(localStorage)){if(!entry.startsWith('mooncci-reading-'))continue;try{const value=JSON.parse(localStorage.getItem(entry)||'null');if(!value||Date.now()-Number(value.updated_at)>180*86400000)localStorage.removeItem(entry);}catch{localStorage.removeItem(entry);}}} const r=user?await api('/engagement/history/'+postId):{enabled:true,item:JSON.parse(localStorage.getItem(key)||'null'),epoch:0}; - if(!alive)return;ready=r.enabled;epoch=r.epoch;revision=Number(r.item?.revision||0);if(!user&&r.item&&Date.now()-Number(r.item.updated_at)>180*86400000){localStorage.removeItem(key);revision=0;setSaved(null);}else setSaved(r.item); + if(!alive)return;ready=r.enabled;epoch=r.epoch;revision=Number(r.item?.revision||0);if(!user&&r.item&&Date.now()-Number(r.item.updated_at)>180*86400000){localStorage.removeItem(key);revision=0;setSaved(null);}else if(!dismissed.current)setSaved(r.item); }catch{ready=false;}};void initial(); const save=async()=>{ if(!alive||!ready||!dirty||writing||stopped)return; @@ -42,10 +62,16 @@ export function ReadingProgress({postId}:{postId:number}){ const resume=()=>{ const behavior=reducedMotion()?'auto':'smooth'; const anchor=saved?.anchor&&document.getElementById(saved.anchor); - if(anchor)anchor.scrollIntoView({behavior,block:'start'}); + if(anchor)window.scrollTo({top:scrollY+anchor.getBoundingClientRect().top-(document.querySelector('.site-header')?.getBoundingClientRect().height||76)-24,behavior}); else{const article=document.querySelector('.article-presentation');if(article){const rect=article.getBoundingClientRect();window.scrollTo({top:scrollY+rect.top+saved.progress*Math.max(1,rect.height-innerHeight),behavior});}} - setSaved(null); + dismiss(); }; if(!enabled)return null; - return <>{saved&&saved.progress>=0.05&&saved.progress<=0.95&&
上次读到 {Math.round(saved.progress*100)}%{!user?' · 仅保存在此浏览器':''}
}{error&&

{error}

}; + return createPortal(<> + {visible&&
setHovered(true)} onMouseLeave={()=>setHovered(false)} onFocusCapture={()=>setFocused(true)} onBlurCapture={event=>{if(!event.currentTarget.contains(event.relatedTarget))setFocused(false);}}> + 上次读到 {Math.round(progress*100)}%{!user&& · 仅保存在此浏览器} + +
} + {error&&
{error}
} + ,document.body); } diff --git a/src/app/pages/ArticlePage.tsx b/src/app/pages/ArticlePage.tsx index 8b890295..4b8355bb 100644 --- a/src/app/pages/ArticlePage.tsx +++ b/src/app/pages/ArticlePage.tsx @@ -18,7 +18,7 @@ export default function ArticlePage() { const post = resource.data; const views=usePageView(post?.id?`/article/${post.id}`:null,post?.id); return ( - + }> {post && ( <> @@ -26,7 +26,6 @@ export default function ArticlePage() { }/> - )} diff --git a/src/app/pages/HomePage.tsx b/src/app/pages/HomePage.tsx index 8c8bbc47..99a1fa13 100644 --- a/src/app/pages/HomePage.tsx +++ b/src/app/pages/HomePage.tsx @@ -165,8 +165,8 @@ export default function HomePage() { )} - + ); diff --git a/src/styles/article-detail.css b/src/styles/article-detail.css index 1c1b4014..2b6140b4 100644 --- a/src/styles/article-detail.css +++ b/src/styles/article-detail.css @@ -23,9 +23,21 @@ .article-presentation .hero-band .article-view-count svg { color:inherit; stroke:currentColor; } .hero-card { position:relative; height:400px; margin-top:32px; margin-bottom:-160px; border:1px solid var(--hero-hair); border-radius:14px; overflow:hidden; box-shadow:var(--hero-shadow); } .hero-card .content-image { display:block; width:100%; height:100%; object-fit:cover; object-position:top; margin:0; } -.article-no-cover .hero-band { padding-bottom:32px; } +.hero-heading { display:flow-root; padding:48px 0 32px; color:var(--text-primary); background:var(--background); --heading-pill-hover:color-mix(in srgb,var(--text-primary) 7%,transparent); } +.hero-heading .hero-inner { width:min(900px,calc(100% - 48px)); } +.article-presentation .hero-heading .detail-intro { width:100%; max-width:var(--col); margin:0 auto; } +.neo-page .article-presentation .hero-heading .detail-title { max-width:900px; margin:24px auto 0; color:var(--text-primary); } +.neo-page .article-presentation .hero-heading .hero-meta { color:var(--muted-foreground); } +.article-presentation .hero-heading .article-view-count { color:var(--muted-foreground); } +.article-presentation .hero-heading .article-view-count svg { stroke:currentColor; } +.article-presentation .hero-heading .hero-pill--light { position:relative; display:inline-flex; align-items:center; font-size:13px; line-height:1.4; padding:6px 14px; border-radius:999px; color:var(--text-primary); background:var(--fill); border:0; box-shadow:none; text-shadow:none; backdrop-filter:none; -webkit-backdrop-filter:none; filter:none; transform:none; text-decoration:none; -webkit-font-smoothing:antialiased; transition:none; } +.article-presentation .hero-heading .hero-pill--light::before { content:''; position:absolute; inset:calc((100% - 44px) / 2) 0; } +.article-presentation .hero-heading .hero-pill--light:hover { background-color:var(--heading-pill-hover); } +.article-presentation .hero-heading .hero-pill--light:focus-visible { outline:2px solid var(--article-accent); outline-offset:2px; } +.dark .hero-heading,html[data-reading-color=dark] .hero-heading,html[data-reading-color=contrast] .hero-heading { --heading-pill-hover:color-mix(in srgb,var(--text-primary) 12%,transparent); } +@media(prefers-reduced-motion:no-preference) { html:not([data-reduced-motion=true]) .article-presentation .hero-heading .hero-pill--light { transition:background-color .18s ease; } } .article-after { width:min(var(--container),calc(100% - 48px)); margin:0 auto; padding-top:208px; } -.article-no-cover .article-after { padding-top:48px; } +.article-no-cover .article-after { padding-top:32px; } .article-reading-wrap { position:relative; width:100%; max-width:var(--col); margin:0 auto; } .neo-page .article-presentation .detail-body { padding:0; min-width:0; border:0; } .neo-page .article-presentation .markdown-body,.neo-page .article-presentation .article-summary { font-size:var(--reading-size,17px); line-height:1.85; color:var(--text-body); overflow-wrap:anywhere; } @@ -55,13 +67,12 @@ .article-rail nav a.on { color:var(--text-primary); } .toc-in nav a.on::before { content:''; position:absolute; left:-17px; top:6px; bottom:6px; width:2px; background:var(--article-accent); } .article-rail nav a:hover { color:var(--text-primary); } +@media(min-width:1280px) { .toc-in nav a .toc-entry-title { display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; min-width:0; } } .toc-reading-trigger { display:inline-flex; align-items:center; gap:10px; min-height:44px; margin-top:16px; padding:0 12px; border:0; border-radius:8px; background:transparent; color:var(--muted-foreground); font-size:13.5px; } .toc-reading-trigger:hover,.toc-reading-trigger[aria-expanded=true] { background:var(--fill); } .article-rail-settings-only .toc-in { border-left:0; } .article-rail-settings-only .toc-reading-trigger { margin-top:0; } .article-reading-toolbar,.toc-narrow { display:none; } -.article-presentation .reading-resume { margin:0 0 16px; padding:0; border:0; border-radius:0; background:transparent; font-size:13px; color:var(--hero-meta); gap:8px; } -.article-presentation .reading-resume button { min-height:44px; font-size:13px; font-weight:400; background:transparent; border:0; color:var(--hero-text); } .article-route :is(.article-discovery,.article-layout>.publishing-card,.article-layout>.comment-section) { width:min(var(--col),calc(100% - 48px)); max-width:var(--col); margin-inline:auto; } .article-route .article-layout>.publishing-card { margin-top:48px; padding:20px; border:0; background:var(--fill); } .article-route .article-discovery { margin-top:56px; margin-bottom:0; padding:0; border:0; } @@ -76,7 +87,6 @@ .article-related ul { margin:0; padding:0; list-style:none; } .article-related li { margin:0 0 12px; } .article-route .subscribe-section { margin-top:96px; padding-inline:0; } -.article-route .subscribe-inner { width:min(var(--site-width),calc(100% - 2 * var(--site-gutter))); max-width:var(--site-width); gap:48px; } .neo-page.article-route .comment-section:not(.comment-section--compact) { margin-top:64px; padding-top:0; border:0; } .neo-page.article-route .comment-section:not(.comment-section--compact) .comment-sort button { min-height:44px; border:0; background:transparent; color:var(--muted-foreground); } .neo-page.article-route .comment-section:not(.comment-section--compact) .comment-sort button[aria-pressed=true] { background:transparent; color:var(--foreground); } @@ -121,12 +131,12 @@ .neo-page .article-presentation .detail-title { font-size:28px; } .hero-card { height:240px; margin-bottom:-100px; } .article-after { padding-top:148px; } - .article-no-cover .article-after { padding-top:48px; } .comment-compose-bottom { align-items:flex-start; flex-wrap:wrap; } .comment-compose-bottom .comment-submit { margin-left:auto; } } @media(max-width:600px) { .hero-inner,.article-after,.article-route :is(.article-discovery,.article-layout>.publishing-card,.article-layout>.comment-section) { width:calc(100% - 32px); } + .hero-heading .hero-inner { width:calc(100% - 32px); } .hero-band { padding-top:24px; } .neo-page .article-presentation .hero-meta { font-size:13px; } .article-neighbors a { padding:14px; font-size:14px; } diff --git a/src/styles/editorial.css b/src/styles/editorial.css index cc73a8be..eb63016c 100644 --- a/src/styles/editorial.css +++ b/src/styles/editorial.css @@ -908,12 +908,12 @@ input[type='checkbox'] { } .subscribe-section { display: block; - padding: 64px 24px; + padding: 64px 0; border-top: 0; background: var(--fill); margin-top: 48px; } -.subscribe-inner { max-width:var(--col); margin:0 auto; display:grid; grid-template-columns:1fr 1fr; gap:32px; align-items:start; } +.subscribe-inner { width:min(var(--site-width),calc(100% - 2 * var(--site-gutter))); max-width:var(--site-width); margin:0 auto; display:grid; grid-template-columns:1fr 1fr; gap:48px; align-items:start; } .subscribe-inner h2 { font-size:26px; font-weight:600; } .subscribe-inner > div > p:not(.eyebrow) { font-size:13px; line-height:1.7; } .subscribe-inner .subscribe-form input { border:0; background:var(--card); border-radius:8px; min-height:44px; } diff --git a/src/styles/engagement.css b/src/styles/engagement.css index 5aea9fa6..229daf2e 100644 --- a/src/styles/engagement.css +++ b/src/styles/engagement.css @@ -19,7 +19,16 @@ .engagement-folder-tools{display:flex;gap:12px;flex-wrap:wrap;margin-bottom:20px}.engagement-folder-tools input{min-width:0} @media(max-width:760px){.account-workspace .account-sidebar nav{flex-wrap:wrap}.account-workspace .account-sidebar nav a{flex:1 1 30%}.notification-row,.history-row{padding:20px;gap:12px}.history-row{align-items:flex-start;flex-direction:column}.engagement-preferences{padding:20px}.engagement-toolbar label{flex-wrap:wrap}} -.reading-resume{display:flex;gap:16px;align-items:center;flex-wrap:wrap;padding:14px 20px;margin-bottom:24px;border:1px solid var(--border);border-radius:10px;font-size:13px;background:var(--card)}.reading-resume button{font-weight:600} +.resume-toast { position:fixed; left:50%; transform:translateX(-50%); bottom:calc(env(safe-area-inset-bottom,0px) + 24px); z-index:40; display:flex; align-items:center; gap:12px; max-width:calc(100% - 32px); box-sizing:border-box; padding:10px 16px; border-radius:999px; font-size:13px; line-height:1.4; white-space:nowrap; background:var(--popover); color:var(--text-primary); box-shadow:0 8px 24px rgb(0 0 0 / .12); border:0; opacity:1; } +.resume-toast button { display:inline-flex; align-items:center; justify-content:center; flex-shrink:0; min-width:44px; min-height:44px; padding:0; border:0; border-radius:999px; background:transparent; color:inherit; font-size:13px; font-weight:400; text-decoration:none; box-shadow:none; transform:none; } +.resume-toast-close { font-size:20px; } +.resume-toast button:focus-visible { outline:2px solid var(--article-accent); outline-offset:2px; } +.resume-toast[data-leaving=true] { opacity:0; pointer-events:none; } +.resume-toast--error { max-width:min(480px,calc(100% - 32px)); white-space:normal; border-radius:14px; } +.menu-open .resume-toast,.rs-visible .resume-toast,body:has(:is([role=dialog],[role=alertdialog])[aria-modal=true],.site-header .nav-disclosure[open],.site-header .header-search) .resume-toast { visibility:hidden; pointer-events:none; } +@media(max-width:900px) { .resume-toast { bottom:calc(env(safe-area-inset-bottom,0px) + 72px); } } +@media(prefers-reduced-motion:no-preference) { html:not([data-reduced-motion=true]) .resume-toast { animation:resume-toast-in .16s ease; transition:opacity .16s ease; } } +@keyframes resume-toast-in { from { opacity:0; } to { opacity:1; } } .bookmark-move{display:flex;align-items:center;gap:10px;font-size:12px;color:var(--muted-foreground)}.bookmark-move select{min-width:0;max-width:65%;padding:8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--foreground)}