Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 119 additions & 18 deletions en/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,6 @@
.icon-btn:hover{color:var(--ink)}
.icon-btn svg{width:var(--icon);height:var(--icon)}
button,input,kbd,summary{font-family:var(--sans);font-size:var(--fs-ui)}
.lang-dd summary svg{width:var(--chevron);height:var(--chevron)}
.nav-r{display:flex;align-items:center;gap:var(--sp-2);margin-left:auto}
.switch{display:none!important}
.theme-btn .sun{display:none}
Expand All @@ -218,7 +217,7 @@

/* ---------- language dropdown ---------- */
.lang-dd{position:relative}
.lang-dd>summary{list-style:none;cursor:pointer;display:grid;place-items:center;height:var(--icon-btn);width:auto;min-width:var(--icon-btn);border:0;background:none;color:var(--faint);grid-auto-flow:column;grid-template-columns:auto auto;column-gap:var(--sp-1);align-items:center;padding:0 var(--sp-1);transition:color .15s}
.lang-dd>summary{list-style:none;cursor:pointer;display:grid;place-items:center;height:var(--icon-btn);width:auto;min-width:var(--icon-btn);border:0;background:none;color:var(--faint);grid-auto-flow:column;grid-template-columns:auto auto;column-gap:var(--sp-1);padding:0 var(--sp-1);transition:color .15s}
.lang-dd>summary::-webkit-details-marker{display:none}
.lang-dd>summary:hover{color:var(--ink)}
.lang-dd>summary svg{width:var(--chevron);height:var(--chevron);color:var(--faint);fill:none;stroke:currentColor;stroke-width:2}
Expand Down Expand Up @@ -307,6 +306,27 @@
.sec-h .shown{margin-left:auto;flex:none;font:var(--fw-ui) var(--fs-ui)/var(--lh-ui) var(--sans);color:var(--faint);font-variant-numeric:tabular-nums}
.intro{margin:var(--sp-3) 0 var(--sp-6);font:var(--fs-2)/var(--lh-body) var(--serif);color:var(--text)}

/* ---------- loading skeletons ---------- */
.skel{max-width:var(--measure);margin:0 auto}
.skel-sec{margin:0 0 var(--sec-gap)}
.skel-h{display:flex;align-items:center;gap:var(--sec-h-gap);padding-top:var(--sp-4);margin-bottom:var(--sp-2)}
.skel-h .skel-bar{height:1.15em;flex:none}
.skel-h .skel-bar:first-child{flex:1;max-width:18rem}
.skel-h .skel-bar-sm{margin-left:auto}
.skel-card{padding:var(--card-pad-y);display:flex;flex-direction:column;gap:var(--sp-2)}
.skel-bar{
display:block;height:1.05em;max-width:100%;border-radius:2px;
background:color-mix(in srgb, var(--faint) 22%, var(--bg));
animation:skel-pulse 1.2s ease-in-out infinite;
}
.skel-bar-body{height:.85em}
.skel-bar-sm{height:.7em}
.skel-card > .skel-bar:first-child{height:1.15em;margin-bottom:var(--sp-1)}
.skel-stat{display:inline-block;width:2.4em;height:.75em;vertical-align:-0.05em;margin:0 .12em}
.stat-fallback{display:none}
@keyframes skel-pulse{0%,100%{opacity:1}50%{opacity:.45}}
@media (prefers-reduced-motion:reduce){.skel-bar{animation:none}}

.card{padding:var(--card-pad-y);content-visibility:auto;contain-intrinsic-size:auto var(--card-intrinsic)}
.card-h{display:flex;align-items:baseline;gap:var(--idx-gap)}
.idx{flex:none;min-width:var(--idx-min);font:var(--fw-ui) var(--fs-ui)/var(--lh-ui) var(--serif);font-variant-numeric:tabular-nums;color:var(--faint)}
Expand All @@ -332,7 +352,9 @@
.src summary{display:inline-block;cursor:pointer;font:var(--fw-ui) var(--fs-ui)/var(--lh-ui) var(--sans);color:var(--faint);transition:color .15s;padding:var(--sp-2) 0;margin:calc(var(--sp-2) * -1) 0}
.src summary:hover{color:var(--ink)}
.src .body{margin-top:var(--sp-3);font:var(--fs-2)/var(--lh-body) var(--serif);color:var(--text);overflow-wrap:break-word}
.src .body ul{margin:var(--sp-2) 0 0;padding-left:var(--sp-4)}
.src .body ul{margin:var(--sp-2) 0 0;padding-left:var(--sp-5);list-style:disc}
.src .body li{margin:0 0 var(--sp-2);padding-left:var(--sp-1)}
.src .body li:last-child{margin-bottom:0}
.src .body p{margin:0 0 var(--sp-3)}
.src .body p:last-child{margin-bottom:0}
.src .body a{color:var(--hover)}
Expand Down Expand Up @@ -379,9 +401,14 @@

