Files
lingo/design_mockups/play.html
T
ilaandClaude Opus 4.8 39713eb8ac design: 效果稿改为清淡简洁风格,T-200 完成
- play.html / list.html:从深色重金属改为浅暖纸白 + 墨色 + 单一柔和赭石强调,
  去掉颗粒/暗角/发光,UI 改用 Public Sans / Noto Sans SC;补齐焦点态、
  cursor、44px 触控、对比度、prefers-reduced-motion、aria-label
- README:设计语言更新为「清淡简洁 · Swiss/Minimal」
- 06-tasks:T-200 标记 DONE

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 17:21:14 +08:00

201 lines
8.7 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>播放页 · 走遍美国精听</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;1,6..72,400&family=Public+Sans:wght@400;500;600&family=Noto+Sans+SC:wght@400;500&display=swap" rel="stylesheet">
<style>
:root{
--paper:#f8f6f1; --surface:#ffffff;
--ink:#2a2724; --ink-soft:#56514a; --muted:#938b7e; --faint:#c2bbae;
--hair:#e9e3d8; --hair-soft:#f0ebe1;
--accent:#a4662b; --accent-2:#c0833f; --accent-soft:#f1e8d9;
--serif:"Newsreader",Georgia,serif;
--sans:"Public Sans",system-ui,sans-serif;
--han:"Noto Sans SC",sans-serif;
}
*{margin:0;padding:0;box-sizing:border-box}
html,body{height:100%}
body{
font-family:var(--sans); color:var(--ink); background:var(--paper);
display:flex; justify-content:center; min-height:100%;
-webkit-font-smoothing:antialiased;
}
.phone{
width:100%; max-width:440px; min-height:100vh; position:relative;
display:flex; flex-direction:column; background:var(--paper);
border-left:1px solid var(--hair); border-right:1px solid var(--hair);
}
:focus-visible{outline:2px solid var(--accent); outline-offset:3px; border-radius:6px}
/* ---------- top bar ---------- */
.top{
position:sticky; top:0; z-index:20; padding:16px 20px;
display:flex; align-items:center; gap:14px;
background:rgba(248,246,241,.9); backdrop-filter:blur(8px);
border-bottom:1px solid var(--hair);
}
.icon-btn{
width:40px;height:40px;border-radius:11px;border:1px solid var(--hair);
background:var(--surface); color:var(--ink-soft); display:grid;place-items:center;
cursor:pointer; transition:.2s; flex:none; font-size:18px;
}
.icon-btn:hover{border-color:var(--accent); color:var(--accent)}
.top-meta{flex:1; text-align:center; line-height:1.3}
.top-meta .ep{font-size:11px; letter-spacing:.16em; text-transform:uppercase; color:var(--accent); font-weight:600}
.top-meta .ti{font-family:var(--serif); font-size:18px; font-weight:500; color:var(--ink)}
.top-meta .cn{font-family:var(--han); font-size:11.5px; color:var(--muted); margin-top:1px}
.tag{font-size:10px;letter-spacing:.1em;color:var(--ink-soft);background:var(--accent-soft);
padding:5px 9px;border-radius:20px;flex:none;font-weight:500}
/* ---------- subtitle stream ---------- */
.stream{
flex:1; overflow:hidden; position:relative; padding:9vh 28px 6vh;
-webkit-mask-image:linear-gradient(180deg,transparent,#000 15%,#000 84%,transparent);
mask-image:linear-gradient(180deg,transparent,#000 15%,#000 84%,transparent);
}
.line{
font-family:var(--serif); font-size:20px; line-height:1.5; letter-spacing:.005em;
color:var(--faint); padding:13px 0 13px 18px; position:relative; cursor:pointer;
transition:color .25s ease, font-size .25s ease;
}
.line:hover{color:var(--muted)}
.line .t{
font-family:var(--sans); font-size:10.5px; color:var(--faint); letter-spacing:.06em;
opacity:0; transition:.2s; position:absolute; left:-1px; top:17px;
}
.line:hover .t{opacity:1}
/* active line */
.line.active{
color:var(--ink); font-size:24px; font-weight:500;
}
.line.active::before{
content:""; position:absolute; left:0; top:50%; transform:translateY(-50%);
width:3px; height:58%; border-radius:3px; background:var(--accent);
}
.line.done{color:var(--muted)}
/* ---------- player bar ---------- */
.player{
position:sticky; bottom:0; z-index:20; padding:16px 22px 24px;
background:var(--surface); border-top:1px solid var(--hair);
}
.seek{display:flex;align-items:center;gap:10px;margin-bottom:16px}
.seek .time{font-size:11px;color:var(--muted);font-variant-numeric:tabular-nums;letter-spacing:.03em}
.bar{flex:1;height:4px;border-radius:4px;background:var(--hair);position:relative;cursor:pointer}
.bar .fill{position:absolute;inset:0 60% 0 0;border-radius:4px;background:var(--accent)}
.bar .knob{position:absolute;left:40%;top:50%;width:12px;height:12px;border-radius:50%;
transform:translate(-50%,-50%);background:var(--accent);border:2px solid var(--surface);box-shadow:0 1px 3px rgba(0,0,0,.15)}
.controls{display:flex;align-items:center;justify-content:center;gap:30px}
.ctl{background:none;border:none;color:var(--ink-soft);cursor:pointer;display:grid;place-items:center;
min-width:44px;min-height:44px;transition:.2s;font-size:18px}
.ctl:hover{color:var(--accent)}
.ctl small{font-family:var(--sans);font-size:9px;letter-spacing:.08em;color:var(--muted);
display:block;text-align:center;margin-top:2px}
.play{
width:62px;height:62px;border-radius:50%;flex:none;
background:var(--accent); color:#fff;
display:grid;place-items:center;cursor:pointer;border:none;
box-shadow:0 4px 14px rgba(164,102,43,.28); transition:.15s;
}
.play:hover{background:var(--accent-2)}
.play:active{transform:scale(.95)}
.hint{text-align:center;font-size:10.5px;color:var(--muted);letter-spacing:.05em;margin-top:15px}
/* load-in */
.line{animation:rise .5s both}
@keyframes rise{from{opacity:0;transform:translateY(6px)}}
@media (prefers-reduced-motion: reduce){
*{animation:none!important; transition:none!important; scroll-behavior:auto!important}
}
</style>
</head>
<body>
<div class="phone">
<header class="top">
<button class="icon-btn" aria-label="返回课程列表">‹</button>
<div class="top-meta">
<div class="ep">EP 1 · Act I</div>
<div class="ti">46 Linden Street</div>
<div class="cn">林登大街 46 号</div>
</div>
<span class="tag">精听</span>
</header>
<main class="stream" id="stream"><!-- lines injected --></main>
<footer class="player">
<div class="seek">
<span class="time" id="cur">0:41</span>
<div class="bar" id="bar" role="slider" aria-label="播放进度"><div class="fill" id="fill"></div><div class="knob" id="knob"></div></div>
<span class="time">3:42</span>
</div>
<div class="controls">
<button class="ctl" id="prev" aria-label="上一句">⏮<small>上一句</small></button>
<button class="play" id="play" aria-label="播放或暂停">
<svg id="ico" width="22" height="22" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M8 5v14l11-7z"/></svg>
</button>
<button class="ctl" id="next" aria-label="下一句">⏭<small>下一句</small></button>
</div>
<div class="hint">点击任意一句 · 音频跳到该句重听</div>
</footer>
</div>
<script>
// 真实 ep1 act1 数据(节选)
const LINES = [
{t:34.18, en:"Excuse me. My name is Richard Stewart."},
{t:37.43, en:"I'm a photographer."},
{t:38.86, en:"May I take a picture of you and your little boy?"},
{t:40.55, en:"What's it for?"},
{t:41.41, en:"It's for a book."},
{t:42.48, en:"You're writing a book?"},
{t:44.18, en:"It's a book of pictures."},
{t:45.54, en:"I call it Family Album, U.S.A."},
{t:47.23, en:"Oh, that's a nice idea."},
];
const stream = document.getElementById('stream');
const fmt = s => Math.floor(s/60)+":"+String(Math.floor(s%60)).padStart(2,'0');
let active = 2, playing = false, timer = null;
function render(){
stream.innerHTML = '';
LINES.forEach((l,i)=>{
const d = document.createElement('div');
d.className = 'line' + (i===active?' active':'') + (i<active?' done':'');
d.style.animationDelay = (i*0.04)+'s';
d.innerHTML = `<span class="t">${l.t.toFixed(1)}s</span>${l.en}`;
d.onclick = ()=>{ active=i; sync(); }; // 点句跳转
stream.appendChild(d);
});
}
function sync(){
render();
const cur = stream.querySelector('.active');
cur && cur.scrollIntoView({behavior:'smooth', block:'center'});
document.getElementById('cur').textContent = fmt(LINES[active].t);
const pct = (active/(LINES.length-1))*100;
document.getElementById('fill').style.right = (100-pct)+'%';
document.getElementById('knob').style.left = pct+'%';
}
document.getElementById('play').onclick = ()=>{
playing = !playing;
document.getElementById('ico').innerHTML = playing
? '<path d="M6 5h4v14H6zM14 5h4v14h-4z"/>' // pause
: '<path d="M8 5v14l11-7z"/>'; // play
clearInterval(timer);
if(playing) timer = setInterval(()=>{
if(active < LINES.length-1){ active++; sync(); }
else { playing=false; document.getElementById('play').click(); }
}, 1700);
};
document.getElementById('prev').onclick = ()=>{ if(active>0){active--;sync();} };
document.getElementById('next').onclick = ()=>{ if(active<LINES.length-1){active++;sync();} };
sync();
</script>
</body>
</html>