Yeartimer
Yeartimerunknown
javascript
a month ago
13 kB
3
Indexable
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Таймер года — сколько прошло / осталось</title>
<style>
:root{
--passed:#0078ff;
--remaining:#ff7f29;
--bg:#0f1011;
--muted:#9aa0a6;
--card:#101214;
--accent:#de7fff;
}
html,body{height:100%}
body{
margin:28px;
font-family: system-ui, "Segoe UI", Roboto, Arial, sans-serif;
background:var(--bg);
color:#e9eef2;
-webkit-font-smoothing:antialiased;
-moz-osx-font-smoothing:grayscale;
}
.wrap{
max-width:720px;
margin:0 auto;
padding:18px;
background:linear-gradient(180deg,var(--card),#0b0c0d);
border-radius:12px;
border:1px solid rgba(255,255,255,0.03);
box-shadow:0 10px 30px rgba(0,0,0,.6);
}
.title{
text-align:center;
font-weight:700;
font-size:18px;
margin-bottom:6px;
}
.subtitle{
text-align:center;
color:var(--muted);
font-size:13px;
margin-bottom:14px;
}
/* layout: weeks left | diagram | days right */
.main-row{
display:flex;
gap:18px;
align-items:center;
justify-content:center;
flex-wrap:wrap;
margin-bottom:14px;
}
/* small diagrams (div.side) 45% opacity as requested */
.side {
min-width:140px;
text-align:center;
opacity:0.45; /* 45% прозрачности */
}
.side .small{ color:var(--muted); font-size:13px } /* requested style */
.side .big{ font-weight:700; font-size:14px; margin-top:6px }
.svg-wrap{ display:flex; align-items:center; justify-content:center; }
svg.timer{ width:240px; height:240px; display:block }
.mini-chart{ width:64px; height:64px; display:block; margin:6px auto 0; }
.info{
display:flex;
gap:12px;
justify-content:center;
flex-wrap:wrap;
align-items:center;
}
.info > div{min-width:180px;text-align:center}
.big{font-weight:700;font-size:16px}
.small{color:var(--muted);font-size:13px}
.legend{display:flex;gap:12px;justify-content:center;margin-top:12px}
.legend .item{display:flex;gap:8px;align-items:center;color:var(--muted);font-size:13px}
.sw{width:14px;height:14px;border-radius:3px;display:inline-block}
.controls{display:flex;gap:8px;justify-content:center;margin-top:14px}
button{
padding:8px 12px;border-radius:8px;border:1px solid rgba(255,255,255,0.04);
background:#0c0d0e;color:#eaeff3;cursor:pointer;font-weight:600;
}
button[disabled]{opacity:.45;cursor:default}
.muted{color:var(--muted)}
/* responsive */
@media (max-width:640px){
.main-row{ flex-direction:column; gap:12px }
.side{ min-width:unset }
}
</style>
</head>
<body>
<div class="wrap" role="main" aria-label="Таймер года">
<div class="title">Таймер года</div>
<div class="subtitle">Показывает, сколько прошло и сколько осталось в текущем году</div>
<div class="main-row" aria-hidden="false">
<!-- Left: weeks passed / remaining with mini chart -->
<div class="side" aria-label="Недели">
<div class="small">Недели:</div>
<svg class="mini-chart" viewBox="0 0 40 40" aria-hidden="true">
<g transform="translate(20,20)">
<circle r="16" fill="none" stroke="var(--remaining)" stroke-width="6"></circle>
<circle id="weeksPassedChart" r="16" fill="none" stroke="var(--passed)" stroke-width="6"
stroke-linecap="round" transform="rotate(-90)" stroke-dasharray="100" stroke-dashoffset="100"></circle>
</g>
</svg>
<div id="weeksPassed" class="big">0 прошло</div>
<div id="weeksRemaining" class="small muted">0 осталось</div>
</div>
<!-- Center: diagram -->
<div class="svg-wrap" aria-hidden="false">
<svg class="timer" viewBox="0 0 120 120" role="img" aria-label="Круговая диаграмма прогресса года">
<g transform="translate(60,60)">
<!-- фон круга (полный: оставшееся цвет) -->
<circle r="52" fill="none" stroke="var(--remaining)" stroke-width="12"></circle>
<!-- прогресс (пройденное) — будет скрывать часть фона через stroke-dashoffset -->
<circle id="passed" r="52" fill="none" stroke="var(--passed)" stroke-width="12"
stroke-linecap="round" transform="rotate(-90)" stroke-dasharray="326.726016" stroke-dashoffset="326.726016"></circle>
<!-- маленький центр для контраста -->
<circle r="34" fill="#0b0c0d" stroke="rgba(255,255,255,0.03)" stroke-width="0.5"></circle>
<!-- централь текст (процент пройдено / осталось) -->
<text id="centerPct" x="0" y="-4" text-anchor="middle" font-size="12" fill="#fff" font-weight="700"></text>
<text id="centerLbl" x="0" y="12" text-anchor="middle" font-size="9" fill="var(--muted)"></text>
</g>
</svg>
</div>
<!-- Right: days passed / remaining with mini chart -->
<div class="side" aria-label="Дни">
<div class="small">Дни:</div>
<svg class="mini-chart" viewBox="0 0 40 40" aria-hidden="true">
<g transform="translate(20,20)">
<circle r="16" fill="none" stroke="var(--remaining)" stroke-width="6"></circle>
<circle id="daysPassedChart" r="16" fill="none" stroke="var(--passed)" stroke-width="6"
stroke-linecap="round" transform="rotate(-90)" stroke-dasharray="100" stroke-dashoffset="100"></circle>
</g>
</svg>
<div id="daysPassed" class="big">0 прошло</div>
<div id="daysRemaining" class="small muted">0 осталось</div>
</div>
</div>
<div class="info" aria-hidden="false">
<div>
<div class="small">Прошло:</div>
<div id="passedPct" class="big">0.00%</div>
<div id="passedSec" class="small muted">0 сек</div>
</div>
<!-- Добавлен блок с текущей датой между "Прошло" и "Осталось" -->
<div>
<div class="small">Текущая дата:</div>
<div id="currentDate" class="big" style="line-height:1.05"></div>
</div>
<div>
<div class="small">Осталось:</div>
<div id="remPct" class="big">0.00%</div>
<div id="remSec" class="small muted">0 сек</div>
</div>
</div>
<div class="legend" aria-hidden="true">
<div class="item"><span class="sw" style="background:var(--passed)"></span>Прошло</div>
<div class="item"><span class="sw" style="background:var(--remaining)"></span>Осталось</div>
</div>
</div>
<script>
/*
Таймер года — обновляется в реальном времени (каждую секунду).
Мини-диаграммы (div.side) имеют opacity:0.45.
Между "Прошло" и "Осталось" выводится текущая дата в формате:
день недели (с заглавной буквы), <br>
число, <br>
месяц — короткий вариант с точкой, строчными, <br>
год + " г."
*/
const R = 52;
const CIRC = 2*Math.PI*R;
const passedCircle = document.getElementById('passed');
passedCircle.style.strokeDasharray = String(CIRC);
const centerPct = document.getElementById('centerPct');
const centerLbl = document.getElementById('centerLbl');
const passedPctEl = document.getElementById('passedPct');
const remPctEl = document.getElementById('remPct');
const passedSecEl = document.getElementById('passedSec');
const remSecEl = document.getElementById('remSec');
const weeksPassedEl = document.getElementById('weeksPassed');
const weeksRemainingEl = document.getElementById('weeksRemaining');
const daysPassedEl = document.getElementById('daysPassed');
const daysRemainingEl = document.getElementById('daysRemaining');
const weeksPassedChart = document.getElementById('weeksPassedChart');
const daysPassedChart = document.getElementById('daysPassedChart');
const currentDateEl = document.getElementById('currentDate');
const pauseBtn = document.getElementById('pauseBtn');
const resumeBtn = document.getElementById('resumeBtn');
const resetBtn = document.getElementById('resetBtn');
let tickId = null;
const fmtNum = n => new Intl.NumberFormat('ru-RU').format(n);
function boundariesFor(date){
const y = date.getFullYear();
const start = new Date(y,0,1,0,0,0,0);
const end = new Date(y+1,0,1,0,0,0,0);
return {start, end, year: y};
}
function isLeapYear(y){ return (y%4===0 && y%100!==0) || (y%400===0); }
function secondsInYear(y){ return (isLeapYear(y)?366:365)*86400; }
function formatCurrentDate(now){
// weekday (capitalize first letter), day (numeric), month (short with dot), year + " г."
const weekdayRaw = new Intl.DateTimeFormat('ru-RU', {weekday:'long'}).format(now);
const weekday = weekdayRaw.charAt(0).toUpperCase() + weekdayRaw.slice(1); // "Среда"
const day = now.getDate();
let monthShort = new Intl.DateTimeFormat('ru-RU', {month:'short'}).format(now).toLowerCase();
if (!monthShort.endsWith('.')) monthShort = monthShort + '.'; // ensure trailing dot, e.g. "июн."
const year = now.getFullYear();
return `${weekday}<br>${day}<br>${monthShort}<br>${year} г.`;
}
function tick(){
const now = new Date();
let {start, end, year} = boundariesFor(now);
if (now >= end){
const nextNow = new Date();
({start, end, year} = boundariesFor(nextNow));
}
const elapsedMs = now - start;
const remMs = end - now;
const elapsedSec = Math.max(0, Math.floor(elapsedMs/1000));
const remSec = Math.max(0, Math.ceil(remMs/1000));
const totalSec = secondsInYear(year);
const passedFrac = Math.min(1, Math.max(0, elapsedSec / totalSec));
const remFrac = 1 - passedFrac;
const passedPct = passedFrac * 100;
const remPct = remFrac * 100;
// big circle
const offset = CIRC * (1 - passedFrac);
passedCircle.style.strokeDashoffset = String(offset);
centerPct.textContent = passedPct.toFixed(2).replace('.',',') + '%';
centerLbl.textContent = 'прошло';
passedPctEl.textContent = passedPct.toFixed(2).replace('.',',') + '%';
remPctEl.textContent = remPct.toFixed(2).replace('.',',') + '%';
passedSecEl.textContent = fmtNum(elapsedSec) + ' сек';
remSecEl.textContent = fmtNum(remSec) + ' сек';
// days
const daysPassed = Math.floor(elapsedSec / 86400);
const daysRemaining = Math.max(0, Math.ceil((totalSec - elapsedSec) / 86400));
daysPassedEl.textContent = fmtNum(daysPassed) + ' прошло';
daysRemainingEl.textContent = fmtNum(daysRemaining) + ' осталось';
// weeks
const secPerWeek = 7 * 86400;
const weeksPassed = Math.floor(elapsedSec / secPerWeek);
const weeksRemaining = Math.max(0, Math.ceil((totalSec - elapsedSec) / secPerWeek));
weeksPassedEl.textContent = fmtNum(weeksPassed) + ' прошло';
weeksRemainingEl.textContent = fmtNum(weeksRemaining) + ' осталось';
// mini charts (dasharray 100)
const weeksTotalWeeks = totalSec / secPerWeek;
const weeksFrac = Math.min(1, elapsedSec / (weeksTotalWeeks * secPerWeek));
weeksPassedChart.style.strokeDasharray = '100';
weeksPassedChart.style.strokeDashoffset = String(100 * (1 - weeksFrac));
const totalDays = isLeapYear(year)?366:365;
const daysFrac = Math.min(1, elapsedSec / (totalDays * 86400));
daysPassedChart.style.strokeDasharray = '100';
daysPassedChart.style.strokeDashoffset = String(100 * (1 - daysFrac));
// current date block
if (currentDateEl) currentDateEl.innerHTML = formatCurrentDate(now);
}
function startTicker(){
if (tickId) clearInterval(tickId);
tick();
tickId = setInterval(tick, 1000);
if (pauseBtn) pauseBtn.disabled = false;
if (resumeBtn) resumeBtn.disabled = true;
}
function stopTicker(){
if (tickId) { clearInterval(tickId); tickId = null; }
if (pauseBtn) pauseBtn.disabled = true;
if (resumeBtn) resumeBtn.disabled = false;
}
window.addEventListener('DOMContentLoaded', () => {
// ensure initial state for mini charts
weeksPassedChart.style.strokeDasharray = '100';
weeksPassedChart.style.strokeDashoffset = '100';
daysPassedChart.style.strokeDasharray = '100';
daysPassedChart.style.strokeDashoffset = '100';
startTicker();
});
if (pauseBtn) pauseBtn.addEventListener('click', () => stopTicker());
if (resumeBtn) resumeBtn.addEventListener('click', () => startTicker());
if (resetBtn) resetBtn.addEventListener('click', () => { startTicker(); tick(); });
</script>
</body>
</html>
Editor is loading...
Leave a Comment