/* ---------- mobile (bp 1100 / 560 — literals required) ---------- */
@media (max-width:1099px){
.nav{left:0}
.nav{left:0;transition:transform .22s ease,left var(--side-dur)}
html.nav-hidden .nav{transform:translateY(-100%)}
@media (prefers-reduced-motion:reduce){
.nav{transition:left var(--side-dur)}
html.nav-hidden .nav{transform:none}
}
.icon-btn.menu-btn{display:grid}
.nav-title{display:flex}
.nav-title{display:none} /* бренд в drawer (.side-title) и в h1; topbar не дублирует */
.nav-in{padding:0 calc((var(--nav-h) - var(--icon-btn)) / 2)}
.side-title{display:flex;margin:0 0 var(--group-gap);font:var(--fw-strong) var(--fs-2)/var(--lh-tight) var(--serif);color:var(--ink);white-space:normal;padding:0}
.search{position:relative;left:auto;transform:none;flex:1;width:auto;max-width:none}
Expand All @@ -397,15 +424,10 @@
.badges,.human,.rows,.src{margin-left:0}
}
@media (max-width:560px){
.nav-title span:last-child{display:none}
.nav-title .logo{display:none}
/* wider text column on phones: .doc desktop padding stacks with .content's */
.content{padding-left:var(--sp-4);padding-right:var(--sp-4)}
.doc{padding-left:0;padding-right:0}
.nav-title .logo svg{width:var(--icon);height:var(--icon)}
.ic-doc{display:none}
.nav-r{gap:var(--sp-1)}
.nav-r .icon-btn{width:var(--icon-btn);height:var(--icon-btn)}
.search{max-width:none}
.nav-in{gap:var(--sp-2)}
.doc-head h1{font-size:var(--fs-4)}
Expand Down Expand Up @@ -437,7 +459,7 @@ <h1>Пособие по выгодной жизни</h1>
</label>
<div class="nav-r">
<details class="lang-dd" id="lang">
<summary class="icon-btn" title="Language / Язык / 语言" aria-label="Выбрать язык"><span id="lang-label">РУ</span><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></summary>
<summary title="Language / Язык / 语言" aria-label="Выбрать язык"><span id="lang-label">РУ</span><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></summary>
<div class="lang-menu" role="menu">
<button role="menuitem" data-lang="ru">Русский</button>
<button role="menuitem" data-lang="en">English</button>
Expand Down Expand Up @@ -534,10 +556,40 @@ <h1>Пособие по выгодной жизни</h1>
<span id="fb-star-t">Понравился перевод —</span>
<a id="fb-star" href="https://github.com/dlgrv/HowToLiveBetter" target="_blank" rel="noopener">поставьте звезду репозиторию</a>
</p>
<div class="stat" aria-live="polite">Показано <span id="cnt">–</span> из <span id="tot">–</span></div>
<div class="stat" aria-live="polite">Показано <span id="cnt"><i class="skel-bar skel-stat" aria-hidden="true"></i><span class="stat-fallback">–</span></span> из <span id="tot"><i class="skel-bar skel-stat" aria-hidden="true"></i><span class="stat-fallback">–</span></span></div>
</div>
<div id="list">
<div class="status" id="status"></div>
<div id="list" aria-busy="true">
<div class="skel" id="skel" aria-hidden="true"></div>
<script>
(function(){
var sk=document.getElementById('skel'); if(!sk) return;
var H=[[42,3],[48,3],[36,3],[55,3],[40,3],[50,3]];
var C=[
[72,100,88,92,78],[64,96,70,85,60],[68,100,82,90,74],
[58,94,76,88,66],[75,98,84,91,71],[61,95,68,80,55],
[70,100,80,86,72],[66,97,73,89,63],[77,99,85,93,69]
];
function card(a){
return '<div class="skel-card">'
+'<i class="skel-bar" style="width:'+a[0]+'%"></i>'
+'<i class="skel-bar skel-bar-body" style="width:'+a[1]+'%"></i>'
+'<i class="skel-bar skel-bar-body" style="width:'+a[2]+'%"></i>'
+'<i class="skel-bar skel-bar-sm" style="width:5rem;margin-top:var(--sp-3)"></i>'
+'<i class="skel-bar skel-bar-body" style="width:'+a[3]+'%"></i>'
+'<i class="skel-bar skel-bar-sm" style="width:4.5rem;margin-top:var(--sp-3)"></i>'
+'<i class="skel-bar skel-bar-body" style="width:'+a[4]+'%"></i>'
+'</div>';
}
var html='', i, j;
for(i=0;i<H.length;i++){
html+='<div class="skel-sec"><div class="skel-h"><i class="skel-bar" style="width:'+H[i][0]+'%"></i><i class="skel-bar skel-bar-sm" style="width:3.5rem"></i></div>';
for(j=0;j<3;j++) html+=card(C[(i*3+j)%C.length]);
html+='</div>';
}
sk.innerHTML=html;
})();
</script>
<div class="status" id="status" hidden></div>
<div class="empty" id="empty" hidden>
Ничего не найдено. Уберите один из фильтров или попробуйте более короткий запрос.
<div><button id="reset2">Сбросить</button></div>
Expand Down Expand Up @@ -1079,7 +1131,22 @@ <h1>Пособие по выгодной жизни</h1>
function srcCount(s){
const n = ((s || '').match(/https?:\/\//g) || []).length;
if (n) return n;
return (s || '').trim() ? 1 : 0;
return splitSources(s).length;
}
function splitSources(s){
return (s || '').split(/\s*[;;]\s*/).map(x => x.trim()).filter(Boolean);
}
function renderSources(el, text, terms){
el.textContent = '';
const parts = splitSources(text);
if (!parts.length) return;
const ul = document.createElement('ul');
for (const p of parts){
const li = document.createElement('li');
renderText(li, p, terms);
ul.appendChild(li);
}
el.appendChild(ul);
}
function renderCard(card, terms, key){
const {e, f} = card;
Expand All @@ -1098,7 +1165,7 @@ <h1>Пособие по выгодной жизни</h1>
mg.hidden = !hasNote;
if (hasNote){ mg.querySelector('.k').textContent = T().kNote; renderText(mg.querySelector('.mt'), e.note, terms); }
}
renderText(f.src, e.src, terms);
renderSources(f.src, e.src, terms);
if (f.srcSum) f.srcSum.textContent = T().src + ' (' + srcCount(e.src) + ')';
const inBody = (e.title+e.human+e.cost+e.gain+e.note).toLowerCase();
f.details.open = terms.length > 0 && terms.some(t => e.src.toLowerCase().includes(t)) && !terms.some(t => inBody.includes(t));
Expand Down Expand Up @@ -1157,8 +1224,31 @@ <h1>Пособие по выгодной жизни</h1>
/* ---------- 事件 ---------- */
function wire(){
const sb = document.getElementById('sidebar'), bd = document.getElementById('backdrop'), mb = document.getElementById('menu');
const setMenu = (on) => { sb.classList.toggle('open', on); bd.classList.toggle('on', on); mb.setAttribute('aria-expanded', String(on)); };
const setMenu = (on) => {
sb.classList.toggle('open', on); bd.classList.toggle('on', on); mb.setAttribute('aria-expanded', String(on));
if (on) document.documentElement.classList.remove('nav-hidden');
};
const toTop = () => { setMenu(false); scrollTo({ top: 0, behavior: matchMedia('(prefers-reduced-motion: reduce)').matches ? 'auto' : 'smooth' }); };
/* mobile: hide topbar on scroll down, show on scroll up */
{
const mq = matchMedia('(max-width:1099px)');
const reduce = matchMedia('(prefers-reduced-motion: reduce)');
let lastY = scrollY, ticking = false;
const showNav = () => document.documentElement.classList.remove('nav-hidden');
addEventListener('scroll', () => {
if (!window.__HTLB_V2__ || !mq.matches || reduce.matches) { showNav(); return; }
if (ticking) return;
ticking = true;
requestAnimationFrame(() => {
const y = scrollY, dy = y - lastY;
if (y < 12 || sb.classList.contains('open')) showNav();
else if (dy > 6) document.documentElement.classList.add('nav-hidden');
else if (dy < -6) showNav();
lastY = y; ticking = false;
});
}, {passive:true});
mq.addEventListener('change', () => { if (!mq.matches) showNav(); });
}
document.querySelectorAll('[data-dim]').forEach(g => g.addEventListener('click', ev => {
const b = ev.target.closest('[data-v]'); if (!b) return;
const dim = g.dataset.dim, v = b.dataset.v, set = state[dim];
Expand Down Expand Up @@ -1231,8 +1321,13 @@ <h1>Пособие по выгодной жизни</h1>
/* ---------- 启动 ---------- */
(async function init(){
applyI18n();
const list = document.getElementById('list');
const status = document.getElementById('status');
status.textContent = T().loading;
const skel = document.getElementById('skel');
const clearSkel = () => {
skel?.remove();
list.removeAttribute('aria-busy');
};
try {
if (location.protocol === 'file:') throw new Error('file');
const data = {ru:'README.ru.md', en:'README.md', zh:'README.zh.md'}[LANG];
Expand All @@ -1252,6 +1347,7 @@ <h1>Пособие по выгодной жизни</h1>
if (!sections.length) throw new Error('empty');
GLOSS = parseGlossary(md);
PERF.log('парсинг текста', t1, files.length + ' файлов');
clearSkel();
status.remove();
const t2 = performance.now(); build(sections); PERF.log('построение DOM', t2, CARDS.length + ' карточек');
buildGlossary(); readUrl(); wire(); applyI18n(); apply();
Expand All @@ -1272,6 +1368,11 @@ <h1>Пособие по выгодной жизни</h1>
if (target) requestAnimationFrame(() => target.scrollIntoView());
}
} catch (err) {
clearSkel();
const c = document.getElementById('cnt'), t = document.getElementById('tot');
if (c) c.textContent = '–';
if (t) t.textContent = '–';
status.hidden = false;
status.innerHTML = err.message === 'file'
? T().errFile
: T().errLoad(esc(String(err.message)));
Expand Down
Loading
Loading