feat(viewer): refine visual details to match design reference

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-22 13:49:15 +08:00
parent 9dbdb0084e
commit 900ff2a74d
10 changed files with 162 additions and 132 deletions
+17 -11
View File
@@ -57,11 +57,8 @@ onBeforeUnmount(() => {
<div v-else class="viewer-shell">
<header class="topbar">
<span class="brand">
<svg class="brand-logo" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<rect x="1" y="1" width="22" height="22" rx="6" fill="var(--viewer-primary)" />
<circle cx="12" cy="12" r="5.5" fill="none" stroke="#FFFFFF" stroke-width="2" />
<circle cx="18" cy="6" r="1.6" fill="#FFFFFF" />
</svg>
<!-- 参考稿:蓝色圆形印记(26px 圆 + 白 ◈)+ PhotoLive 文本 -->
<span class="brand-logo" aria-hidden="true">◈</span>
<span class="brand-name">{{ t('viewer.brand') }}</span>
</span>
<div class="topbar-actions">
@@ -133,7 +130,7 @@ body {
display: flex;
align-items: center;
justify-content: space-between;
height: 50px;
height: 52px;
padding: 0 var(--viewer-pad-info);
border-bottom: 1px solid var(--viewer-divider);
background: var(--viewer-surface);
@@ -144,15 +141,24 @@ body {
gap: 8px;
min-width: 0;
}
/* 参考稿品牌印记:26px 主色圆 + 白 ◈ */
.brand-logo {
width: 24px;
height: 24px;
display: inline-flex;
align-items: center;
justify-content: center;
width: 26px;
height: 26px;
flex: none;
border-radius: 50%;
background: var(--viewer-primary);
color: #ffffff;
font-size: 15px;
line-height: 1;
}
.brand-name {
font-size: 1.0625rem;
font-weight: 700;
letter-spacing: 0.01em;
font-size: 1.3125rem;
font-weight: 750;
letter-spacing: -0.7px;
color: var(--viewer-text);
}
.topbar-actions {
+2 -1
View File
@@ -93,8 +93,9 @@ describe('activity page (Task 6)', () => {
expect(wrapper.find('.empty-notice').text()).toBe('照片即将上线');
// 无 banner 资产 → 不渲染 img;viewCount 未知(null)→ 浏览项显示「—」而非 0(UI-02 §4.1)。
// (参考稿:浏览项前置 ◉ Unicode 图标,属渲染内容的一部分。)
expect(wrapper.find('img.banner').exists()).toBe(false);
expect(wrapper.find('.view-count').text()).toBe('—');
expect(wrapper.find('.view-count').text()).toBe('◉—');
expect(document.title).toBe('2026 公司年会');
});
@@ -73,8 +73,8 @@ describe('album header info area (UI-02 V02)', () => {
expect(wrapper.find('h1.title').text()).toBe('2026 公司年会');
// pre-line:换行保留;文本插值(非 HTML 注入)。
expect(wrapper.find('.description').text()).toBe('年度盛会\n多行介绍');
// Intl.NumberFormat 分组 + 词条拼接(§4.1)。
expect(wrapper.find('.view-count').text()).toBe('1,286 次浏览');
// Intl.NumberFormat 分组 + 词条拼接(§4.1);参考稿:浏览项前置 ◉ Unicode 图标。
expect(wrapper.find('.view-count').text()).toBe('◉1,286 次浏览');
expect(wrapper.find('.meta').text()).toContain('大宴会厅 · 上海市浦东新区世纪大道 100 号');
// Asia/Shanghai(夹具时区):UTC 10-01 → 本地 10月1日。
expect(wrapper.find('.meta').text()).toContain('10月1日');
@@ -98,7 +98,7 @@ describe('album header info area (UI-02 V02)', () => {
expect(wrapper.find('img.banner').exists()).toBe(false);
expect(wrapper.find('.description').exists()).toBe(false);
expect(wrapper.findAll('.meta-item')).toHaveLength(2); // 浏览(—)+ 日期;地点整项省略
expect(wrapper.find('.view-count').text()).toBe('—');
expect(wrapper.find('.view-count').text()).toBe('◉—');
expect(wrapper.find('.categories').exists()).toBe(false);
});
@@ -59,7 +59,8 @@ describe('viewer shell tokens and embed isolation (UI-02 V01)', () => {
'--viewer-like-accent': '#FF667D',
'--viewer-font-family': "system-ui, -apple-system, 'Segoe UI', 'PingFang SC', sans-serif",
'--viewer-radius-img': '4px',
'--viewer-radius-pill': '20px',
'--viewer-radius-pill': '22px',
'--viewer-chip-bg': '#F5F7FA',
'--viewer-pad-info': '16px',
'--viewer-pad-photo': '4px',
'--viewer-touch-target': '44px',
+37 -17
View File
@@ -311,23 +311,26 @@ onBeforeUnmount(() => {
white-space: nowrap;
}
/* —— 新照片提示 pill:白底蓝字蓝icon,悬浮照片区顶部 —— */
/* —— 新照片提示 pill(参考稿):顶部 8px 居中悬浮、白底蓝字、34px 高、
* 22px 圆角、轻投影 0 3px 15px —— */
.new-photo-bar {
position: sticky;
top: 10px;
top: 8px;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
width: fit-content;
min-height: 34px;
margin: 0 auto 12px;
border: none;
border-radius: var(--viewer-radius-pill, 20px);
border-radius: var(--viewer-radius-pill, 22px);
background: var(--viewer-surface, #fff);
box-shadow: 0 2px 10px rgba(23, 32, 51, 0.16);
box-shadow: 0 3px 15px rgba(23, 32, 51, 0.14);
color: var(--viewer-primary, #1a73e8);
padding: 8px 16px;
font-size: 0.8125rem;
padding: 0 17px;
font-size: 0.75rem;
font-weight: 600;
cursor: pointer;
}
@@ -357,6 +360,18 @@ onBeforeUnmount(() => {
background: var(--viewer-divider, #e5eaf1);
cursor: pointer;
}
/* 参考稿:底部 40% 高度的轻渐变(transparent → rgba(0,0,0,.25)),
* 承托右下角白色点赞角标;不拦截指针(点击仍开大图)。 */
.cell::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 40%;
background: linear-gradient(transparent, rgba(0, 0, 0, 0.25));
pointer-events: none;
}
.cell-img {
display: block;
width: 100%;
@@ -364,32 +379,36 @@ onBeforeUnmount(() => {
object-fit: cover;
}
/* —— 点赞角标:右下角半透明暗底(明亮照片上可读),白心 + 白色计数 —— */
/* —— 点赞角标(参考稿):右下 3px 贴边、无底色胶囊——白心 19px + 白色计数,
* 仅靠 text-shadow 保证明亮照片上的可读性 —— */
.cell-like {
position: absolute;
right: 6px;
bottom: 6px;
right: 3px;
bottom: 3px;
z-index: 1;
display: inline-flex;
align-items: center;
gap: 4px;
border: none;
border-radius: var(--viewer-radius-pill, 20px);
background: linear-gradient(to right, rgba(14, 18, 24, 0.42), rgba(14, 18, 24, 0.62));
background: transparent;
color: #fff;
padding: 3px 8px;
padding: 2px 3px;
font-size: 0.75rem;
line-height: 1;
text-shadow: 0 1px 4px #222;
cursor: pointer;
}
.cell-like .heart {
width: 16px;
height: 16px;
width: 19px;
height: 19px;
fill: #fff;
filter: drop-shadow(0 1px 4px #222);
animation: heart-pop 400ms ease-out;
}
.cell-like .count {
color: #fff;
font-variant-numeric: tabular-nums;
text-shadow: 0 1px 4px #222;
}
@keyframes heart-pop {
0% {
@@ -459,11 +478,12 @@ onBeforeUnmount(() => {
text-align: center;
padding: 8px 0;
}
/* 参考稿:到底提示 #96a0b0、12px、24px 16px 收底 */
.end-note {
text-align: center;
color: var(--viewer-text-secondary, #647084);
font-size: 0.875rem;
padding: 12px 0;
color: #96a0b0;
font-size: 0.75rem;
padding: 24px 16px;
margin: 0;
}
.sentinel {
+23 -23
View File
@@ -602,6 +602,7 @@ onBeforeUnmount(() => {
</div>
</div>
<!-- 参考稿:42px 细体(200)字形箭头 + 半透明圆底 rgba(0,0,0,.13) -->
<button
type="button"
class="nav prev"
@@ -609,9 +610,7 @@ onBeforeUnmount(() => {
:aria-label="t('viewer.previous')"
@click="goTo(index - 1)"
>
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="m15 18-6-6 6-6" />
</svg>
<span class="nav-glyph" aria-hidden="true">‹</span>
</button>
<button
type="button"
@@ -620,9 +619,7 @@ onBeforeUnmount(() => {
:aria-label="t('viewer.next')"
@click="goTo(index + 1)"
>
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="m9 6 6 6-6 6" />
</svg>
<span class="nav-glyph" aria-hidden="true">›</span>
</button>
<!-- 心形粒子层(Task 5 / V05 §6.2):装饰性、不可交互;触发见上方互动编排 -->
@@ -849,23 +846,25 @@ onBeforeUnmount(() => {
padding: 4px 12px;
cursor: pointer;
}
/* 参考稿切换箭头:44px 宽 48px 高圆钮、半透明黑底 rgba(0,0,0,.13)、
* 42px/200 细体字形、白字 + 深色 text-shadow;top calc(50% - 24px)。 */
.nav {
position: absolute;
top: 50%;
transform: translateY(-50%);
top: calc(50% - 24px);
display: flex;
align-items: center;
justify-content: center;
width: var(--viewer-touch-target, 44px);
height: var(--viewer-touch-target, 44px);
height: 48px;
border: none;
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
background: rgba(0, 0, 0, 0.13);
color: #ffffff;
padding: 0;
cursor: pointer;
}
.nav:hover:not(:disabled) {
background: rgba(255, 255, 255, 0.2);
background: rgba(0, 0, 0, 0.3);
}
.nav:focus-visible {
outline: 2px solid var(--viewer-primary, #1a73e8);
@@ -874,20 +873,20 @@ onBeforeUnmount(() => {
opacity: 0.3;
cursor: default;
}
.nav svg {
width: 28px;
height: 28px;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
.nav-glyph {
font-size: 2.625rem;
font-weight: 200;
line-height: 1;
text-shadow: 0 1px 8px #000;
/* 字形基线在圆内视觉居中 */
transform: translateY(-2px);
user-select: none;
}
.nav.prev {
left: 8px;
left: 4px;
}
.nav.next {
right: 8px;
right: 4px;
}
/* 粒子层/互动提示层的定位与样式由 HeartParticles / InteractionNotice 自带
@@ -936,8 +935,9 @@ onBeforeUnmount(() => {
* PhotoActionBar(§5.4:下载关闭移除整项并均分剩余栏宽)。 */
.viewer-actionbar {
flex: none;
padding: 6px 12px;
padding-bottom: calc(6px + env(safe-area-inset-bottom, 0px));
/* 参考稿:5px 16px(+ 底部安全区) */
padding: 5px 16px;
padding-bottom: calc(5px + env(safe-area-inset-bottom, 0px));
background: rgba(14, 18, 24, 0.72);
}
@@ -184,13 +184,9 @@ onBeforeUnmount(() => {
<header class="album-info">
<div class="title-row">
<h1 ref="titleEl" class="title" :class="{ clamped: !titleExpanded }">{{ album.translation.title }}</h1>
<!-- 参考稿:44px 主色圆钮 + 白 ↗ 图标(文案由 aria-label 承载) -->
<button type="button" class="share-btn" :aria-label="t('viewer.shareAlbum')" @click="share">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="M12 3v12" />
<path d="m7 8 5-5 5 5" />
<path d="M5 13v6a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-6" />
</svg>
<span>{{ t('viewer.share') }}</span>
<span class="share-icon" aria-hidden="true">↗</span>
</button>
</div>
<button v-if="titleClamped" type="button" class="text-expand" @click="titleExpanded = !titleExpanded">
@@ -213,26 +209,18 @@ onBeforeUnmount(() => {
{{ descExpanded ? t('viewer.readLess') : t('viewer.readMore') }}
</button>
<!-- 参考稿:元信息行用简洁 Unicode 图标(◉ 浏览 / ▦ 日期 / ♧ 地点) -->
<div class="meta">
<span class="meta-item view-count">
<svg class="meta-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z" />
<circle cx="12" cy="12" r="3" />
</svg>
<span class="meta-icon" aria-hidden="true">◉</span>
<span>{{ viewsText }}</span>
</span>
<span v-if="dateText !== ''" class="meta-item" :title="dateFullText">
<svg class="meta-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<rect x="3" y="5" width="18" height="16" rx="2" />
<path d="M8 3v4M16 3v4M3 10h18" />
</svg>
<span class="meta-icon" aria-hidden="true">▦</span>
<span>{{ dateText }}</span>
</span>
<span v-if="venueText !== ''" class="meta-item">
<svg class="meta-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="M12 21s-7-5.1-7-11a7 7 0 0 1 14 0c0 5.9-7 11-7 11Z" />
<circle cx="12" cy="10" r="2.5" />
</svg>
<span class="meta-icon" aria-hidden="true">♧</span>
<span>{{ venueText }}</span>
</span>
</div>
@@ -297,12 +285,12 @@ onBeforeUnmount(() => {
background: var(--viewer-surface, #fff);
}
/* —— 宣传图:手机约 2.4:1;桌面限高 320px;contain 保海报文字(§3.3) —— */
/* —— 宣传图:手机约 2.46:1;桌面限高 320px;contain 保海报文字(§3.3;占位 #dbe6ee) —— */
.banner-wrap {
width: 100%;
aspect-ratio: 2.4 / 1;
aspect-ratio: 2.46 / 1;
max-height: 320px;
background: var(--viewer-surface, #fff);
background: #dbe6ee;
}
.banner {
display: block;
@@ -311,9 +299,9 @@ onBeforeUnmount(() => {
object-fit: contain;
}
/* —— 信息区:16px 内边距由本区负责(页面无全局包围,§3.2) —— */
/* —— 信息区:16px 内边距由本区负责(页面无全局包围,§3.2;参考稿 12px 16px 0) —— */
.album-info {
padding: 12px var(--viewer-pad-info, 16px) 4px;
padding: 12px var(--viewer-pad-info, 16px) 0;
}
.title-row {
@@ -321,13 +309,15 @@ onBeforeUnmount(() => {
align-items: flex-start;
gap: 12px;
}
/* 参考稿:22px/28px、字距 -0.6px */
.title {
flex: 1;
min-width: 0;
margin: 0;
font-size: 1.375rem;
line-height: 1.3;
line-height: 28px;
font-weight: 700;
letter-spacing: -0.6px;
color: var(--viewer-text, #172033);
overflow-wrap: break-word;
}
@@ -337,34 +327,35 @@ onBeforeUnmount(() => {
-webkit-line-clamp: 2;
overflow: hidden;
}
/* 参考稿:44px 主色圆形分享钮(白 ↗,无边框) */
.share-btn {
flex: none;
display: inline-flex;
align-items: center;
gap: 6px;
min-height: var(--viewer-touch-target, 44px);
margin: -6px calc(var(--viewer-pad-info, 16px) * -1) -6px 0;
justify-content: center;
width: var(--viewer-touch-target, 44px);
height: var(--viewer-touch-target, 44px);
margin: -6px 0 -6px 0;
border: none;
background: transparent;
color: var(--viewer-primary, #1a73e8);
padding: 0 6px;
border-radius: 50%;
background: var(--viewer-primary, #1a73e8);
color: var(--viewer-surface, #fff);
padding: 0;
align-self: flex-start;
font-size: 0.875rem;
font-weight: 600;
cursor: pointer;
}
.share-btn svg {
width: 18px;
height: 18px;
fill: none;
stroke: currentColor;
stroke-width: 1.8;
stroke-linecap: round;
stroke-linejoin: round;
.share-btn:hover {
filter: brightness(0.94);
}
.share-icon {
font-size: 1.25rem;
line-height: 1;
/* 视觉基线微调:↗ 略下沉 */
transform: translateY(-1px);
}
.share-btn:focus-visible {
outline: 2px solid var(--viewer-primary, #1a73e8);
border-radius: 8px;
outline-offset: 2px;
}
.share-status {
display: block;
@@ -394,9 +385,10 @@ onBeforeUnmount(() => {
border-radius: 4px;
}
/* —— 说明:≤3 行 + 展开/收起;保留换行(pre-line),文本插值非 HTML 注入 —— */
/* —— 说明:≤3 行 + 展开/收起;保留换行(pre-line),文本插值非 HTML 注入。
* 参考稿:15px、次级色、上下外距 3px/9px —— */
.description {
margin: 8px 0 0;
margin: 3px 0 9px;
color: var(--viewer-text-secondary, #647084);
font-size: 0.9375rem;
line-height: 1.55;
@@ -410,15 +402,18 @@ onBeforeUnmount(() => {
overflow: hidden;
}
/* —— 元信息行:窄屏自然换行(§4.1);数字 tabular-nums —— */
/* —— 元信息行:窄屏自然换行(§4.1);数字 tabular-nums。
* 参考稿:12px、行距 8px 20px、底部 1px 分隔线 + 10px 收底 —— */
.meta {
display: flex;
flex-wrap: wrap;
gap: 6px 20px;
margin: 12px 0 0;
gap: 8px 20px;
margin: 9px 0 0;
padding-bottom: 10px;
border-bottom: 1px solid var(--viewer-divider, #e5eaf1);
color: var(--viewer-text-secondary, #647084);
font-size: 0.8125rem;
line-height: 1.4;
font-size: 0.75rem;
line-height: 1.5;
}
.meta-item {
display: inline-flex;
@@ -430,28 +425,24 @@ onBeforeUnmount(() => {
.meta-item span {
overflow-wrap: break-word;
}
/* Unicode 图标与正文同尺寸(参考稿:简洁字符图标) */
.meta-icon {
flex: none;
width: 16px;
height: 16px;
fill: none;
stroke: currentColor;
stroke-width: 1.8;
stroke-linecap: round;
stroke-linejoin: round;
width: 1em;
line-height: 1;
text-align: center;
}
.view-count {
font-variant-numeric: tabular-nums;
}
/* —— 排序 tab —— */
/* —— 排序 tab(参考稿:分隔线由上方元信息行承担;选中项 2px 底线) —— */
.sort-tabs {
display: flex;
gap: 24px;
margin-top: 8px;
/* 标签与信息区 16px 对齐(效果图 §3.1/§4.2);分隔线保持通栏。 */
/* 标签与信息区 16px 对齐(效果图 §3.1/§4.2)。 */
padding: 0 var(--viewer-pad-info, 16px);
border-bottom: 1px solid var(--viewer-divider, #e5eaf1);
}
.sort-tab {
position: relative;
@@ -467,13 +458,13 @@ onBeforeUnmount(() => {
.sort-tab.active {
color: var(--viewer-primary, #1a73e8);
}
/* 选中底部 2px 线(§4.2) */
/* 选中底部 2px 线(参考稿:absolute bottom 0 / left 0 / right 0) */
.sort-tab.active::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: -1px;
bottom: 0;
height: 2px;
background: var(--viewer-primary, #1a73e8);
}
@@ -501,22 +492,22 @@ onBeforeUnmount(() => {
margin: -2px 0 0;
scrollbar-width: thin;
}
/* 参考稿:胶囊 22px 圆角、#f5f7fa 底、32px 高、12px 字号;选中主色实底白字 */
.chip {
flex: none;
min-height: 36px;
min-height: 32px;
border: 1px solid var(--viewer-divider, #e5eaf1);
border-radius: var(--viewer-radius-pill, 20px);
background: var(--viewer-surface, #fff);
border-radius: var(--viewer-radius-pill, 22px);
background: var(--viewer-chip-bg, #f5f7fa);
color: var(--viewer-text, #172033);
padding: 0 16px;
font-size: 0.8125rem;
padding: 0 15px;
font-size: 0.75rem;
cursor: pointer;
}
.chip.active {
border-color: var(--viewer-primary, #1a73e8);
background: var(--viewer-primary, #1a73e8);
color: var(--viewer-surface, #fff);
font-weight: 600;
}
.chip:focus-visible {
outline: 2px solid var(--viewer-primary, #1a73e8);
@@ -158,13 +158,18 @@ onBeforeUnmount(cancelPress);
<style scoped>
/* 深色底部操作栏(§5.1:约 88-104px + 安全区,安全区 padding 由父级 footer 承担)。
* token 经 .photo-viewer 链 var() 回退,独立挂载也成立。 */
/* 参考稿:操作栏居中排布、间距 20px;每键固定 110px 宽的纵排
* (图标 → 计数 → 文案)。下载关闭时剩余键仍居中(不拉伸占满)。 */
.action-bar {
display: flex;
align-items: stretch;
justify-content: center;
gap: 20px;
min-height: 88px;
}
.action-slot {
flex: 1 1 0;
flex: 0 0 auto;
width: 110px;
min-width: 0;
display: flex;
flex-direction: column;
@@ -189,9 +194,10 @@ onBeforeUnmount(cancelPress);
outline: 2px solid var(--viewer-primary, #1a73e8);
border-radius: 8px;
}
/* 参考稿:操作图标 30px;Like 心形 38px(coral 实心)。 */
.action-icon {
width: 24px;
height: 24px;
width: 30px;
height: 30px;
fill: none;
stroke: currentColor;
stroke-width: 1.8;
@@ -200,6 +206,8 @@ onBeforeUnmount(cancelPress);
}
/* Like:coral 实心心形 + 累计数(tabular-nums 稳定数字宽度)。 */
.like-icon {
width: 38px;
height: 38px;
fill: var(--viewer-like-accent, #ff667d);
stroke: var(--viewer-like-accent, #ff667d);
animation: heart-pop 400ms ease-out;
@@ -219,16 +227,16 @@ onBeforeUnmount(cancelPress);
:global(.viewer-reduced-motion) .like-icon {
animation: none;
}
/* 参考稿:计数 16px/700(tabular-nums);次级提示 #b4b9c1、12px。 */
.like-count {
font-variant-numeric: tabular-nums;
font-size: 1.0625rem;
font-size: 1rem;
font-weight: 700;
line-height: 1.2;
}
.like-hint {
margin: 2px 0 0;
max-width: 180px;
color: rgba(255, 255, 255, 0.55);
margin: 4px 0 0;
color: #b4b9c1;
font-size: 12px;
line-height: 1.3;
text-align: center;
@@ -133,11 +133,11 @@ watch(
.thumb-track::-webkit-scrollbar {
display: none;
}
/* §5.3:宽约 56-64px、高约 44-52px;非当前项保留透明 2px 边框防切换抖动。 */
/* §5.3 + 参考稿:62x48、2px 描边(当前项主色);非当前项降不透明度 .7。 */
.thumb {
flex: none;
display: block;
width: 60px;
width: 62px;
height: 48px;
padding: 0;
border: 2px solid transparent;
@@ -145,9 +145,11 @@ watch(
background: rgba(255, 255, 255, 0.08);
overflow: hidden;
cursor: pointer;
opacity: 0.7;
}
.thumb.current {
border-color: var(--viewer-primary, #1a73e8);
opacity: 1;
}
.thumb:focus-visible {
outline: 2px solid var(--viewer-primary, #1a73e8);
+2 -1
View File
@@ -15,7 +15,8 @@
--viewer-like-accent: #FF667D;
--viewer-font-family: system-ui, -apple-system, 'Segoe UI', 'PingFang SC', sans-serif;
--viewer-radius-img: 4px;
--viewer-radius-pill: 20px;
--viewer-radius-pill: 22px;
--viewer-chip-bg: #F5F7FA;
--viewer-pad-info: 16px;
--viewer-pad-photo: 4px;
--viewer-touch-target: 44px;