Сайт-визитка: главная, архитектура, проекты
- Главная: фото, факты, соцсети, форма связи, анимации - Архитектура: карточки проектов и отдельные страницы (Тихий двор, павильон, «Лофт») - Проекты: Aman's World (скриншоты, описание) и хоумлаб - Меню из config.json, шаблон новых страниц, черновики training/work/fixed - Чистый HTML/CSS/JS без сборки, шрифты и картинки локально Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
// Подключается в <head>: помечает «JS работает», сразу запрашивает config.json и даёт общий сигнал готовности
|
||||
(function () {
|
||||
var root = document.documentElement;
|
||||
root.className += ' js';
|
||||
window.siteReady = function () { root.classList.add('ready'); };
|
||||
setTimeout(window.siteReady, 2000); // страховка: страница не останется скрытой
|
||||
window.siteConfig = fetch('/config.json')
|
||||
.then(function (r) { if (!r.ok) throw 0; return r.json(); })
|
||||
.catch(function () { return null; });
|
||||
})();
|
||||
@@ -0,0 +1,52 @@
|
||||
// Заполняет страницу из config.json
|
||||
(function () {
|
||||
var $ = function (id) { return document.getElementById(id); };
|
||||
var make = function (tag, cls, text) { var n = document.createElement(tag); if (cls) n.className = cls; if (text) n.textContent = text; return n; };
|
||||
var safe = function (u) { return /^https?:\/\//i.test(u) ? u : '#'; };
|
||||
|
||||
window.siteConfig.then(function (c) {
|
||||
if (!c) return;
|
||||
if (c.name) {
|
||||
$('name').textContent = c.name;
|
||||
document.title = c.name + ': давай общаться';
|
||||
$('initials').textContent = c.name.split(/\s+/).map(function (w) { return w.charAt(0); }).join('').slice(0, 2).toUpperCase();
|
||||
}
|
||||
if (c.status) $('status').textContent = c.status;
|
||||
if (c.tagline) {
|
||||
// текст раскладывается на слова: каждое появляется по очереди (стили в css, .w)
|
||||
var tg = $('tagline'); tg.textContent = '';
|
||||
c.tagline.split(/\s+/).forEach(function (word, i, all) {
|
||||
var w = make('span', 'w', word); w.style.setProperty('--i', i); tg.appendChild(w);
|
||||
if (i < all.length - 1) tg.appendChild(document.createTextNode(' '));
|
||||
});
|
||||
}
|
||||
(c.facts || []).forEach(function (f) {
|
||||
var li = make('li'); li.appendChild(make('span', 'em', f.emoji || '')); li.appendChild(document.createTextNode(' ' + f.text));
|
||||
$('facts').appendChild(li);
|
||||
});
|
||||
if (c.cta) $('cta').textContent = c.cta;
|
||||
if (c.photo && /^\/[\w\-./]+$/.test(c.photo)) {
|
||||
var img = document.createElement('img');
|
||||
img.src = c.photo; img.alt = ''; img.width = 480; img.height = 480; img.fetchPriority = 'high';
|
||||
$('avatar').textContent = ''; $('avatar').classList.add('has-photo'); $('avatar').appendChild(img);
|
||||
}
|
||||
(c.tags || []).forEach(function (t) {
|
||||
var li = make('li');
|
||||
li.appendChild(make('span', 'em', t.emoji || ''));
|
||||
li.appendChild(document.createTextNode(' ' + t.text));
|
||||
$('tags').appendChild(li);
|
||||
});
|
||||
|
||||
var list = $('links'), mailLi = $('mail').parentNode;
|
||||
(c.socials || []).forEach(function (s) {
|
||||
var a = make('a', 'link');
|
||||
a.href = safe(s.url); a.target = '_blank'; a.rel = 'me noopener';
|
||||
a.appendChild(make('span', 'ic', (s.label || '?').charAt(0)));
|
||||
a.appendChild(make('span', 'lk', s.label));
|
||||
a.appendChild(make('span', 'lh', s.handle || ''));
|
||||
var li = make('li'); li.appendChild(a);
|
||||
list.insertBefore(li, mailLi);
|
||||
});
|
||||
if (c.email) { $('mail').href = 'mailto:' + c.email; $('mail').querySelector('.lh').textContent = c.email; }
|
||||
}).then(window.siteReady);
|
||||
})();
|
||||
@@ -0,0 +1,20 @@
|
||||
// Числа в карточках проекта «набегают» от нуля, когда появляются на экране. Без JS и при «уменьшить движение» остаются как есть.
|
||||
(function () {
|
||||
var nodes = document.querySelectorAll('[data-count]');
|
||||
if (!nodes.length || !('IntersectionObserver' in window) || matchMedia('(prefers-reduced-motion: reduce)').matches) return;
|
||||
var fmt = function (n, dec) { return n.toFixed(dec).replace('.', ','); };
|
||||
var run = function (el) {
|
||||
var raw = el.getAttribute('data-count'), end = parseFloat(raw), dec = (raw.split('.')[1] || '').length;
|
||||
if (isNaN(end)) return;
|
||||
var t0 = performance.now(), dur = 900;
|
||||
(function step(now) {
|
||||
var k = Math.min(1, (now - t0) / dur), e = 1 - Math.pow(1 - k, 3);
|
||||
el.textContent = fmt(end * e, dec);
|
||||
if (k < 1) requestAnimationFrame(step);
|
||||
})(t0);
|
||||
};
|
||||
var io = new IntersectionObserver(function (items) {
|
||||
items.forEach(function (it) { if (it.isIntersecting) { run(it.target); io.unobserve(it.target); } });
|
||||
}, { threshold: 0.6 });
|
||||
nodes.forEach(function (n) { io.observe(n); });
|
||||
})();
|
||||
@@ -0,0 +1,31 @@
|
||||
// Верхнее меню из config.json: pages. На главной показывается, только если есть страницы; на остальных всегда.
|
||||
(function () {
|
||||
window.siteConfig.then(function (c) {
|
||||
var nav = document.getElementById('topnav');
|
||||
if (nav && c) {
|
||||
var pages = (c.pages || []).filter(function (p) { return !p.draft; }); // черновики в меню не попадают
|
||||
var norm = function (p) { return p.replace(/\/+$/, '') + '/'; };
|
||||
var here = norm(location.pathname);
|
||||
var home = here === '/';
|
||||
if (pages.length || !home) {
|
||||
var brand = document.createElement('a');
|
||||
brand.className = 'brand'; brand.href = '/';
|
||||
var parts = (c.name || 'Главная').split(' ');
|
||||
brand.appendChild(document.createTextNode(parts[0]));
|
||||
if (parts.length > 1) { var rest = document.createElement('span'); rest.className = 'brand-rest'; rest.textContent = ' ' + parts.slice(1).join(' '); brand.appendChild(rest); }
|
||||
nav.appendChild(brand);
|
||||
var ul = document.createElement('ul');
|
||||
pages.forEach(function (p) {
|
||||
if (!/^\/[\w\-./]*$/.test(p.url)) return; // только внутренние адреса
|
||||
var a = document.createElement('a');
|
||||
a.className = 'pill'; a.href = p.url; a.textContent = p.label;
|
||||
var u = norm(p.url);
|
||||
if (u === here || (u !== '/' && here.indexOf(u) === 0)) a.setAttribute('aria-current', 'page');
|
||||
var li = document.createElement('li'); li.appendChild(a); ul.appendChild(li);
|
||||
});
|
||||
nav.appendChild(ul);
|
||||
document.body.classList.add('has-nav');
|
||||
}
|
||||
}
|
||||
}).then(window.siteReady);
|
||||
})();
|
||||
Reference in New Issue
Block a user